From 0be4403e510a5e9ba37df8ffe0d4fe23cbc25fff Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Fri, 25 Oct 2024 18:14:18 +0200 Subject: [PATCH] implement linkRef and complete git info The code contributed to implement $build.git() was missing integration with the caching system, which was added in this commit. --- build.zig.zon | 4 +- build/Git.zig | 162 ++++++++++++++++++++++++++++++++++ build/content.zig | 25 ++++++ src/context/Build.zig | 30 +++++-- src/context/Git.zig | 109 ----------------------- src/context/Page.zig | 50 ++++++++++- src/exes/layout.zig | 29 +++++- src/exes/layout/DepWriter.zig | 17 ---- src/exes/layout/cache.zig | 2 +- src/root.zig | 16 ++++ 10 files changed, 304 insertions(+), 140 deletions(-) create mode 100644 build/Git.zig delete mode 100644 src/exes/layout/DepWriter.zig diff --git a/build.zig.zon b/build.zig.zon index 1be94f8..b0ac740 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,8 +3,8 @@ .version = "0.0.0", .dependencies = .{ .supermd = .{ - .url = "git+https://github.com/kristoff-it/supermd#e82051668fa0fdc4da065fcb245e98712b4454dc", - .hash = "1220c00c9be44387976c3da3fe991ae21692407f6c85c6b6903e239fe14654bcf0f4", + .url = "git+https://github.com/kristoff-it/supermd#b6fc13bd9de0fa076abafeb1cba46da3be85b0eb", + .hash = "122053d5f01101b05bbeb13b600f53cf7bbc70c9173af674f1986580301aacbf99d2", }, .scripty = .{ .url = "git+https://github.com/kristoff-it/scripty#df8c11380f9e9bec34809f2242fb116d27cf39d6", diff --git a/build/Git.zig b/build/Git.zig new file mode 100644 index 0000000..d82c13c --- /dev/null +++ b/build/Git.zig @@ -0,0 +1,162 @@ +const Git = @This(); + +const std = @import("std"); +const builtin = @import("builtin"); +const ziggy = @import("ziggy"); +const zeit = @import("zeit"); +const Allocator = std.mem.Allocator; + +_in_repo: bool = false, + +commit_hash: []const u8 = undefined, +commit_date: struct { + unix: i64 = undefined, + + const Self = @This(); + pub const ziggy_options = struct { + pub fn stringify( + value: Self, + opts: ziggy.serializer.StringifyOptions, + indent_level: usize, + depth: usize, + writer: anytype, + ) !void { + _ = opts; + _ = indent_level; + _ = depth; + + const date = zeit.instant(.{ + .source = .{ .unix_timestamp = value.unix }, + }) catch unreachable; + + try writer.print("@date(\"", .{}); + date.time().gofmt(writer, "2006-01-02T15:04:05") catch unreachable; + try writer.print("\")", .{}); + } + }; +} = undefined, +commit_message: []const u8 = undefined, +author_name: []const u8 = undefined, +author_email: []const u8 = undefined, + +_tag: ?[]const u8 = null, +_branch: ?[]const u8 = null, + +pub const gitCommitHashLen = 40; + +pub fn init(gpa: Allocator, path: []const u8) !Git { + var git = Git{}; + + var p = path; + const git_dir = while (true) { + var dir = try std.fs.openDirAbsolute(p, .{}); + defer dir.close(); + break dir.openDir(".git", .{}) catch |err| switch (err) { + error.FileNotFound => { + p = std.fs.path.dirname(p) orelse return git; + continue; + }, + else => return err, + }; + }; + + git._in_repo = true; + + const head = readHead(gpa, git_dir) catch return Git{}; + switch (head) { + .commit_hash => |hash| git.commit_hash = hash, + .branch => |branch| { + git._branch = branch; + git.commit_hash = readCommitOfBranch(gpa, git_dir, branch) catch return Git{}; + }, + } + + git._tag = getTagForCommitHash(gpa, git_dir, git.commit_hash) catch return Git{}; + + git.setAdditionalMetadata(gpa, git_dir) catch return Git{}; + return git; +} + +fn readHead(arena: Allocator, git_dir: std.fs.Dir) !union(enum) { commit_hash: []const u8, branch: []const u8 } { + var head_file = try git_dir.openFile("HEAD", .{}); + defer head_file.close(); + const buf = try head_file.readToEndAlloc(arena, 4096); + + if (std.mem.startsWith(u8, buf, "ref:")) { + return .{ .branch = buf[16 .. buf.len - 1] }; + } else { + return .{ .commit_hash = buf[0 .. buf.len - 1] }; + } +} + +fn readCommitOfBranch(arena: Allocator, git_dir: std.fs.Dir, branch: []const u8) ![]const u8 { + const rel_path = switch (builtin.os.tag) { + .windows => win: { + const duped_branch = try arena.dupe(u8, branch); + defer arena.free(duped_branch); + std.mem.replaceScalar(u8, duped_branch, '/', '\\'); + break :win try std.fs.path.join(arena, &.{ "refs", "heads", duped_branch }); + }, + else => try std.fs.path.join(arena, &.{ "refs", "heads", branch }), + }; + defer arena.free(rel_path); + + const content = try git_dir.readFileAlloc(arena, rel_path, gitCommitHashLen + 1); + return content[0..gitCommitHashLen]; +} + +fn getTagForCommitHash(arena: Allocator, git_dir: std.fs.Dir, commit_hash: []const u8) !?[]const u8 { + const rel_path = try std.fs.path.join(arena, &.{ "refs", "tags" }); + defer arena.free(rel_path); + + var tags = try git_dir.openDir(rel_path, .{ .iterate = true }); + defer tags.close(); + + var iter = tags.iterate(); + while (try iter.next()) |tag| { + const content = try tags.readFileAlloc(arena, tag.name, gitCommitHashLen + 1); + const tag_hash = content[0..gitCommitHashLen]; + + if (std.mem.eql(u8, tag_hash, commit_hash)) { + return try arena.dupe(u8, tag.name); + } + } + return null; +} + +// NOTE: Does not support packed objects +fn setAdditionalMetadata(git: *Git, arena: Allocator, git_dir: std.fs.Dir) !void { + const commit_path = try std.fs.path.join(arena, &.{ "objects", git.commit_hash[0..2], git.commit_hash[2..] }); + defer arena.free(commit_path); + + const content = try git_dir.openFile(commit_path, .{}); + var decompressed = std.compress.zlib.decompressor(content.reader()); + const reader = decompressed.reader(); + const data = try reader.readAllAlloc(arena, 100000); + + var attributes = std.mem.splitScalar(u8, data, '\n'); + + _ = attributes.next(); // tree hash + _ = attributes.next(); // parent commit hash + _ = attributes.next(); // author + + if (attributes.next()) |committer| { + const @"<_index" = std.mem.indexOfScalar(u8, committer, '<').?; + const @">_index" = std.mem.indexOfScalar(u8, committer, '>').?; + + git.author_name = committer[10 .. @"<_index" - 1]; + git.author_email = committer[@"<_index" + 1 .. @">_index"]; + + const unix_time = try std.fmt.parseInt(i64, committer[@">_index" + 2 .. committer.len - 6], 10); + const offset_hour = try std.fmt.parseInt(i64, committer[committer.len - 4 .. committer.len - 2], 10); + const offset = switch (committer[committer.len - 5]) { + '-' => -offset_hour, + '+' => offset_hour, + else => unreachable, + }; + git.commit_date = .{ .unix = unix_time + offset * 3600 }; + } + + _ = attributes.next(); // empty line + git.commit_message = attributes.rest(); +} diff --git a/build/content.zig b/build/content.zig index 843f252..f7761e0 100644 --- a/build/content.zig +++ b/build/content.zig @@ -68,6 +68,8 @@ fn scan( // parent section index const ps_index_dir = index_dir.makeOpenPath("ps", .{}) catch unreachable; + collectGitInfo(project, index_dir, project.build_root.path.?); + const assets_updater = zine_dep.artifact("update-assets"); const update_assets = project.addRunArtifact(assets_updater); update_assets.addArg(project.install_path); @@ -1105,3 +1107,26 @@ pub fn ensureDir(project: *std.Build, path: []const u8) void { std.process.exit(1); }; } + +const Git = @import("Git.zig"); +fn collectGitInfo(project: *std.Build, dir: std.fs.Dir, path: []const u8) void { + const g = Git.init(project.allocator, path) catch |err| { + std.debug.print("error while collecting git info: {s}", .{ + @errorName(err), + }); + std.process.exit(1); + }; + + const f = dir.createFile("git.ziggy", .{}) catch |err| { + std.debug.print("error while creating .zig-cache/zine/git.ziggy: {s}", .{ + @errorName(err), + }); + std.process.exit(1); + }; + defer f.close(); + + var buf = std.ArrayList(u8).init(project.allocator); + ziggy.stringify(g, .{}, buf.writer()) catch unreachable; + + f.writeAll(buf.items) catch unreachable; +} diff --git a/src/context/Build.zig b/src/context/Build.zig index 3a0e0ac..c89beaf 100644 --- a/src/context/Build.zig +++ b/src/context/Build.zig @@ -1,25 +1,34 @@ const Build = @This(); const std = @import("std"); -const Allocator = std.mem.Allocator; const scripty = @import("scripty"); const utils = @import("utils.zig"); const context = @import("../context.zig"); +const DepWriter = @import("../root.zig").DepWriter; +const Signature = @import("doctypes.zig").Signature; +const Allocator = std.mem.Allocator; const Value = context.Value; const Optional = context.Optional; -const Signature = @import("doctypes.zig").Signature; const uninitialized = utils.uninitialized; pub const dot = scripty.defaultDot(Build, Value, false); pub const PassByRef = true; generated: context.DateTime, +_dep_writer: DepWriter, +_git_data_path: []const u8, _git: context.Git, -pub fn init(arena: Allocator) Build { +pub fn init( + dep_writer: DepWriter, + git_data_path: []const u8, + git: context.Git, +) Build { return .{ .generated = context.DateTime.initNow(), - ._git = context.Git.init(arena), + ._dep_writer = dep_writer, + ._git_data_path = git_data_path, + ._git = git, }; } @@ -92,7 +101,13 @@ pub const Builtins = struct { }; if (args.len != 0) return bad_arg; - return if (build._git._in_repo) .{ .git = build._git } else .{ .err = "Not in a git repository" }; + build._dep_writer.writePrereq(build._git_data_path) catch @panic("unexpected error while addind a dependency"); + + return if (build._git._in_repo) .{ + .git = build._git, + } else .{ + .err = "Not in a git repository", + }; } }; @@ -116,7 +131,10 @@ pub const Builtins = struct { }; if (args.len != 0) return bad_arg; - return if (build._git._in_repo) Optional.init(gpa, build._git) else Optional.Null; + return if (build._git._in_repo) + Optional.init(gpa, build._git) + else + Optional.Null; } }; }; diff --git a/src/context/Git.zig b/src/context/Git.zig index 0fe2a1a..ce07031 100644 --- a/src/context/Git.zig +++ b/src/context/Git.zig @@ -14,8 +14,6 @@ const Value = context.Value; pub const dot = scripty.defaultDot(Git, Value, false); -pub const gitCommitHashLen = 40; - _in_repo: bool = false, commit_hash: []const u8 = undefined, @@ -27,113 +25,6 @@ author_email: []const u8 = undefined, _tag: ?[]const u8 = null, _branch: ?[]const u8 = null, -pub fn init(arena: Allocator) Git { - var git = Git{}; - - const git_dir = std.fs.cwd().openDir(".git", .{}) catch { - return git; - }; - git._in_repo = true; - - const head = readHead(arena, git_dir) catch return Git{}; - switch (head) { - .commit_hash => |hash| git.commit_hash = hash, - .branch => |branch| { - git._branch = branch; - git.commit_hash = readCommitOfBranch(arena, git_dir, branch) catch return Git{}; - }, - } - - git._tag = getTagForCommitHash(arena, git_dir, git.commit_hash) catch return Git{}; - - git.setAdditionalMetadata(arena, git_dir) catch return Git{}; - return git; -} - -fn readHead(arena: Allocator, git_dir: std.fs.Dir) !union(enum) { commit_hash: []const u8, branch: []const u8 } { - var head_file = try git_dir.openFile("HEAD", .{}); - defer head_file.close(); - const buf = try head_file.readToEndAlloc(arena, 4096); - - if (std.mem.startsWith(u8, buf, "ref:")) { - return .{ .branch = buf[16 .. buf.len - 1] }; - } else { - return .{ .commit_hash = buf[0 .. buf.len - 1] }; - } -} - -fn readCommitOfBranch(arena: Allocator, git_dir: std.fs.Dir, branch: []const u8) ![]const u8 { - const rel_path = switch (builtin.os.tag) { - .windows => win: { - const duped_branch = try arena.dupe(u8, branch); - defer arena.free(duped_branch); - std.mem.replaceScalar(u8, duped_branch, '/', '\\'); - break :win try std.fs.path.join(arena, &.{ "refs", "heads", duped_branch }); - }, - else => try std.fs.path.join(arena, &.{ "refs", "heads", branch }), - }; - defer arena.free(rel_path); - - const content = try git_dir.readFileAlloc(arena, rel_path, gitCommitHashLen + 1); - return content[0..gitCommitHashLen]; -} - -fn getTagForCommitHash(arena: Allocator, git_dir: std.fs.Dir, commit_hash: []const u8) !?[]const u8 { - const rel_path = try std.fs.path.join(arena, &.{ "refs", "tags" }); - defer arena.free(rel_path); - - var tags = try git_dir.openDir(rel_path, .{ .iterate = true }); - defer tags.close(); - - var iter = tags.iterate(); - while (try iter.next()) |tag| { - const content = try tags.readFileAlloc(arena, tag.name, gitCommitHashLen + 1); - const tag_hash = content[0..gitCommitHashLen]; - - if (std.mem.eql(u8, tag_hash, commit_hash)) { - return try arena.dupe(u8, tag.name); - } - } - return null; -} - -// NOTE: Does not support packed objects -fn setAdditionalMetadata(git: *Git, arena: Allocator, git_dir: std.fs.Dir) !void { - const commit_path = try std.fs.path.join(arena, &.{ "objects", git.commit_hash[0..2], git.commit_hash[2..] }); - defer arena.free(commit_path); - - const content = try git_dir.openFile(commit_path, .{}); - var decompressed = std.compress.zlib.decompressor(content.reader()); - const reader = decompressed.reader(); - const data = try reader.readAllAlloc(arena, 100000); - - var attributes = std.mem.splitScalar(u8, data, '\n'); - - _ = attributes.next(); // tree hash - _ = attributes.next(); // parent commit hash - _ = attributes.next(); // author - - if (attributes.next()) |committer| { - const @"<_index" = std.mem.indexOfScalar(u8, committer, '<').?; - const @">_index" = std.mem.indexOfScalar(u8, committer, '>').?; - - git.author_name = committer[10 .. @"<_index" - 1]; - git.author_email = committer[@"<_index" + 1 .. @">_index"]; - - const unix_time = try std.fmt.parseInt(i64, committer[@">_index" + 2 .. committer.len - 6], 10); - const offset_hour = try std.fmt.parseInt(i64, committer[committer.len - 4 .. committer.len - 2], 10); - const offset = switch (committer[committer.len - 5]) { - '-' => -offset_hour, - '+' => offset_hour, - else => unreachable, - }; - git.commit_date = try DateTime.initUnix(unix_time + offset * 3600); - } - - _ = attributes.next(); // empty line - git.commit_message = attributes.rest(); -} - pub const description = \\Information about the current git repository. ; diff --git a/src/context/Page.zig b/src/context/Page.zig index 208c004..819e2c6 100644 --- a/src/context/Page.zig +++ b/src/context/Page.zig @@ -710,6 +710,54 @@ pub const Builtins = struct { } }; + pub const linkRef = struct { + pub const signature: Signature = .{ .ret = .String }; + pub const description = + \\Returns the URL of the target page. + ; + pub const examples = + \\$page.link() + ; + pub fn call( + p: *const Page, + gpa: Allocator, + args: []const Value, + ) !Value { + const bad_arg = .{ + .err = "expected 1 string argument", + }; + if (args.len != 1) return bad_arg; + + const elem_id = switch (args[0]) { + .string => |s| s.value, + else => return bad_arg, + }; + + const ast = p._meta.ast.?; + if (!ast.ids.contains(elem_id)) return Value.errFmt( + gpa, + "cannot find id ='{s}' in the content page", + .{elem_id}, + ); + + const relp = p._meta.md_rel_path; + const path = switch (p._meta.is_section) { + true => relp[0 .. relp.len - "index.smd".len], + false => relp[0 .. relp.len - ".smd".len], + }; + + const fragment = try std.fmt.allocPrint(gpa, "#{s}", .{elem_id}); + const result = try join(gpa, &.{ + "/", + p._meta.site._meta.url_path_prefix, + path, + fragment, + }); + + return String.init(result); + } + }; + // TODO: delete this pub const permalink = struct { pub const signature: Signature = .{ .ret = .String }; @@ -768,7 +816,7 @@ pub const Builtins = struct { args: []const Value, ) !Value { const bad_arg = .{ - .err = "expected 1 string argument argument", + .err = "expected 1 string argument", }; if (args.len != 1) return bad_arg; diff --git a/src/exes/layout.zig b/src/exes/layout.zig index 9a18649..9fe766a 100644 --- a/src/exes/layout.zig +++ b/src/exes/layout.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const options = @import("options"); const ziggy = @import("ziggy"); @@ -8,7 +7,7 @@ const cache = @import("layout/cache.zig"); const join = @import("../root.zig").join; const zine = @import("zine"); const context = zine.context; -const DepWriter = @import("layout/DepWriter.zig"); +const Allocator = std.mem.Allocator; const log = std.log.scoped(.layout); pub const std_options: std.Options = .{ @@ -105,7 +104,7 @@ pub fn main() !void { }; var dep_buf_writer = std.io.bufferedWriter(dep_file.writer()); - const dep_writer = DepWriter.init(dep_buf_writer.writer().any()); + const dep_writer = zine.DepWriter.init(dep_buf_writer.writer().any()); dep_writer.writeTarget("target") catch |err| { fatal("error writing to the dep file: {s}", .{@errorName(err)}); }; @@ -186,11 +185,33 @@ pub fn main() !void { true, ); + var diag: ziggy.Diagnostic = .{ + .path = locales_path, + }; + + const index_dir = std.fs.cwd().openDir(index_dir_path, .{}) catch |err| { + fatal("error while opening the index dir:\n{s}\n{s}\n", .{ + index_dir_path, + @errorName(err), + }); + }; + + const git_data_path = try join(arena, &.{ + index_dir_path, "git.ziggy", + }); + const git_data = try readFile(index_dir, "git.ziggy", arena); + + const git = ziggy.parseLeaky(context.Git, arena, git_data, .{ + .diagnostic = &diag, + }) catch { + std.debug.panic("unable to load git info:\n{s}\n\n", .{diag}); + }; + var ctx: context.Template = .{ .site = site, .page = page, .i18n = i18n, - .build = context.Build.init(arena), + .build = context.Build.init(dep_writer, git_data_path, git), }; const SuperVM = superhtml.VM( diff --git a/src/exes/layout/DepWriter.zig b/src/exes/layout/DepWriter.zig deleted file mode 100644 index 690e08e..0000000 --- a/src/exes/layout/DepWriter.zig +++ /dev/null @@ -1,17 +0,0 @@ -const std = @import("std"); - -const DepWriter = @This(); - -w: std.io.AnyWriter, - -pub fn init(writer: std.io.AnyWriter) DepWriter { - return .{.w=writer}; -} - -pub fn writeTarget(dw: DepWriter, target: []const u8) !void { - try dw.w.print("\n{s}:", .{target}); -} - -pub fn writePrereq(dw: DepWriter, prereq: []const u8) !void { - try dw.w.print(" \"{s}\"", .{prereq}); -} diff --git a/src/exes/layout/cache.zig b/src/exes/layout/cache.zig index 2a5fdf1..0c0b8e6 100644 --- a/src/exes/layout/cache.zig +++ b/src/exes/layout/cache.zig @@ -5,7 +5,7 @@ const zine = @import("zine"); const join = zine.join; const context = zine.context; const Allocator = std.mem.Allocator; -const DepWriter = @import("DepWriter.zig"); +const DepWriter = zine.DepWriter; const log = std.log.scoped(.layout_cache); diff --git a/src/root.zig b/src/root.zig index 612dfdf..7b85546 100644 --- a/src/root.zig +++ b/src/root.zig @@ -61,3 +61,19 @@ pub fn join(allocator: std.mem.Allocator, paths: []const []const u8) ![]u8 { // No need for shrink since buf is exactly the correct size. return buf; } + +pub const DepWriter = struct { + w: std.io.AnyWriter, + + pub fn init(writer: std.io.AnyWriter) DepWriter { + return .{ .w = writer }; + } + + pub fn writeTarget(dw: DepWriter, target: []const u8) !void { + try dw.w.print("\n{s}:", .{target}); + } + + pub fn writePrereq(dw: DepWriter, prereq: []const u8) !void { + try dw.w.print(" \"{s}\"", .{prereq}); + } +};