From b60e5850cf36fe55275d1a894acd47c92ef7f2ba Mon Sep 17 00:00:00 2001
From: Nulo <git@nulo.in>
Date: Sat, 16 Jul 2022 21:59:32 -0300
Subject: [PATCH] =?UTF-8?q?Reemplazar=20script=20de=20compilaci=C3=B3n=20c?=
 =?UTF-8?q?on=20script=20en=20Zig?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Squashed commit of the following:

commit ea51959ee8f9c0c56d2bd0669906c1a4551d573a
Author: Nulo <git@nulo.in>
Date:   Sat Jul 16 21:56:30 2022 -0300

    ci: Usar gcc

    No funciona con clang ya que no provee `cc`

commit c152c8ec9fb5544745ac5052d626bbebf195f5dc
Author: Nulo <git@nulo.in>
Date:   Sat Jul 16 21:54:27 2022 -0300

    ci: Instalar clang

    Zig necesita un compilador de C para encontrar las librerías C

commit 0f360aaa1061c4f6168cc801d7640eec8b05f625
Author: Nulo <git@nulo.in>
Date:   Sat Jul 16 21:52:42 2022 -0300

    ci: No instalar cmark de terminal, instalar musl-dev

commit 24da2c684ed847620214d87c6e0fcfa0d782787b
Author: Nulo <git@nulo.in>
Date:   Sat Jul 16 21:43:04 2022 -0300

    ci: Agregar repo testing

commit 2163a9f71ebc6db81d884380458b6e3637a554eb
Author: Nulo <git@nulo.in>
Date:   Sat Jul 16 21:40:10 2022 -0300

    ci: Usar edge para usar testing/zig

commit 259fad54e5290ef8c51dac23e87d0d712f9526fb
Author: Nulo <git@nulo.in>
Date:   Sat Jul 16 21:07:22 2022 -0300

    Reemplazar script de compilación con script en Zig
---
 .gitignore      |   1 +
 .woodpecker.yml |   9 +-
 README.md       |   8 +-
 build.sh        |  62 --------------
 compilar.zig    | 223 ++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 235 insertions(+), 68 deletions(-)
 delete mode 100755 build.sh
 create mode 100644 compilar.zig

diff --git a/.gitignore b/.gitignore
index 567609b..3714436 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 build/
+zig-cache/
diff --git a/.woodpecker.yml b/.woodpecker.yml
index d8552df..177d697 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,11 +1,10 @@
 pipeline:
   build:
-    image: docker.io/alpine:3.16
+    image: docker.io/alpine:edge
     commands:
-      - apk add cmark git cmd:setup-timezone lua5.1
-      # Para generar las fechas bien
-      - setup-timezone -z America/Argentina/Buenos_Aires
-      - base_uri=https://nulo.in/ ./build.sh
+      - echo "https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
+      - apk add musl-dev cmark-dev lua5.1 zig gcc
+      - zig run compilar.zig -lc -lcmark
   deploy:
     image: docker.io/alpine:3.14
     commands:
diff --git a/README.md b/README.md
index b1b5b31..171385d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,12 @@
 Este es el README para [el repositorio del sitio](https://gitea.nulo.in/Nulo/sitio).
 
-Escuché [HOT DEMON B!TCHES NEAR U ! ! ! (de CORPSE y Night Lovell)](https://youtu.be/wqZnO71PBis) en loop para hacer la versión inicial del sitio.
+## Compilar
+
+Require [Zig](https://ziglang.org), shell, cmark (con cmark-dev) y Lua 5.1.
+
+```
+zig run compilar.zig -lc -lcmark
+```
 
 ---
 
diff --git a/build.sh b/build.sh
deleted file mode 100755
index 41dcac7..0000000
--- a/build.sh
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/bin/sh
-
-template () {
-	echo "<!doctype html>"
-	echo "<meta charset=utf-8>"
-	echo "<meta name=viewport content='width=device-width, initial-scale=1.0'>"
-	echo "<meta name=author content=Nulo>"
-	echo "<meta property=og:title content='$1'>"
-	echo "<meta property=og:type content=website>"
-	if test -n "$base_uri"; then
-		echo "<meta property=og:url content='${base_uri}${1}.html'>"
-	fi
-	echo "<meta property=og:image content=cowboy.svg>"
-	echo "<link rel=stylesheet href=drip.css>"
-	echo "<link rel=icon href=cowboy.svg>"
-	echo "<title>$1</title>"
-	: "${inicio:=}"
-	if test "$mirror" = true; then
-		echo "<p style=color:darkred>Ojo: este sitio es un espejo (mirror). <a href=https://nulo.in>nulo.in</a> es la fuente.</p>"
-	fi
-	if test "$inicio" != true; then
-		echo "<a href=.>☚ Volver al inicio</a>"
-	fi
-	if test -n "$2"; then
-		echo "<header>"
-		echo "<h1>$1</h1>"
-		echo "<p><small>Último cambio: <time datetime='$(git log -1 --format=%ai "$2")'>$(date -d "@$(git log -1 --format=%at "$2")" '+%Y-%m-%d %H:%M')</time></small></p>"
-		echo "</header>"
-	fi
-}
-
-markdown () {
-	# TODO: hacky
-	cmark --unsafe "$1" \
-	| sed "s/<a h/<a rel='noopener noreferrer' h/gi" \
-	| sed 's/\[\[\(.*\)\]\]/<a href="\1.html">\1<\/a>/g'
-}
-
-outdir=build
-mkdir -p $outdir
-# Autocopiarnos :)
-cp ./*.sh ./*.md ./*.css ./*.png ./*.jpg ./*.mp4 ./*.svg ./*.html "$outdir"
-
-index="$outdir/index.html"
-inicio=true header=false template "nulo.in" > "$index"
-markdown index.md >> "$index"
-
-for file in *.md; do
-	test "$(basename "$file")" = index.md && continue
-	title="$(basename "$file" .md)"
-	outfile="$outdir/$title.html"
-	template "$title" "$file" > "$outfile"
-	markdown "$file" >> "$outfile"
-done
-
-for file in *.gen; do
-	title="$(basename "$file" .gen)"
-	outfile="$outdir/$title.html"
-	template "$title" "$file" > "$outfile"
-	"./$file" >> "$outfile"
-done
-
diff --git a/compilar.zig b/compilar.zig
new file mode 100644
index 0000000..530a43a
--- /dev/null
+++ b/compilar.zig
@@ -0,0 +1,223 @@
+const std = @import("std");
+const c = @cImport({
+    @cInclude("cmark.h");
+});
+const endsWith = std.mem.endsWith;
+
+fn stripExtension(file_name: []const u8) ![]const u8 {
+    const index = std.mem.lastIndexOfLinear(u8, file_name, ".");
+    if (index) |i| {
+        return file_name[0..i];
+    } else return error.NoExtension;
+}
+
+const HeaderOptions = struct {
+    // Si esto es true, se muestra un botón para volver al index.
+    ir_al_inicio: bool = true,
+    // Si esto es true, se muestra un <header>.
+    header: bool = true,
+};
+fn header(
+    writer: std.fs.File.Writer,
+    title: []const u8,
+    src_name: []const u8,
+    options: HeaderOptions,
+) !void {
+    // TODO: deshardcodear base_uri
+    try writer.print(
+        \\<!doctype html>
+        \\<meta charset=utf-8>
+        \\<meta name=viewport content="width=device-width, initial-scale=1.0">
+        \\<meta name=author content=Nulo>
+        \\<meta property=og:title content="{0s}">
+        \\<meta property=og:type content=website>
+        \\<meta property=og:url content="https://nulo.in/{1s}.html">
+        \\<meta property=og:image content=cowboy.svg>
+        \\<link rel=stylesheet href=drip.css>
+        \\<link rel=icon href=cowboy.svg>
+        \\<title>{0s}</title>
+        \\
+    , .{ title, src_name });
+    // if test "$mirror" = true; then
+    // 	echo "<p style=color:darkred>Ojo: este sitio es un espejo (mirror). <a href=https://nulo.in>nulo.in</a> es la fuente.</p>"
+    // fi
+    if (options.ir_al_inicio) {
+        try writer.writeAll("<a href=.>☚ Volver al inicio</a>\n");
+    }
+    if (options.header) {
+        try writer.print(
+            \\<header>
+            \\<h1>{s}</h1>
+            \\<a href="https://gitea.nulo.in/Nulo/sitio/commits/branch/ANTIFASCISTA/{s}">Historial</a>
+            \\</header>
+            \\
+        , .{ title, src_name });
+    }
+}
+
+pub fn main() !void {
+    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+    defer _ = gpa.deinit();
+    const allocator = gpa.allocator();
+
+    var cwd = try std.fs.cwd().openDir(".", .{ .iterate = true });
+    defer cwd.close();
+    var cwd_iterator = cwd.iterate();
+
+    var build_dir = try cwd.makeOpenPath("build", .{});
+    defer build_dir.close();
+
+    while (try cwd_iterator.next()) |entry| {
+        if (entry.kind != .File) continue;
+
+        // Autocopiarnos :)
+        if (endsWith(u8, entry.name, ".sh") or
+            endsWith(u8, entry.name, ".md") or
+            endsWith(u8, entry.name, ".css") or
+            endsWith(u8, entry.name, ".png") or
+            endsWith(u8, entry.name, ".jpg") or
+            endsWith(u8, entry.name, ".mp4") or
+            endsWith(u8, entry.name, ".svg") or
+            endsWith(u8, entry.name, ".html"))
+        {
+            try cwd.copyFile(entry.name, build_dir, entry.name, .{});
+        }
+
+        if (endsWith(u8, entry.name, ".md"))
+            try generateMarkdown(allocator, cwd, entry.name, build_dir);
+        if (endsWith(u8, entry.name, ".gen"))
+            try generateExecutable(allocator, cwd, entry.name, build_dir);
+    }
+}
+
+const Writer = std.io.BufferedWriter(4096, std.fs.File.Writer).Writer;
+
+fn hackilyTransformHtml(input: []const u8, writer: Writer) !void {
+    var iter = std.mem.split(u8, input, "\n");
+    while (iter.next()) |line| {
+        var index: usize = 0;
+
+        while (index < line.len) : (index += 1) {
+            const rest = line[index..];
+
+            const link_marker = "<a h";
+            if (std.ascii.startsWithIgnoreCase(rest, link_marker)) {
+                index += link_marker.len - 1;
+                try writer.writeAll("<a rel='noopener noreferrer' h");
+                continue;
+            }
+
+            if (std.mem.startsWith(u8, rest, "[[")) {
+                if (std.mem.indexOf(u8, rest, "]]")) |end| {
+                    const name = rest[2..end];
+                    index += 2 + name.len + 2 - 1;
+                    try writer.print(
+                        \\<a href="{0s}.html">{0s}</a>
+                    , .{name});
+                    continue;
+                }
+            }
+
+            try writer.writeByte(line[index]);
+        }
+
+        try writer.writeByte('\n');
+    }
+}
+
+fn generateMarkdown(
+    allocator: std.mem.Allocator,
+    cwd: std.fs.Dir,
+    src_name: []const u8,
+    build_dir: std.fs.Dir,
+) !void {
+    var file = try cwd.openFile(src_name, .{});
+    defer file.close();
+    const markdown = try file.readToEndAllocOptions(
+        allocator,
+        69696969,
+        null,
+        @alignOf(u32),
+        0,
+    );
+    defer allocator.free(markdown);
+    const html = c.cmark_markdown_to_html(
+        markdown.ptr,
+        markdown.len,
+        c.CMARK_OPT_UNSAFE, //| c.CMARK_OPT_SMART,
+    );
+    defer std.c.free(html);
+
+    const output_file_name = try std.fmt.allocPrint(
+        allocator,
+        "{s}.html",
+        .{try stripExtension(src_name)},
+    );
+    defer allocator.free(output_file_name);
+
+    const is_index = std.ascii.eqlIgnoreCase(src_name, "index.md");
+
+    const title = if (is_index)
+        "nulo.in"
+    else
+        try stripExtension(src_name);
+
+    var output = try build_dir.createFile(output_file_name, .{});
+    defer output.close();
+    try header(output.writer(), title, src_name, .{
+        .ir_al_inicio = !is_index,
+        .header = !is_index,
+    });
+    var buffered_writer = std.io.bufferedWriter(output.writer());
+    try hackilyTransformHtml(
+        std.mem.span(html),
+        buffered_writer.writer(),
+    );
+    try buffered_writer.flush();
+}
+
+fn generateExecutable(
+    allocator: std.mem.Allocator,
+    cwd: std.fs.Dir,
+    src_name: []const u8,
+    build_dir: std.fs.Dir,
+) !void {
+    _ = cwd;
+    const output_file_name = try std.fmt.allocPrint(
+        allocator,
+        "{s}.html",
+        .{try stripExtension(src_name)},
+    );
+    defer allocator.free(output_file_name);
+
+    const title = try stripExtension(src_name);
+
+    var output = try build_dir.createFile(output_file_name, .{});
+    defer output.close();
+    try header(output.writer(), title, src_name, .{});
+
+    const executable_name = try std.fmt.allocPrint(allocator, "./{s}", .{src_name});
+    defer allocator.free(executable_name);
+
+    // const process = try std.ChildProcess.init(&.{executable_name}, allocator);
+    // defer process.deinit();
+    // process.stdout_behavior = .Ignore;
+    // process.stdout = output;
+    // const term = try process.spawnAndWait();
+    const result = try std.ChildProcess.exec(.{
+        .allocator = allocator,
+        .argv = &.{executable_name},
+    });
+    defer allocator.free(result.stdout);
+    defer allocator.free(result.stderr);
+    if (result.stderr.len > 0) {
+        std.log.err("{s} stderr: {s}", .{ src_name, result.stderr });
+    }
+    switch (result.term) {
+        .Exited => |status| {
+            if (status != 0) return error.ProcessFailed;
+        },
+        else => unreachable,
+    }
+    try output.writeAll(result.stdout);
+}
