The blog is a static site generated with Pandoc,
styled with the LaTeX CSS stylesheet (latex.now.sh). The
generation process converts Markdown files into standalone HTML pages
and deploys them to two targets simultaneously.
docs/*.md (source content)
templates/*.md.tmpl (Jinja2 templates)
assets/ (shared HTML/JS fragments)
├── license.html
├── topbar.html (dark mode toggle button)
└── dark.js (dark mode logic)
│
▼
bin/generate.sh (orchestrator)
bin/template.py (Jinja2 renderer)
│
▼
staging/ (intermediate working dir)
│
▼
┌─────┴──────┐
html/ ~/Documents/GitHub/ecallen.github.io/
(local) (GitHub Pages deploy)
bin/generate.sh Works (Current Version)The script is a zsh script (the docs describe an older bash version). Here is the step-by-step flow:
Clean output directories - Removes all
.html files from both deploy targets (html/
and the GitHub Pages repo) and clears staging/.
Stage source content - Copies all
.md files from docs/ into
staging/. Renames notes.md to
heap.md in the process.
Loop over each deploy target - The script iterates over two deploy directories and for each one:
index.md.tmpl template into
staging/index.md.bin/template.py to render the Jinja2
{{ hostname }} variable in the index template (set to
ecallen.github.io or ecallen.com depending on
the target)..md file in
staging/, converting each to a standalone HTML file in the
deploy directory.Pandoc options used:
--standalone - Full HTML document with head/body.--template=./default.html5 - Custom Pandoc HTML5
template (from the pandoc-templates repo).--css "https://latex.now.sh/style.css" - LaTeX-style
CSS.--metadata title="ecallen" - Page title.--include-after-body="assets/license.html" - License
footer.--include-before-body="assets/topbar.html" - Dark mode
toggle.--include-after-body="assets/dark.js" - Dark mode
JavaScript.--variable=lastUpdated /
--variable=creationDate - File timestamps via
stat.bin/template.pyA small Jinja2 renderer. Takes a template file and
key=value pairs as CLI arguments, renders the template, and
prints the result to stdout. Used to inject the correct hostname into
index.md.tmpl for each deploy target. Requires the
jinja2 package (managed via uv).
| Directory | Hostname | Purpose |
|---|---|---|
html/ |
ecallen.com |
Local preview / custom domain |
~/Documents/GitHub/ecallen.github.io |
ecallen.github.io |
GitHub Pages |
The deploy_info associative array maps each directory to
its hostname so the index template gets the correct links.
The docs/generate-site.md describes an older
version of the pipeline. Key differences:
| Aspect | Documented (old) | Current (bin/generate.sh) |
|---|---|---|
| Shell | bash | zsh |
| Notes handling | csplit to split notes.md by headers into
sections/ |
Copies notes.md as heap.md into
staging/ (no splitting) |
| Index links | Auto-generated from section HTML via htmlq |
Managed manually in index.md.tmpl |
| Template engine | None (manual cp index.md.tmpl) |
Jinja2 via bin/template.py |
| Deploy targets | Single (html/) |
Two (html/ + GitHub Pages repo) |
| Dark mode | Not present | topbar.html + dark.js injected |
| Local preview | Flask server | Not included in script |
stat variable uses ${fn}
instead of ${f} - Lines 39-40 reference
${fn} which is never assigned in the current script. The
loop variable is $f. This means lastUpdated
and creationDate are likely empty or pulling from a stale
value.
stat flags are macOS-specific - The
script uses stat -f %m and stat -f %c which
are BSD/macOS stat flags. The documented version used
stat -c (GNU/Linux). The current version is correctly
targeting macOS but would break on Linux.
drafts/.docs/.templates/index.md.tmpl if needed.bin/generate.sh to build the site.bin/deploy.sh to push to GitHub Pages.