download patch
commit c641d71ac828a0e83f74777e206b9fc8d2828427
Author: tri <tri@thac.loan>
Date: Sat Oct 11 11:21:35 2025 +0700
first slice: parse .dj files with frontmatter
diff --git a/.luarc.json b/.luarc.json
new file mode 100644
index 0000000..625f331
--- /dev/null
+++ b/.luarc.json
+{
+ "diagnostics.disable": ["lowercase-global"]
+}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5e36e86
--- /dev/null
+++ b/Makefile
+test:
+ zig build test --watch
+
+clean:
+ rm -rf .zig-cache zig-out
diff --git a/sample/first/index.dj b/sample/first/index.dj
new file mode 100644
index 0000000..0e7f140
--- /dev/null
+++ b/sample/first/index.dj
+```
+title: First
+published_at: 2025-10-09T21:13:03+07:00
+```
+
+## First!
+
+Hello _friend_.
diff --git a/sample/first/index.html b/sample/first/index.html
new file mode 100644
index 0000000..af32d2e
--- /dev/null
+++ b/sample/first/index.html
+<section id="First">
+<h2>First!</h2>
+<p>Hello <em>friend</em>.</p>
+</section>
diff --git a/sample/meta.json b/sample/meta.json
new file mode 100644
index 0000000..f1b193f
--- /dev/null
+++ b/sample/meta.json
+{
+ "site_name": "Sample Site",
+ "tagline": "Who samples the samplers?"
+}
diff --git a/sample/second/index.dj b/sample/second/index.dj
new file mode 100644
index 0000000..22e6cfa
--- /dev/null
+++ b/sample/second/index.dj
+```
+title: Second post
+published_at: 2025-10-11T09:04:40+07:00
+```
+
+Look ma, second post!
diff --git a/sample/second/index.html b/sample/second/index.html
new file mode 100644
index 0000000..21750bd
--- /dev/null
+++ b/sample/second/index.html
+<p>Look ma, second post!</p>
diff --git a/sample/second/meta.json b/sample/second/meta.json
new file mode 100644
index 0000000..9261e08
--- /dev/null
+++ b/sample/second/meta.json
+{
+ "title": "Secondly",
+ "published_at": "2025-10-09T23:51:45+07:00"
+}
diff --git a/src/Djot.lua b/src/Djot.lua
new file mode 100644
index 0000000..ee6cc40
--- /dev/null
+++ b/src/Djot.lua
+local djot = require("djot");
+
+--ignore_frontmatter = { {
+-- code_block = {
+-- enter = function(e)
+-- if e.lang == "frontmatter" then
+-- e.text = "shit"
+-- end
+-- end
+-- ,
+-- exit = function(e)
+-- if e.lang == "frontmatter" then
+-- e.attr = e.attr or djot.ast.new_attributes()
+-- e.attr.style = "display:none"
+-- end
+-- end
+-- }
+--} }
+
+function djot_to_html(input)
+ local doc = djot.parse(input)
+ --djot.filter.apply_filter(doc, ignore_frontmatter)
+ return djot.render_html(doc)
+end
diff --git a/src/Djot.zig b/src/Djot.zig
index ba81db4..b43c77b 100644
--- a/src/Djot.zig
+++ b/src/Djot.zig
pub fn init(gpa: mem.Allocator) !Djot {
return err;
};
- lua.doString(
- \\djot = require("djot")
- \\function djot_to_html(input)
- \\ return djot.render_html(djot.parse(input))
- \\end
- ) catch |err| {
+ lua.doString(@embedFile("Djot.lua")) catch |err| {
try djot.printError();
return err;
};
pub fn toHtml(self: *const Djot, gpa: mem.Allocator, djot_text: []const u8) ![]c
return result;
}
-/// Caller owns returned memory
pub fn writeHtml(self: *const Djot, writer: *Io.Writer, djot_text: []const u8) !void {
// call the global function
assert(try self.lua.getGlobal("djot_to_html") == .function);
diff --git a/src/Post.zig b/src/Post.zig
new file mode 100644
index 0000000..09bca06
--- /dev/null
+++ b/src/Post.zig
+const std = @import("std");
+const mem = std.mem;
+const println = @import("utils.zig").println;
+const Djot = @import("Djot.zig");
+
+pub const Post = @This();
+
+title: []const u8 = "",
+published_at: []const u8 = "",
+djot_text: []const u8 = "",
+
+pub fn init(gpa: mem.Allocator, input: []const u8) !Post {
+ const fence = "```";
+ var post = Post{};
+
+ var lines = mem.splitScalar(u8, input, '\n');
+ var idx: usize = 0;
+
+ const first_line = lines.next().?;
+ if (!mem.eql(u8, first_line, fence)) {
+ @panic("First line must be " ++ fence ++ " to start frontmatter");
+ }
+ idx += first_line.len + 1;
+
+ // Read frontmatter line by line
+ while (lines.next()) |line| {
+ defer idx += line.len + 1;
+
+ if (mem.eql(u8, line, fence)) {
+ break;
+ }
+ if (mem.startsWith(u8, line, "title: ")) {
+ post.title = try gpa.dupe(u8, line["title: ".len..]);
+ } else if (mem.startsWith(u8, line, "published_at: ")) {
+ post.published_at = try gpa.dupe(u8, line["published_at: ".len..]);
+ }
+ }
+
+ post.djot_text = try gpa.dupe(u8, input[idx..]);
+ return post;
+}
+
+pub fn deinit(self: Post, gpa: mem.Allocator) void {
+ gpa.free(self.title);
+ gpa.free(self.published_at);
+ gpa.free(self.djot_text);
+}
diff --git a/src/main.zig b/src/main.zig
index d90905b..1347cf5 100644
--- a/src/main.zig
+++ b/src/main.zig
const std = @import("std");
+const fs = std.fs;
+const os = std.os;
+const mem = std.mem;
+
+const println = @import("utils.zig").println;
const Djot = @import("Djot.zig");
+const Post = @import("Post.zig");
+
+pub fn main() !u8 {
+ var target_dir: [*:0]const u8 = undefined;
+ switch (os.argv.len) {
+ 1 => target_dir = ".",
+ 2 => target_dir = os.argv[1],
+ else => {
+ std.debug.print("Usage: loa [target_dir]\n", .{});
+ return 1;
+ },
+ }
-pub fn main() !void {
+ try entrypoint(target_dir);
+
+ return 0;
+}
+
+pub fn entrypoint(target_dir: [*:0]const u8) !void {
var gpa_state = std.heap.DebugAllocator(.{}){};
const gpa = gpa_state.allocator();
defer _ = gpa_state.deinit();
pub fn main() !void {
const djot = try Djot.init(gpa);
defer djot.deinit();
- const html = try djot.toHtml(gpa, "# foo");
- defer gpa.free(html);
- std.debug.print("--\n{s}\n--", .{html});
+ var dir = try fs.cwd().openDirZ(target_dir, .{ .iterate = true });
+ defer dir.close();
+
+ var walker = try dir.walk(gpa);
+ defer walker.deinit();
+
+ var posts: std.ArrayList(Post) = try .initCapacity(gpa, 16);
+ defer {
+ for (posts.items) |p| {
+ p.deinit(gpa);
+ }
+ posts.deinit(gpa);
+ }
+
+ var djot_buf: [1024 * 16]u8 = undefined;
+ var writer_buf: [1024 * 4]u8 = undefined;
+ while (try walker.next()) |entry| {
+ if (entry.kind == .file and mem.eql(u8, entry.basename, "index.dj")) {
+ const djot_text = try entry.dir.readFile(entry.basename, &djot_buf);
+
+ const p = try Post.init(gpa, djot_text);
+ try posts.append(gpa, p);
+
+ var html_file = try entry.dir.createFile("index.html", .{});
+ defer html_file.close();
+
+ var html_writer = html_file.writer(&writer_buf);
+ try djot.writeHtml(&html_writer.interface, p.djot_text);
+ try html_writer.interface.flush();
+ }
+ }
+
+ println("Found {d} posts", .{posts.items.len});
}
test {
std.testing.refAllDeclsRecursive(@This());
}
+
+test entrypoint {
+ try entrypoint("sample");
+}
diff --git a/src/utils.zig b/src/utils.zig
new file mode 100644
index 0000000..1e03cdc
--- /dev/null
+++ b/src/utils.zig
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn println(comptime fmt: []const u8, args: anytype) void {
+ return print(fmt ++ "\n", args);
+}