Source: ~/.unfirehose/unfirehose.db (project_id=81, 4 sessions covering
2026-03-29 through 2026-04-05). Reconstructed via chronological replay
of Write/Edit tool_input on file_paths under /home/fox/zebra-report/.
stats:
files reconstructed: 20
writes baselined: all (zero missing)
edits applied: 68
edits unapplied: 8 (1 SKIP pre-baseline, 6 FAIL old_string drift, 1 AMBIGUOUS)
unapplied edits represent small drift in 6 files; baseline content for
each is intact. quality verification deferred to phase 2.
recovered tree:
CLAUDE.md, Makefile
src/{tx,rx,pulse,carrier,chat,bt}.c
include/{modem,zebra}.h
test/{functional,integration,unit}.c, test/test.h
web/{index,kernel}.html, web/blog/style.css
blog/build.py, blog/posts/{001-volume-modem,002-sse-chatroom}.md
report: /tmp/zebra_recover_report.txt
script: /tmp/zebra_recover.py
185 lines
5.8 KiB
Python
185 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
blog/build.py — static blog generator for zebra-report
|
|
markdown + frontmatter → html, no dependencies beyond stdlib
|
|
"""
|
|
|
|
import re, html
|
|
from pathlib import Path
|
|
|
|
POSTS_DIR = Path("blog/posts")
|
|
OUT_DIR = Path("web/blog")
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# minimal markdown → html #
|
|
# ------------------------------------------------------------------ #
|
|
|
|
def md_to_html(text):
|
|
lines = text.split("\n")
|
|
out = []
|
|
in_code = False
|
|
in_list = False
|
|
buf = []
|
|
|
|
def flush_para():
|
|
if buf:
|
|
content = inline(" ".join(buf).strip())
|
|
if content:
|
|
out.append(f"<p>{content}</p>")
|
|
buf.clear()
|
|
|
|
def flush_list():
|
|
nonlocal in_list
|
|
if in_list:
|
|
out.append("</ul>")
|
|
in_list = False
|
|
|
|
def inline(s):
|
|
s = html.escape(s, quote=False)
|
|
s = re.sub(r"`(.+?)`", r"<code>\1</code>", s)
|
|
s = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", s)
|
|
s = re.sub(r"\*(.+?)\*", r"<em>\1</em>", s)
|
|
s = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'<a href="\2">\1</a>', s)
|
|
return s
|
|
|
|
for line in lines:
|
|
# fenced code blocks
|
|
if line.startswith("```"):
|
|
if in_code:
|
|
out.append("</code></pre>")
|
|
in_code = False
|
|
else:
|
|
flush_para(); flush_list()
|
|
lang = line[3:].strip()
|
|
out.append(f'<pre><code class="language-{lang}">' if lang else "<pre><code>")
|
|
in_code = True
|
|
continue
|
|
if in_code:
|
|
out.append(html.escape(line))
|
|
continue
|
|
|
|
# headings
|
|
m = re.match(r"^(#{1,3})\s+(.*)", line)
|
|
if m:
|
|
flush_para(); flush_list()
|
|
n = len(m.group(1))
|
|
out.append(f"<h{n}>{inline(m.group(2))}</h{n}>")
|
|
continue
|
|
|
|
# unordered list
|
|
m = re.match(r"^[-*]\s+(.*)", line)
|
|
if m:
|
|
flush_para()
|
|
if not in_list:
|
|
out.append("<ul>")
|
|
in_list = True
|
|
out.append(f"<li>{inline(m.group(1))}</li>")
|
|
continue
|
|
|
|
# blank line
|
|
if not line.strip():
|
|
flush_para(); flush_list()
|
|
continue
|
|
|
|
buf.append(line)
|
|
|
|
flush_para(); flush_list()
|
|
return "\n".join(out)
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# frontmatter parser #
|
|
# ------------------------------------------------------------------ #
|
|
|
|
def parse_post(path):
|
|
text = path.read_text()
|
|
meta = {}
|
|
body = text
|
|
if text.startswith("---"):
|
|
parts = text.split("---", 2)
|
|
if len(parts) >= 3:
|
|
for line in parts[1].strip().splitlines():
|
|
if ":" in line:
|
|
k, v = line.split(":", 1)
|
|
meta[k.strip().lower()] = v.strip()
|
|
body = parts[2]
|
|
meta["content"] = md_to_html(body.strip())
|
|
meta.setdefault("slug", path.stem)
|
|
meta.setdefault("date", "")
|
|
meta.setdefault("title", path.stem)
|
|
meta.setdefault("summary", "")
|
|
return meta
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# templates #
|
|
# ------------------------------------------------------------------ #
|
|
|
|
CSS_LINK = '<link rel="stylesheet" href="/blog/style.css">'
|
|
|
|
BASE = """\
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>{title} — zebra report</title>
|
|
{css}
|
|
</head>
|
|
<body>
|
|
<header class="site-header">
|
|
<a class="site-name" href="/blog/">zebra report</a>
|
|
<span class="tagline">make crypto scary again</span>
|
|
</header>
|
|
<main>{body}</main>
|
|
</body>
|
|
</html>"""
|
|
|
|
def render_post(meta, newer=None, older=None):
|
|
nav = []
|
|
if older:
|
|
nav.append(f'<a href="/blog/{older["slug"]}/">← {older["title"]}</a>')
|
|
if newer:
|
|
nav.append(f'<a href="/blog/{newer["slug"]}/">{newer["title"]} →</a>')
|
|
nav_html = f'<nav class="post-nav">{" ".join(nav)}</nav>' if nav else ""
|
|
|
|
body = f"""<article>
|
|
<h1>{html.escape(meta["title"])}</h1>
|
|
<p class="meta">{html.escape(meta["date"])} — <a href="/blog/">all posts</a></p>
|
|
<div class="content">{meta["content"]}</div>
|
|
{nav_html}
|
|
</article>"""
|
|
return BASE.format(title=html.escape(meta["title"]), css=CSS_LINK, body=body)
|
|
|
|
def render_index(posts):
|
|
items = ""
|
|
for p in posts:
|
|
items += f"""<li>
|
|
<span class="post-date">{html.escape(p["date"])}</span>
|
|
<a class="post-title" href="/blog/{p["slug"]}/">{html.escape(p["title"])}</a>
|
|
<p class="post-summary">{html.escape(p["summary"])}</p>
|
|
</li>\n"""
|
|
body = f'<h1>posts</h1>\n<ul class="post-list">\n{items}</ul>'
|
|
return BASE.format(title="zebra report", css=CSS_LINK, body=body)
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# build #
|
|
# ------------------------------------------------------------------ #
|
|
|
|
def build():
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
posts = [parse_post(p) for p in sorted(POSTS_DIR.glob("*.md"))]
|
|
posts.sort(key=lambda p: p["date"], reverse=True)
|
|
|
|
for i, post in enumerate(posts):
|
|
newer = posts[i - 1] if i > 0 else None
|
|
older = posts[i + 1] if i + 1 < len(posts) else None
|
|
d = OUT_DIR / post["slug"]
|
|
d.mkdir(exist_ok=True)
|
|
(d / "index.html").write_text(render_post(post, newer=newer, older=older))
|
|
print(f" {post['slug']}/")
|
|
|
|
(OUT_DIR / "index.html").write_text(render_index(posts))
|
|
print(" index.html")
|
|
print("done.")
|
|
|
|
if __name__ == "__main__":
|
|
build()
|