From 33bf2e0b1d4eb486396d29c0a3a59d490c2a9703 Mon Sep 17 00:00:00 2001 From: Abe Gong Date: Mon, 29 Jun 2026 09:38:27 -0600 Subject: [PATCH 1/5] Add docs PDF exports --- Makefile | 20 +- docs/content/pdf-downloads.md | 14 ++ docs/hugo.yaml | 10 + docs/layouts/index.print.html | 195 ++++++++++++++++++ .../layouts/partials/docs/print-excluded.html | 17 ++ docs/layouts/partials/docs/print-pages.html | 12 ++ docs/layouts/partials/docs/print-toc.html | 12 ++ scripts/docs-pdf.sh | 148 +++++++++++++ 8 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 docs/content/pdf-downloads.md create mode 100644 docs/layouts/index.print.html create mode 100644 docs/layouts/partials/docs/print-excluded.html create mode 100644 docs/layouts/partials/docs/print-pages.html create mode 100644 docs/layouts/partials/docs/print-toc.html create mode 100755 scripts/docs-pdf.sh diff --git a/Makefile b/Makefile index 6c2118ba..8f34871e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,11 @@ -.PHONY: all build test vet fmt tidy run clean skills skill skills-link docs-deps docs-serve docs-build docs-gen docs-gen-check +.PHONY: all build test vet fmt tidy run clean skills skill skills-link docs-deps docs-serve docs-build docs-pdf docs-gen docs-gen-check BINARY := katalyst DOCS_DIR := docs +DOCS_PDF_EXCLUDE ?= +DOCS_PDF_TONER_FRIENDLY ?= 0 +DOCS_PDF_STANDARD := $(CURDIR)/$(DOCS_DIR)/public/katalyst-docs.pdf +DOCS_PDF_TONER := $(CURDIR)/$(DOCS_DIR)/public/katalyst-docs-toner-friendly.pdf HUGO_BOOK_MODULE := github.com/alex-shpak/hugo-book HUGO_LOCAL := $(shell command -v hugo 2>/dev/null) HUGO_LOCAL_EXTENDED := $(shell hugo version 2>/dev/null | grep -q extended && echo 1 || echo 0) @@ -82,4 +86,16 @@ docs-serve: docs-deps $(HUGO) server -s $(DOCS_DIR) --buildDrafts --disableFastRender docs-build: docs-deps - $(HUGO) -s $(DOCS_DIR) --minify + HUGO_DOCS_PDF_EXCLUDE="" HUGO_DOCS_PDF_TONER_FRIENDLY=0 $(HUGO) -s $(DOCS_DIR) --minify + DOCS_PDF_OUTPUT="$(DOCS_PDF_STANDARD)" ./scripts/docs-pdf.sh + HUGO_DOCS_PDF_EXCLUDE="" HUGO_DOCS_PDF_TONER_FRIENDLY=1 $(HUGO) -s $(DOCS_DIR) --minify --cleanDestinationDir=false + DOCS_PDF_OUTPUT="$(DOCS_PDF_TONER)" ./scripts/docs-pdf.sh + +# Export the whole docs site to PDF. DOCS_PDF_EXCLUDE is a comma-separated list +# of URL prefixes to omit. DOCS_PDF_TONER_FRIENDLY=1 prints code blocks with a +# white background instead of the theme's syntax-highlighted dark background. +# Example: +# make docs-pdf DOCS_PDF_EXCLUDE=/contributing/,/deep-dives/ DOCS_PDF_TONER_FRIENDLY=1 +docs-pdf: docs-deps + HUGO_DOCS_PDF_EXCLUDE="$(DOCS_PDF_EXCLUDE)" HUGO_DOCS_PDF_TONER_FRIENDLY="$(DOCS_PDF_TONER_FRIENDLY)" $(HUGO) -s $(DOCS_DIR) --baseURL / --minify + ./scripts/docs-pdf.sh diff --git a/docs/content/pdf-downloads.md b/docs/content/pdf-downloads.md new file mode 100644 index 00000000..b3a35db4 --- /dev/null +++ b/docs/content/pdf-downloads.md @@ -0,0 +1,14 @@ ++++ +title = "PDF downloads" +weight = 50 ++++ + +# PDF downloads + +Download the full Katalyst documentation as a PDF: + +- [Standard PDF](/katalyst-docs.pdf) +- [Toner-friendly PDF](/katalyst-docs-toner-friendly.pdf) + +The toner-friendly version uses white code block backgrounds for lower-ink +printing. diff --git a/docs/hugo.yaml b/docs/hugo.yaml index 05ff5109..ff865683 100644 --- a/docs/hugo.yaml +++ b/docs/hugo.yaml @@ -4,6 +4,16 @@ title: Katalyst Documentation theme: github.com/alex-shpak/hugo-book cleanDestinationDir: true enableGitInfo: true +outputFormats: + print: + mediaType: text/html + baseName: print/index + isHTML: true + notAlternative: true +outputs: + home: + - html + - print module: imports: - path: github.com/alex-shpak/hugo-book diff --git a/docs/layouts/index.print.html b/docs/layouts/index.print.html new file mode 100644 index 00000000..81b7dd89 --- /dev/null +++ b/docs/layouts/index.print.html @@ -0,0 +1,195 @@ + + + {{- $exclude := slice -}} + {{- with os.Getenv "HUGO_DOCS_PDF_EXCLUDE" -}} + {{- $exclude = split . "," -}} + {{- end -}} + {{- $tonerFriendly := false -}} + {{- with os.Getenv "HUGO_DOCS_PDF_TONER_FRIENDLY" -}} + {{- $value := lower (trim . " \t\r\n") -}} + {{- $tonerFriendly = or (eq $value "1") (eq $value "true") (eq $value "yes") (eq $value "on") -}} + {{- end -}} + + + + {{ .Site.Title }} PDF + + + + + + + +
+ {{ partial "docs/print-pages.html" (dict "Pages" .Site.Home.Pages "Exclude" $exclude) }} +
+ + diff --git a/docs/layouts/partials/docs/print-excluded.html b/docs/layouts/partials/docs/print-excluded.html new file mode 100644 index 00000000..3a5ed47e --- /dev/null +++ b/docs/layouts/partials/docs/print-excluded.html @@ -0,0 +1,17 @@ +{{- $page := .Page -}} +{{- $excluded := false -}} +{{- range .Exclude -}} + {{- $rule := trim . " \t\r\n" -}} + {{- if $rule -}} + {{- if not (hasPrefix $rule "/") -}} + {{- $rule = printf "/%s" $rule -}} + {{- end -}} + {{- if not (hasSuffix $rule "/") -}} + {{- $rule = printf "%s/" $rule -}} + {{- end -}} + {{- if or (eq $page.RelPermalink $rule) (hasPrefix $page.RelPermalink $rule) -}} + {{- $excluded = true -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- return $excluded -}} diff --git a/docs/layouts/partials/docs/print-pages.html b/docs/layouts/partials/docs/print-pages.html new file mode 100644 index 00000000..60526c7b --- /dev/null +++ b/docs/layouts/partials/docs/print-pages.html @@ -0,0 +1,12 @@ +{{- $exclude := .Exclude -}} +{{- range .Pages.ByWeight -}} + {{- if and (not .Draft) (not (partial "docs/print-excluded.html" (dict "Page" . "Exclude" $exclude))) -}} + + {{- with .Pages -}} + {{ partial "docs/print-pages.html" (dict "Pages" . "Exclude" $exclude) }} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/docs/layouts/partials/docs/print-toc.html b/docs/layouts/partials/docs/print-toc.html new file mode 100644 index 00000000..c920aef9 --- /dev/null +++ b/docs/layouts/partials/docs/print-toc.html @@ -0,0 +1,12 @@ +{{- $depth := .Depth -}} +{{- $exclude := .Exclude -}} +{{- range .Pages.ByWeight -}} + {{- if and (not .Draft) (not (partial "docs/print-excluded.html" (dict "Page" . "Exclude" $exclude))) -}} +
  • + {{ .Title }} +
  • + {{- with .Pages -}} + {{ partial "docs/print-toc.html" (dict "Pages" . "Depth" (add $depth 1) "Exclude" $exclude) }} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/scripts/docs-pdf.sh b/scripts/docs-pdf.sh new file mode 100755 index 00000000..4d8ea1a4 --- /dev/null +++ b/scripts/docs-pdf.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +public_dir="${repo_root}/docs/public" +print_page="${public_dir}/print/index.html" +output_pdf="${DOCS_PDF_OUTPUT:-${public_dir}/katalyst-docs.pdf}" + +if [[ ! -f "${print_page}" ]]; then + echo "missing ${print_page}; run make docs-build or make docs-pdf first" >&2 + exit 1 +fi + +find_browser() { + local candidates=( + "${CHROME_BIN:-}" + "google-chrome" + "google-chrome-stable" + "chromium" + "chromium-browser" + "microsoft-edge" + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + "/Applications/Chromium.app/Contents/MacOS/Chromium" + "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" + ) + + local candidate + for candidate in "${candidates[@]}"; do + [[ -n "${candidate}" ]] || continue + if command -v "${candidate}" >/dev/null 2>&1; then + command -v "${candidate}" + return 0 + fi + if [[ -x "${candidate}" ]]; then + printf '%s\n' "${candidate}" + return 0 + fi + done +} + +browser="$(find_browser || true)" +if [[ -z "${browser}" ]]; then + echo "could not find Chrome, Chromium, or Edge; set CHROME_BIN=/path/to/browser" >&2 + exit 1 +fi + +file_size() { + stat -f%z "$1" 2>/dev/null || stat -c%s "$1" +} + +port="$(python3 - <<'PY' +import socket + +with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + print(sock.getsockname()[1]) +PY +)" + +python3 -m http.server "${port}" --bind 127.0.0.1 --directory "${public_dir}" >/tmp/katalyst-docs-pdf-http.log 2>&1 & +server_pid="$!" +profile_dir="$(mktemp -d)" +browser_log="$(mktemp)" +cleanup() { + kill "${server_pid}" >/dev/null 2>&1 || true + wait "${server_pid}" 2>/dev/null || true + rm -rf "${profile_dir}" + rm -f "${browser_log}" +} +trap cleanup EXIT + +for _ in {1..40}; do + if python3 - "$port" <<'PY' >/dev/null 2>&1 +import http.client +import sys + +conn = http.client.HTTPConnection("127.0.0.1", int(sys.argv[1]), timeout=0.2) +conn.request("GET", "/print/") +response = conn.getresponse() +sys.exit(0 if response.status < 500 else 1) +PY + then + break + fi + sleep 0.1 +done + +mkdir -p "$(dirname "${output_pdf}")" +rm -f "${output_pdf}" + +"${browser}" \ + --headless=new \ + --disable-background-networking \ + --disable-component-update \ + --disable-default-apps \ + --disable-gpu \ + --disable-sync \ + --metrics-recording-only \ + --mute-audio \ + --no-sandbox \ + --user-data-dir="${profile_dir}" \ + --print-to-pdf="${output_pdf}" \ + --no-pdf-header-footer \ + --print-to-pdf-no-header \ + "http://127.0.0.1:${port}/print/" \ + >"${browser_log}" 2>&1 & +browser_pid="$!" + +last_size=0 +stable_count=0 +for _ in {1..240}; do + if [[ -f "${output_pdf}" ]]; then + size="$(file_size "${output_pdf}")" + if [[ "${size}" -gt 0 && "${size}" == "${last_size}" ]]; then + stable_count=$((stable_count + 1)) + else + stable_count=0 + last_size="${size}" + fi + + if [[ "${stable_count}" -ge 4 ]]; then + break + fi + fi + + if ! kill -0 "${browser_pid}" >/dev/null 2>&1; then + wait "${browser_pid}" || { + cat "${browser_log}" >&2 + exit 1 + } + break + fi + + sleep 0.25 +done + +if [[ ! -s "${output_pdf}" ]]; then + kill "${browser_pid}" >/dev/null 2>&1 || true + wait "${browser_pid}" 2>/dev/null || true + cat "${browser_log}" >&2 + echo "browser did not write ${output_pdf}" >&2 + exit 1 +fi + +kill "${browser_pid}" >/dev/null 2>&1 || true +wait "${browser_pid}" 2>/dev/null || true + +echo "wrote ${output_pdf}" From 746d3c7712ee084573b7e06b2a523631bda94b2a Mon Sep 17 00:00:00 2001 From: Abe Gong Date: Mon, 29 Jun 2026 09:52:48 -0600 Subject: [PATCH 2/5] Move PDF downloads under reference --- docs/content/{ => reference}/pdf-downloads.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename docs/content/{ => reference}/pdf-downloads.md (95%) diff --git a/docs/content/pdf-downloads.md b/docs/content/reference/pdf-downloads.md similarity index 95% rename from docs/content/pdf-downloads.md rename to docs/content/reference/pdf-downloads.md index b3a35db4..09f85bac 100644 --- a/docs/content/pdf-downloads.md +++ b/docs/content/reference/pdf-downloads.md @@ -1,6 +1,6 @@ +++ title = "PDF downloads" -weight = 50 +weight = 60 +++ # PDF downloads From 6eed117e1ac9f58a83f0a50150250fad54a68809 Mon Sep 17 00:00:00 2001 From: Abe Gong Date: Mon, 29 Jun 2026 09:57:05 -0600 Subject: [PATCH 3/5] Use shared docs build for Netlify previews --- Makefile | 5 +++-- netlify.toml | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 8f34871e..c6b0ab39 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ BINARY := katalyst DOCS_DIR := docs DOCS_PDF_EXCLUDE ?= DOCS_PDF_TONER_FRIENDLY ?= 0 +DOCS_HUGO_FLAGS ?= DOCS_PDF_STANDARD := $(CURDIR)/$(DOCS_DIR)/public/katalyst-docs.pdf DOCS_PDF_TONER := $(CURDIR)/$(DOCS_DIR)/public/katalyst-docs-toner-friendly.pdf HUGO_BOOK_MODULE := github.com/alex-shpak/hugo-book @@ -86,9 +87,9 @@ docs-serve: docs-deps $(HUGO) server -s $(DOCS_DIR) --buildDrafts --disableFastRender docs-build: docs-deps - HUGO_DOCS_PDF_EXCLUDE="" HUGO_DOCS_PDF_TONER_FRIENDLY=0 $(HUGO) -s $(DOCS_DIR) --minify + HUGO_DOCS_PDF_EXCLUDE="" HUGO_DOCS_PDF_TONER_FRIENDLY=0 $(HUGO) -s $(DOCS_DIR) $(DOCS_HUGO_FLAGS) --minify DOCS_PDF_OUTPUT="$(DOCS_PDF_STANDARD)" ./scripts/docs-pdf.sh - HUGO_DOCS_PDF_EXCLUDE="" HUGO_DOCS_PDF_TONER_FRIENDLY=1 $(HUGO) -s $(DOCS_DIR) --minify --cleanDestinationDir=false + HUGO_DOCS_PDF_EXCLUDE="" HUGO_DOCS_PDF_TONER_FRIENDLY=1 $(HUGO) -s $(DOCS_DIR) $(DOCS_HUGO_FLAGS) --minify --cleanDestinationDir=false DOCS_PDF_OUTPUT="$(DOCS_PDF_TONER)" ./scripts/docs-pdf.sh # Export the whole docs site to PDF. DOCS_PDF_EXCLUDE is a comma-separated list diff --git a/netlify.toml b/netlify.toml index 33022fd6..0efd731c 100644 --- a/netlify.toml +++ b/netlify.toml @@ -9,9 +9,9 @@ # with GitHub Pages and can be ignored (or disabled in the Netlify UI). [build] - base = "docs" # Hugo site lives in docs/ (hugo.yaml, content/) - publish = "public" # relative to base -> docs/public - command = "hugo --gc --minify" + base = "." # build from repo root so the shared Makefile runs + publish = "docs/public" + command = "make docs-build" [build.environment] HUGO_VERSION = "0.163.3" # match the version CI builds with @@ -20,7 +20,7 @@ # Set baseURL to the deploy's own URL so links resolve in previews. For Deploy # Previews this is the stable per-PR URL; for branch deploys, the branch URL. [context.deploy-preview] - command = "hugo --gc --minify -b $DEPLOY_PRIME_URL" + command = "DOCS_HUGO_FLAGS=\"-b $DEPLOY_PRIME_URL\" make docs-build" [context.branch-deploy] - command = "hugo --gc --minify -b $DEPLOY_PRIME_URL" + command = "DOCS_HUGO_FLAGS=\"-b $DEPLOY_PRIME_URL\" make docs-build" From 6f0c96c4e299bd6a4f7daf2ff6be88697a36284d Mon Sep 17 00:00:00 2001 From: Abe Gong Date: Mon, 29 Jun 2026 10:01:51 -0600 Subject: [PATCH 4/5] Keep Netlify docs base for PDF builds --- netlify.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/netlify.toml b/netlify.toml index 0efd731c..6a4420e6 100644 --- a/netlify.toml +++ b/netlify.toml @@ -9,9 +9,9 @@ # with GitHub Pages and can be ignored (or disabled in the Netlify UI). [build] - base = "." # build from repo root so the shared Makefile runs - publish = "docs/public" - command = "make docs-build" + base = "docs" # Hugo site lives in docs/ (hugo.yaml, content/) + publish = "public" # relative to base -> docs/public + command = "make -C .. docs-build" [build.environment] HUGO_VERSION = "0.163.3" # match the version CI builds with @@ -20,7 +20,7 @@ # Set baseURL to the deploy's own URL so links resolve in previews. For Deploy # Previews this is the stable per-PR URL; for branch deploys, the branch URL. [context.deploy-preview] - command = "DOCS_HUGO_FLAGS=\"-b $DEPLOY_PRIME_URL\" make docs-build" + command = "DOCS_HUGO_FLAGS=\"-b $DEPLOY_PRIME_URL\" make -C .. docs-build" [context.branch-deploy] - command = "DOCS_HUGO_FLAGS=\"-b $DEPLOY_PRIME_URL\" make docs-build" + command = "DOCS_HUGO_FLAGS=\"-b $DEPLOY_PRIME_URL\" make -C .. docs-build" From 119d441454ed0cc8ee11e6ebc39e05b606da4e73 Mon Sep 17 00:00:00 2001 From: Abe Gong Date: Mon, 29 Jun 2026 10:05:02 -0600 Subject: [PATCH 5/5] Keep docs PDF generation manual --- Makefile | 8 +------- docs/content/reference/pdf-downloads.md | 14 -------------- netlify.toml | 6 +++--- 3 files changed, 4 insertions(+), 24 deletions(-) delete mode 100644 docs/content/reference/pdf-downloads.md diff --git a/Makefile b/Makefile index c6b0ab39..00e575da 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,6 @@ BINARY := katalyst DOCS_DIR := docs DOCS_PDF_EXCLUDE ?= DOCS_PDF_TONER_FRIENDLY ?= 0 -DOCS_HUGO_FLAGS ?= -DOCS_PDF_STANDARD := $(CURDIR)/$(DOCS_DIR)/public/katalyst-docs.pdf -DOCS_PDF_TONER := $(CURDIR)/$(DOCS_DIR)/public/katalyst-docs-toner-friendly.pdf HUGO_BOOK_MODULE := github.com/alex-shpak/hugo-book HUGO_LOCAL := $(shell command -v hugo 2>/dev/null) HUGO_LOCAL_EXTENDED := $(shell hugo version 2>/dev/null | grep -q extended && echo 1 || echo 0) @@ -87,10 +84,7 @@ docs-serve: docs-deps $(HUGO) server -s $(DOCS_DIR) --buildDrafts --disableFastRender docs-build: docs-deps - HUGO_DOCS_PDF_EXCLUDE="" HUGO_DOCS_PDF_TONER_FRIENDLY=0 $(HUGO) -s $(DOCS_DIR) $(DOCS_HUGO_FLAGS) --minify - DOCS_PDF_OUTPUT="$(DOCS_PDF_STANDARD)" ./scripts/docs-pdf.sh - HUGO_DOCS_PDF_EXCLUDE="" HUGO_DOCS_PDF_TONER_FRIENDLY=1 $(HUGO) -s $(DOCS_DIR) $(DOCS_HUGO_FLAGS) --minify --cleanDestinationDir=false - DOCS_PDF_OUTPUT="$(DOCS_PDF_TONER)" ./scripts/docs-pdf.sh + $(HUGO) -s $(DOCS_DIR) --minify # Export the whole docs site to PDF. DOCS_PDF_EXCLUDE is a comma-separated list # of URL prefixes to omit. DOCS_PDF_TONER_FRIENDLY=1 prints code blocks with a diff --git a/docs/content/reference/pdf-downloads.md b/docs/content/reference/pdf-downloads.md deleted file mode 100644 index 09f85bac..00000000 --- a/docs/content/reference/pdf-downloads.md +++ /dev/null @@ -1,14 +0,0 @@ -+++ -title = "PDF downloads" -weight = 60 -+++ - -# PDF downloads - -Download the full Katalyst documentation as a PDF: - -- [Standard PDF](/katalyst-docs.pdf) -- [Toner-friendly PDF](/katalyst-docs-toner-friendly.pdf) - -The toner-friendly version uses white code block backgrounds for lower-ink -printing. diff --git a/netlify.toml b/netlify.toml index 6a4420e6..33022fd6 100644 --- a/netlify.toml +++ b/netlify.toml @@ -11,7 +11,7 @@ [build] base = "docs" # Hugo site lives in docs/ (hugo.yaml, content/) publish = "public" # relative to base -> docs/public - command = "make -C .. docs-build" + command = "hugo --gc --minify" [build.environment] HUGO_VERSION = "0.163.3" # match the version CI builds with @@ -20,7 +20,7 @@ # Set baseURL to the deploy's own URL so links resolve in previews. For Deploy # Previews this is the stable per-PR URL; for branch deploys, the branch URL. [context.deploy-preview] - command = "DOCS_HUGO_FLAGS=\"-b $DEPLOY_PRIME_URL\" make -C .. docs-build" + command = "hugo --gc --minify -b $DEPLOY_PRIME_URL" [context.branch-deploy] - command = "DOCS_HUGO_FLAGS=\"-b $DEPLOY_PRIME_URL\" make -C .. docs-build" + command = "hugo --gc --minify -b $DEPLOY_PRIME_URL"