From cb8a17da613aac6b29cbbd6c84c12efcee7268a5 Mon Sep 17 00:00:00 2001 From: Brandon Dyck Date: Mon, 2 Dec 2024 08:56:08 -0700 Subject: [PATCH] Render footnotes (#90) * Render footnotes * More flexible footnote rendering * Make footnotes optional and fix docgen * Return correct type from footnotes?.call * Fix incorrect footnotes? example --- src/context.zig | 3 ++ src/context/Page.zig | 93 ++++++++++++++++++++++++++++++++++++++++ src/context/doctypes.zig | 42 ++++++++++++------ src/exes/docgen.zig | 2 +- src/render/html.zig | 20 +++++++-- 5 files changed, 143 insertions(+), 17 deletions(-) diff --git a/src/context.zig b/src/context.zig index e9b17cb..c499c90 100644 --- a/src/context.zig +++ b/src/context.zig @@ -84,6 +84,7 @@ pub const Value = union(enum) { ctx: Ctx(Value), alternative: Page.Alternative, content_section: Page.ContentSection, + footnote: Page.Footnote, build: *const Build, git: Git, asset: Asset, @@ -174,6 +175,7 @@ pub const Value = union(enum) { *const Page, *Page => .{ .page = v }, Page.Alternative => .{ .alternative = v }, Page.ContentSection => .{ .content_section = v }, + Page.Footnote => .{ .footnote = v }, *const Build => .{ .build = v }, Git => .{ .git = v }, Ctx(Value) => .{ .ctx = v }, @@ -213,6 +215,7 @@ pub const Value = union(enum) { []const Page.Alternative => try Array.init(gpa, Page.Alternative, v), []Page.ContentSection => try Array.init(gpa, Page.ContentSection, v), + []Page.Footnote => try Array.init(gpa, Page.Footnote, v), else => @compileError("TODO: implement Value.from for " ++ @typeName(@TypeOf(v))), }; } diff --git a/src/context/Page.zig b/src/context/Page.zig index 48152bd..d40423d 100644 --- a/src/context/Page.zig +++ b/src/context/Page.zig @@ -150,6 +150,52 @@ pub const Alternative = struct { }; }; }; + +pub const Footnote = struct { + def_id: []const u8, + ref_ids: []const []const u8, + + _page: *const Page, + _idx: usize, + + pub const description = + \\A footnote from a page. + ; + pub const Fields = struct { + pub const def_id = + \\The ID for the footnote definition. + ; + pub const ref_ids = + \\The IDs of the footnote's references, + \\to be used for creating backlinks. + ; + }; + pub const Builtins = struct { + pub const html = struct { + pub const signature: Signature = .{ .ret = .String }; + pub const description = + \\Renders the footnote definition. + ; + pub const examples = ""; + pub fn call( + f: Footnote, + gpa: Allocator, + args: []const Value, + ) !Value { + if (args.len != 0) return .{ .err = "expected 0 arguments" }; + + var buf = std.ArrayList(u8).init(gpa); + const ast = f._page._meta.ast orelse unreachable; + const node = ast.footnotes.values()[f._idx].node; + + try render.html(gpa, ast, node, "", buf.writer()); + return String.init(try buf.toOwnedSlice()); + } + }; + }; + pub const dot = scripty.defaultDot(Footnote, Value, false); +}; + pub const dot = scripty.defaultDot(Page, Value, false); pub const PassByRef = true; @@ -996,6 +1042,53 @@ pub const Builtins = struct { } }; + pub const @"footnotes?" = struct { + pub const signature: Signature = .{ + .params = &.{}, + .ret = .{ .Opt = .{ .Many = .Footnote } }, + }; + pub const description = + \\Returns a list of footnotes for the current page, if any exist. + ; + pub const examples = + \\ + \\
    + \\
  1. + \\ + \\ + \\ + \\ + \\
  2. + \\
+ \\
+ ; + pub fn call( + p: *const Page, + gpa: Allocator, + args: []const Value, + ) !Value { + const bad_arg = .{ + .err = "expected 0 arguments", + }; + if (args.len != 0) return bad_arg; + + const ast = p._meta.ast.?; + if (ast.footnotes.count() == 0) { + return Optional.Null; + } + var _footnotes = try gpa.alloc(Footnote, ast.footnotes.count()); + for (ast.footnotes.values(), 0..) |footnote, i| { + _footnotes[i] = .{ + .def_id = footnote.def_id, + .ref_ids = footnote.ref_ids, + ._page = p, + ._idx = i, + }; + } + return Optional.init(gpa, _footnotes); + } + }; + pub const toc = struct { pub const signature: Signature = .{ .ret = .String }; pub const description = diff --git a/src/context/doctypes.zig b/src/context/doctypes.zig index 2788953..0f4ee7a 100644 --- a/src/context/doctypes.zig +++ b/src/context/doctypes.zig @@ -36,6 +36,7 @@ pub const ScriptyParam = union(enum) { Asset, Alternative, ContentSection, + Footnote, Iterator, Array, String, @@ -51,11 +52,12 @@ pub const ScriptyParam = union(enum) { Opt: Base, Many: Base, - pub const Base = enum { + pub const Base = union(enum) { Site, Page, Alternative, ContentSection, + Footnote, Iterator, String, Int, @@ -63,6 +65,11 @@ pub const ScriptyParam = union(enum) { Date, KV, any, + Many: Base2, + + pub const Base2 = enum { + Footnote, + }; }; pub fn fromType(t: type) ScriptyParam { @@ -76,6 +83,7 @@ pub const ScriptyParam = union(enum) { superhtml.utils.Ctx(context.Value) => .Ctx, context.Page.Alternative => .Alternative, context.Page.ContentSection => .ContentSection, + context.Page.Footnote => .Footnote, context.Asset => .Asset, // context.Slice => .any, context.Optional, ?*const context.Optional => .{ .Opt = .any }, @@ -90,6 +98,8 @@ pub const ScriptyParam = union(enum) { context.Iterator => .Iterator, ?*context.Iterator => .{ .Opt = .Iterator }, []const context.Page.Alternative => .{ .Many = .Alternative }, + []const context.Page.Footnote => .{ .Many = .Footnote }, + ?[]const context.Page.Footnote => .{ .Opt = .{ .Many = .Footnote } }, []const u8 => .String, ?[]const u8 => .{ .Opt = .String }, []const []const u8 => .{ .Many = .String }, @@ -106,14 +116,17 @@ pub const ScriptyParam = union(enum) { comptime is_fn_param: bool, ) []const u8 { switch (p) { - .Many => |m| switch (m) { - inline else => |mm| { + inline .Many => |m| switch (m) { + inline else => { const dots = if (is_fn_param) "..." else ""; - return "[" ++ @tagName(mm) ++ dots ++ "]"; + return "[" ++ @tagName(m) ++ dots ++ "]"; }, }, .Opt => |o| switch (o) { - inline else => |oo| return "?" ++ @tagName(oo), + .Many => |om| switch (om) { + inline else => |omm| return "?[" ++ @tagName(omm) ++ "]", + }, + inline else => return "?" ++ @tagName(o), }, inline else => return @tagName(p), } @@ -123,21 +136,26 @@ pub const ScriptyParam = union(enum) { comptime is_fn_param: bool, ) []const u8 { switch (p) { - .Many => |m| switch (m) { - inline else => |mm| { + inline .Many => |m| switch (m) { + inline else => { const dots = if (is_fn_param) "..." else ""; return std.fmt.comptimePrint( \\[[{0s}]($link.ref("{0s}")){1s}]{2s} , .{ - @tagName(mm), dots, if (is_fn_param or mm == .any) "" else + @tagName(m), dots, if (is_fn_param or m == .any) "" else \\ *(see also [[any]]($link.ref("Array")))* }); }, }, - .Opt => |o| switch (o) { - inline else => |oo| return comptime std.fmt.comptimePrint( + inline .Opt => |o| switch (o) { + inline .Many => |om| switch (om) { + inline else => |omm| return comptime std.fmt.comptimePrint( + \\?[[{0s}]($link.ref("{0s}"))] + , .{@tagName(omm)}), + }, + inline else => return comptime std.fmt.comptimePrint( \\?[{0s}]($link.ref("{0s}")) - , .{@tagName(oo)}), + , .{@tagName(o)}), }, inline else => |_, t| return comptime std.fmt.comptimePrint( \\[{0s}]($link.ref("{0s}")) @@ -148,7 +166,7 @@ pub const ScriptyParam = union(enum) { pub fn id(p: ScriptyParam) []const u8 { switch (p) { .Opt, .Many => |o| switch (o) { - inline else => |oo| return @tagName(oo), + inline else => return @tagName(o), }, inline else => return @tagName(p), } diff --git a/src/exes/docgen.zig b/src/exes/docgen.zig index bb55290..291e4bf 100644 --- a/src/exes/docgen.zig +++ b/src/exes/docgen.zig @@ -42,7 +42,7 @@ pub fn main() !void { \\--- \\ ); - try w.print("{}", .{ref}); + try w.writeAll(std.fmt.comptimePrint("{}", .{ref})); try buf_writer.flush(); } diff --git a/src/render/html.zig b/src/render/html.zig index 0789080..d2d4fcb 100644 --- a/src/render/html.zig +++ b/src/render/html.zig @@ -17,8 +17,7 @@ pub fn html( path: []const u8, w: anytype, ) !void { - var it = Iter.init(ast.md.root); - it.reset(start, .enter); + var it = Iter.init(start); const full_page = start.n == ast.md.root.n; @@ -162,9 +161,22 @@ pub fn html( .enter => try w.print("
", .{}), .exit => {}, }, + .FOOTNOTE_REFERENCE => switch (ev.dir) { + .enter => { + const literal = node.literal().?; + const def_idx = ast.footnotes.getIndex(literal).?; + const footnote = ast.footnotes.values()[def_idx]; + try w.print("{d}", .{ + footnote.def_id, + footnote.ref_ids[@intCast(node.footnoteRefIx() - 1)], + def_idx + 1, + }); + }, + .exit => {}, + }, .FOOTNOTE_DEFINITION => switch (ev.dir) { - .enter => @panic("TODO: FOOTNOTE_DEFINITION"), - .exit => @panic("TODO: FOOTNOTE_DEFINITION"), + .enter => {}, + .exit => {}, }, .HTML_INLINE => switch (ev.dir) { .enter => try w.print(