summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-03-27 14:27:19 -0300
committerGabriel Schneider <[email protected]>2026-03-27 15:16:44 -0300
commiteeb2d880626c72b1f94e6d84838d8a0342bb99c7 (patch)
tree510a7030fcbf29f49c6b313fe9602648de928e4f
parent5d935263f828f8be1037484f22f9181aa9f734c5 (diff)
download0x4200.cafe-eeb2d880626c72b1f94e6d84838d8a0342bb99c7.tar.gz
0x4200.cafe-eeb2d880626c72b1f94e6d84838d8a0342bb99c7.zip
Added jj metadata to page build, orchestrated with nushell and mise
-rw-r--r--.gitignore3
-rw-r--r--build/jj-log.nu34
-rw-r--r--build/jj-log.tmpl1
-rw-r--r--build/jj-log.typ158
-rw-r--r--mise.toml15
-rw-r--r--trees/index.typst4
-rw-r--r--trees/jj-log.typst105
7 files changed, 320 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 77f1133..4ae4847 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
/.cache
.DS_Store
+trees/_data/
+build/*.json
+build/*.pdf
diff --git a/build/jj-log.nu b/build/jj-log.nu
new file mode 100644
index 0000000..7d2aa92
--- /dev/null
+++ b/build/jj-log.nu
@@ -0,0 +1,34 @@
+#!/usr/bin/env nu
+
+# Generate JSON from jj log and compile a Typst document
+# Usage: nu build/jj-log.nu [--revset '::@'] [--output build/jj-log.pdf]
+
+def main [
+ --revset (-r): string = "::@" # jj revset expression
+ --output (-o): string = "build/jj-log.pdf" # output PDF path
+ --json-only (-j) # only generate JSON, skip typst compile
+] {
+ let build_dir = ($env.FILE_PWD? | default ("build" | path expand))
+ let tmpl = (open ([$build_dir "jj-log.tmpl"] | path join))
+ let json_path = ($output | path parse | update extension "json" | path join)
+
+ let trees_json = "trees/_data/jj-log.json"
+ mkdir trees/_data
+
+ print $"generating log for revset: ($revset)"
+ let log_output = (jj log -r $revset --no-graph -T $tmpl)
+ $log_output | save -f $json_path
+ $log_output | save -f $trees_json
+
+ let n = (open --raw $json_path | lines | where $it != "" | length)
+ print $"wrote ($n) entries to ($json_path), ($trees_json)"
+
+ if not $json_only {
+ let typ_path = ([$build_dir "jj-log.typ"] | path join)
+ let json_abs = ($json_path | path expand)
+ let out_abs = ($output | path expand)
+ print $"compiling ($output)..."
+ typst compile $typ_path --root / --input $"data=($json_abs)" $out_abs
+ print $"done → ($output)"
+ }
+}
diff --git a/build/jj-log.tmpl b/build/jj-log.tmpl
new file mode 100644
index 0000000..2119648
--- /dev/null
+++ b/build/jj-log.tmpl
@@ -0,0 +1 @@
+"{\"commit\": " ++ json(self) ++ ", \"bookmarks\": " ++ json(local_bookmarks) ++ ", \"empty\": " ++ if(empty, "true", "false") ++ ", \"conflict\": " ++ if(conflict, "true", "false") ++ ", \"root\": " ++ if(root, "true", "false") ++ ", \"short_change\": \"" ++ change_id.shortest() ++ "\", \"short_commit\": \"" ++ commit_id.shortest() ++ "\"}\n"
diff --git a/build/jj-log.typ b/build/jj-log.typ
new file mode 100644
index 0000000..a1b36a9
--- /dev/null
+++ b/build/jj-log.typ
@@ -0,0 +1,158 @@
+// jj-log.typ — Typst document that renders jj log JSON output
+//
+// Usage:
+// jj log -r '::@' --no-graph \
+// -T '{"commit": ' ++ json(self) ++ ', "bookmarks": ' ++ json(local_bookmarks) ++ ', "empty": ' ++ if(empty, "true", "false") ++ ', "conflict": ' ++ if(conflict, "true", "false") ++ ', "root": ' ++ if(root, "true", "false") ++ '}\n' \
+// > log.json
+// typst compile jj-log.typ --input data=log.json
+
+#let data-path = sys.inputs.at("data", default: "log.json")
+#let raw-text = read(data-path)
+#let lines = raw-text.split("\n").filter(l => l.trim() != "")
+#let commits = lines.map(l => json(bytes(l)))
+
+// Filter out root commit
+#let commits = commits.filter(c => not c.root)
+
+// --- Helpers ---
+
+#let mono(body) = text(font: "DejaVu Sans Mono", size: 0.85em, body)
+#let short(hash) = hash.slice(0, 8)
+#let tag-pill(name, color: rgb("#4a6fa5")) = {
+ box(
+ fill: color.lighten(75%),
+ stroke: 0.5pt + color,
+ radius: 3pt,
+ inset: (x: 4pt, y: 2pt),
+ text(size: 0.8em, font: "DejaVu Sans Mono", fill: color, weight: "bold", name),
+ )
+}
+
+#let format-date(ts) = {
+ // timestamp is ISO 8601, e.g. "2026-03-27T14:22:35-03:00"
+ // extract date + time portion
+ let date-part = ts.slice(0, 10)
+ let time-part = ts.slice(11, 16)
+ date-part + " " + time-part
+}
+
+// Collect all bookmarks across commits
+#let all-bookmarks = commits.map(c => {
+ c.bookmarks.map(b => (name: b.name, commit: short(c.commit.commit_id), change: short(c.commit.change_id), desc: c.commit.description.trim()))
+}).flatten()
+
+// --- Page setup ---
+
+#set page(paper: "a4", margin: 2cm)
+#set text(font: "DejaVu Sans", size: 10pt)
+#set par(leading: 0.6em)
+
+// --- Title ---
+
+#align(center)[
+ #text(size: 18pt, weight: "bold")[jj ancestor log]
+ #v(0.3em)
+ #text(size: 9pt, fill: gray)[
+ #commits.len() commits #h(1em)
+ #all-bookmarks.len() bookmarks #h(1em)
+ newest → oldest
+ ]
+]
+
+#v(1em)
+
+// --- Bookmarks index ---
+
+#if all-bookmarks.len() > 0 [
+ == Bookmarks
+
+ #for bm in all-bookmarks [
+ - #tag-pill(bm.name) #h(0.5em) at #mono(bm.commit) — #emph(bm.desc)
+ ]
+
+ #v(1em)
+]
+
+// --- Commit log table ---
+
+== Commit History
+
+#table(
+ columns: (auto, auto, 1fr, auto),
+ align: (center, center, left, right),
+ stroke: 0.4pt + luma(200),
+ inset: 6pt,
+
+ // Header
+ table.header(
+ text(weight: "bold")[Change],
+ text(weight: "bold")[Commit],
+ text(weight: "bold")[Description],
+ text(weight: "bold")[Date],
+ ),
+
+ // Rows — newest to oldest (jj log default order with ::@)
+ ..commits.map(c => {
+ let bookmarks = c.bookmarks.map(b => tag-pill(b.name))
+ let is-empty = c.empty
+ let has-conflict = c.conflict
+ let desc = c.commit.description.trim()
+ if desc == "" { desc = "(no description)" }
+
+ let badges = {
+ if is-empty { tag-pill("empty", color: rgb("#999")) }
+ if has-conflict { tag-pill("conflict", color: rgb("#c44")) }
+ }
+
+ (
+ mono(short(c.commit.change_id)),
+ mono(short(c.commit.commit_id)),
+ [
+ #desc
+ #if bookmarks.len() > 0 { h(0.5em); bookmarks.join(h(0.3em)) }
+ #badges
+ ],
+ text(size: 0.85em, fill: luma(100), format-date(c.commit.author.timestamp)),
+ )
+ }).flatten(),
+)
+
+// --- Detailed list ---
+
+#v(1.5em)
+
+== Commit Details
+
+#for (i, c) in commits.enumerate() [
+ #let desc = c.commit.description.trim()
+ #if desc == "" { desc = "(no description)" }
+ #let bookmarks = c.bookmarks.map(b => tag-pill(b.name))
+
+ #block(
+ width: 100%,
+ inset: 8pt,
+ radius: 4pt,
+ stroke: 0.3pt + luma(200),
+ fill: if calc.even(i) { luma(252) } else { white },
+ )[
+ #grid(
+ columns: (auto, 1fr),
+ column-gutter: 1em,
+ row-gutter: 0.4em,
+ text(fill: luma(120), size: 0.85em)[change:], mono(short(c.commit.change_id)),
+ text(fill: luma(120), size: 0.85em)[commit:], mono(short(c.commit.commit_id)),
+ text(fill: luma(120), size: 0.85em)[author:], [#c.commit.author.name #text(fill: luma(120))[\<#c.commit.author.email\>]],
+ text(fill: luma(120), size: 0.85em)[date:], format-date(c.commit.author.timestamp),
+ ..if bookmarks.len() > 0 {(
+ text(fill: luma(120), size: 0.85em)[bookmarks:],
+ bookmarks.join(h(0.3em)),
+ )},
+ )
+ #v(0.3em)
+ #text(weight: "bold", size: 1.05em, desc)
+ #if c.empty [ #text(fill: luma(150), size: 0.85em)[\[empty\]] ]
+ #if c.conflict [ #text(fill: rgb("#c44"), size: 0.85em)[\[conflict\]] ]
+ ]
+
+ #v(0.5em)
+]
diff --git a/mise.toml b/mise.toml
new file mode 100644
index 0000000..9e84d31
--- /dev/null
+++ b/mise.toml
@@ -0,0 +1,15 @@
+[tasks."jj-log"]
+description = "Generate jj ancestor log as PDF"
+run = "nu build/jj-log.nu"
+dir = "{{cwd}}"
+
+[tasks."jj-log:json"]
+description = "Generate jj log JSON only"
+run = "nu build/jj-log.nu --json-only"
+dir = "{{cwd}}"
+
+[tasks.serve]
+description = "Generate jj log data then serve the kodama site"
+depends = ["jj-log:json"]
+run = "kodama serve"
+dir = "{{cwd}}"
diff --git a/trees/index.typst b/trees/index.typst
index 48570bd..ba426eb 100644
--- a/trees/index.typst
+++ b/trees/index.typst
@@ -30,3 +30,7 @@ My hobbies include, in no particular order:
== Blog posts
- #local("googlectf-2025")
+
+== Meta
+
+- #local("jj-log")
diff --git a/trees/jj-log.typst b/trees/jj-log.typst
new file mode 100644
index 0000000..814cedb
--- /dev/null
+++ b/trees/jj-log.typst
@@ -0,0 +1,105 @@
+#import "_lib/kodama.typ": *
+#show: kodama
+
+#let raw-text = read("_data/jj-log.json")
+#let lines = raw-text.split("\n").filter(l => l.trim() != "")
+#let commits = lines.map(l => json(bytes(l)))
+#let commits = commits.filter(c => not c.root)
+
+// Title is the shortest unique change id of the working copy (first commit)
+#let wc-short = commits.at(0).short_change
+#metadata(("title": wc-short))
+
+// --- Helpers ---
+
+#let body = raw(body)
+#let tag-pill(name, color: rgb("#4a6fa5")) = {
+ box(
+ fill: color.lighten(75%),
+ stroke: 0.5pt + color,
+ radius: 3pt,
+ inset: (x: 4pt, y: 2pt),
+ text(size: 0.8em, weight: "bold", raw(name)),
+ )
+}
+
+#let format-date(ts) = {
+ let date-part = ts.slice(0, 10)
+ let time-part = ts.slice(11, 16)
+ date-part + " " + time-part
+}
+
+#let all-bookmarks = (
+ commits
+ .map(c => {
+ c.bookmarks.map(b => (
+ name: b.name,
+ change: c.short_change,
+ commit: c.short_commit,
+ desc: c.commit.description.trim(),
+ ))
+ })
+ .flatten()
+)
+
+#text(size: 0.9em, fill: luma(120))[
+ #commits.len() commits #h(0.5em) | #h(0.5em)
+ #all-bookmarks.len() bookmarks #h(0.5em) | #h(0.5em)
+ newest #sym.arrow oldest
+]
+
+== Bookmarks
+
+#if all-bookmarks.len() > 0 [
+ #for bm in all-bookmarks [
+ - #tag-pill(bm.name) #h(0.3em) at #bm.change — #emph(bm.desc)
+ ]
+] else [
+ _No bookmarks in ancestor chain._
+]
+
+== Commit History
+
+#table(
+ columns: (auto, auto, 1fr, auto),
+ align: (center, center, left, right),
+ stroke: 0.4pt + luma(200),
+ inset: 6pt,
+
+ table.header(
+ text(weight: "bold")[Change],
+ text(weight: "bold")[Commit],
+ text(weight: "bold")[Description],
+ text(weight: "bold")[Date],
+ ),
+
+ ..commits
+ .map(c => {
+ let bookmarks = c.bookmarks.map(b => tag-pill(b.name))
+ let desc = c.commit.description.trim()
+ if desc == "" { desc = "(no description)" }
+
+ let badges = {
+ if c.empty { tag-pill("empty", color: rgb("#999")) }
+ if c.conflict { tag-pill("conflict", color: rgb("#c44")) }
+ }
+
+ (
+ c.short_change,
+ c.short_commit,
+ [
+ #desc
+ #if bookmarks.len() > 0 {
+ h(0.3em)
+ bookmarks.join(h(0.2em))
+ }
+ #badges
+ ],
+ text(size: 0.85em, fill: luma(100), format-date(
+ c.commit.author.timestamp,
+ )),
+ )
+ })
+ .flatten(),
+)
+