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 "" - echo "" - echo "" - echo "" - echo "" - echo "" - if test -n "$base_uri"; then - echo "" - fi - echo "" - echo "" - echo "" - echo "$1" - : "${inicio:=}" - if test "$mirror" = true; then - echo "

Ojo: este sitio es un espejo (mirror). nulo.in es la fuente.

" - fi - if test "$inicio" != true; then - echo "☚ Volver al inicio" - fi - if test -n "$2"; then - echo "
" - echo "

$1

" - echo "

Último cambio:

" - echo "
" - fi -} - -markdown () { - # TODO: hacky - cmark --unsafe "$1" \ - | sed "s/\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: 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( + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\{0s} + \\ + , .{ title, src_name }); + // if test "$mirror" = true; then + // echo "

Ojo: este sitio es un espejo (mirror). nulo.in es la fuente.

" + // fi + if (options.ir_al_inicio) { + try writer.writeAll("☚ Volver al inicio\n"); + } + if (options.header) { + try writer.print( + \\
+ \\

{s}

+ \\Historial + \\
+ \\ + , .{ 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 = "{0s} + , .{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); +}