summaryrefslogtreecommitdiff
path: root/src/lib/template.source.sh
AgeCommit message (Collapse)Author
2026-06-17Add configurable SOURCE_URL footer link; release 0.9.00.9.0Paul Buetow
The footer "Site generated ... with <link>" was hardcoded to the shuriken.sh repository. Make it configurable via the SOURCE_URL config variable and the --source-url CLI flag (defaulting to the shuriken.sh repo, so existing sites are unchanged). The footer derives the displayed text from the URL by stripping its scheme. Plumbed through apply_config_defaults, CLI override targets/spec, --print-config, the verbose effective-config log, and the header template's new render_source_url_html (config_html) render var. Documented in shuriken.default.conf and README; added a generation test asserting a custom SOURCE_URL replaces the default footer link, and updated the print-config and header render-var-subset expectations. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17kn0 compute only required template render vars per templatePaul Buetow
prepare_template_render_vars previously computed and serialized every one of the 30+ render_* fields for each template invocation, even though e.g. the header template references only a handful. It now computes only the subset the target template actually needs. The needed set is driven by the required_templates (5th) field of each TEMPLATE_RENDER_FIELD_SPECS entry, corrected/completed so every spec's required_templates exactly matches that render_var's references in the .tmpl files (config_html and derived kinds previously left it empty). A new template_needed_render_vars_to builds the per-template set; prepare_template_render_vars takes the template name and skips non-needed fields; serialize_template_render_context emits only the computed keys into the BASH_ENV context file. Dependency closure: handlers read only from the input context array or config globals, never from another computed render_var, so the direct per-template set is the full closure (no transitive expansion needed). Side-effects: every handler is a pure value computation; none consume RANDOM/seed; current_date_html only primes the deterministic SHURIKEN_CURRENT_DATE_TEXT cache (idempotent), so subsetting is safe for all fields. render_html_dir_html (required_templates='*') is referenced by no template but kept always-computed as a documented cheap exception. Output is byte-identical: diff -r over a full generated dist/ (3-image, 2-camera fixture, STATS_PAGE=yes, fixed random seed) between the previous bin/shuriken and this build matches exactly across all 63 files (51 HTML album/details/splash/stats/per-camera/filter pages + tarball). Tests: add test_template_render_vars_subset_is_minimal_for_header (header computes only its needed vars, succeeds with unrelated config globals unset) and test_template_render_var_subsetting_matches_templates (spec needed-set equals each .tmpl's render_* references). Existing test_template_required_context_vars_come_from_render_specs is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17bn0 guard preview_num arithmetic against non-numeric inputPaul Buetow
The preview_num next/prev render handlers computed neighbour page numbers with $(( context_value +/- 1 )) but only guarded against an empty value. A non-numeric preview_num context value (e.g. a stray string) slipped past the [ -n ] check and triggered a bash arithmetic syntax error which, under set -e, aborted the whole script. Validate the context value is a non-negative integer ([[ value =~ ^[0-9]+$ ]]) before the arithmetic in both prepare_template_render_var__preview_num_next_html and __preview_num_prev_html. Invalid or missing values now default to an empty render value, matching the existing missing-neighbour behaviour; the valid-numeric path is unchanged. Add test_template_render_var_preview_num_guards_non_numeric in tests/cli.sh, which drives the handlers directly under bash -euo pipefail to prove a bad preview_num no longer crashes and a numeric one still yields the exact +1 / -1 neighbour. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16wn0 batch preview-thumbnail rendering per pagePaul Buetow
Each thumbnail on a preview page (page-N.html) used to be rendered by its own "template preview" call, paying the full source_template_file cost -- an "env -i bash" invocation -- per thumbnail. With MAXPREVIEWS thumbnails per page that was N template renders per page just for the grid. render_full_preview_page now builds the markup for ALL of a page's thumbnails in bash (build_preview_thumbnail / append_preview_thumbnail) and emits the whole grid in ONE render via a new previewpage.tmpl that takes the pre-built HTML through a context_raw "preview_thumbs" field -- the same pattern the stats filter galleries (camera.tmpl) already use. Per-thumbnail markup is byte-identical to the old preview.tmpl output: same <a name=... href=...><img class='thumb <anim>' .../></a> structure, order, HTML escaping and seeded "slow" animation class. Header and footer stay as their own template calls, so a page now costs ~1 previewpage render + header/footer instead of N + chrome. The parallel job-pool integration and failure contract are unchanged: each preview page is still one background render job. Added the render_preview_thumbs_html field spec (hn0 dispatch pattern, context_raw kind), registered previewpage in the validate_template_dir required templates and in the required-context-vars test expectations, and pointed the four generation template-failure tests at previewpage.tmpl (generation no longer renders preview.tmpl). The standalone "template preview" engine unit tests keep exercising preview.tmpl, which still ships. Verified byte-identical output: generated the fixture album (including a spaces/special-char filename) twice with RANDOM_SEED=42 using the parent commit's bin/shuriken vs the new bin; every .html file is identical. just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16hn0 make template render kind dispatch extensible (OCP)Paul Buetow
Replace the hardcoded `case "$kind"` block in prepare_template_render_vars with a name-based registration/dispatch pattern. Each render field kind is now implemented by one prepare_template_render_var__<kind> handler; the core loop resolves the handler by name (prepare_template_render_var__$kind), verifies it exists via `declare -F`, calls it with a uniform signature (out_nameref, context_name, source_name), and reports a config_error for an unknown kind (no matching handler) -- preserving the previous error behavior. Adding a new kind now means defining a new handler function only; the loop never changes. Handlers cover all existing kinds: context_css, context_html, context_raw, current_date_html, config_html (keeps its inner source_name dispatch for HEIGHT/MAXPREVIEWS/TITLE/etc. and the same :- defaults), original_basepath_is_set, preview_num_next_html, preview_num_prev_html, and tarball_include. Escaping and defaults are unchanged, so rendered output is byte-identical (verified by diff -r of a full --generate album, before vs after). Add test_template_render_var_dispatch_is_extensible proving every declared kind resolves to a handler and that a newly defined handler is dispatched without touching the core loop (OCP). just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16un0 build template context file without declare -f or a bash subprocessPaul Buetow
source_template_file previously built the BASH_ENV context file by piping "declare -p ...; declare -f; serialize_template_render_context ...;" into a fresh "bash -euo pipefail" subprocess for every rendered page. That dumped all ~5000 lines of shuriken functions and spawned a subprocess per page just to run the serializer - a large per-page cost for albums with hundreds-to-thousands of pages. Now serialize_template_render_context runs in the current shell with stdout redirected straight into the context tempfile, followed by an appended "unset BASH_ENV". The serializer returns its own non-zero status explicitly so the failure is detected via an "if" status-test (which returns normally through the RETURN trap and cleans up the partial context file), robust even when source_template_file runs inside a status-tested "if template ..." call chain where bash would otherwise suppress an inner errexit abort. The trap-based cleanup (RETURN plus INT/TERM/HUP re-raising to $BASHPID) is preserved unchanged. Two serializer test mocks that relied on the removed "| bash" errexit now return non-zero explicitly. Rendered HTML output is unchanged; just test, just shellcheck, just check-generated and git diff --check all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Per-camera mini-albums: full-width grid + camera-scoped view navigationPaul Buetow
Two fixes to the per-camera pages: - The camera thumbnail grid is now full-width and reflows with the window like the main album. Previously it sat inside the 900px-wide stats column, so wide thumbnails only fit ~2 per row. camera.tmpl now puts the grid in a full-width sibling block (centered by the body, like the album's direct thumbnail children); the heading/back-link stay in the narrow stats column. - Each camera is now a self-contained mini album. Thumbnails link to per-camera view pages (camera-<slug>--<index>.html) whose prev/next cycle only through that camera's photos, instead of linking into the shared album view pages (where next/prev walked the whole album and left the camera). The view pages reuse the album's look (blurred background, EXIF tooltip, animation) and offer Gallery / Details (to the album details page) / Direct-link nav. The "--" index separator can never collide with a gallery name since slugs never contain "--". Adds cameraview.tmpl + its render field, _stats_render_camera_{gallery,views} and the cameraview body builders. Tests updated for the new thumbnail links and extended to assert the per-camera view pages and their camera-scoped navigation; required-context-vars test now also covers the camera and cameraview templates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15Add EXIF mouse-over tooltip to the normal image viewPaul Buetow
The details view already showed a title="" hover summary (camera, aperture, ISO, shutter, date) on its image; the normal image view did not. Wire the same tooltip into view.tmpl: - render_view_page now computes the EXIF tooltip via photo_exif_tooltip_text (reusing the shared identify cache, so no extra ImageMagick work) and passes it as the exif_tooltip context var. - exif_tooltip is now a required render field for the view template too. - view.tmpl adds the title="" attribute to its <img> when the summary is non-empty, mirroring details.tmpl (no title when there is no EXIF). - Tests: extend the EXIF render test to assert the view image carries the same tooltip, and update the required-context-vars expectation for view. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14rm0 wire stats feature into generate with STATS_PAGE togglePaul Buetow
Final integration of the stats-site feature: - Add STATS_PAGE config (default yes) mirroring SPLASH_PAGE through every layer: shuriken.default.conf, apply_config_defaults, CLI_OPTION_SPEC (--stats/--no-stats), the CLI_CONFIG_OVERRIDE_TARGETS allowlist (without which the flag was silently ignored), usage text, yes/no validation, print_config, the action config list + effective-setting log, the generation metadata, and the dry-run plan. - generate() now calls a gated generate_stats_pages helper after the album pages (and before archiving, so the stats/camera pages are tarballed): collect_photo_exif_stats + render_stats_page . . + render_camera_pages . . - Emit "stats_page" in shuriken.json next to "splash_page". - Gate the header Stats nav link behind STATS_PAGE via a new render_stats_page_html config_html field, so it is hidden (no 404) when stats are disabled. - Document STATS_PAGE and --stats/--no-stats in the README. - Tests: full --generate produces stats.html + camera-<slug>.html + the nav link and stats_page=true; --no-stats suppresses all three and sets stats_page=false; print-config and metadata expectations updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14um0 add per-camera filter pagesPaul Buetow
Implement render_camera_pages <html_dir> <backhref> in stats.source.sh: one camera-<slug>.html per camera in STATS_CAMERA_SLUGS, reusing the aggregation's collision-resolved slugs and per-camera photo lists so filenames match pm0's leaderboard links. Each page reuses the shared header/footer chrome plus a new camera.tmpl that renders an HTML-escaped camera heading, a pre-built thumbnail grid, and a back-to-stats link. Thumbnails reuse preview.tmpl's markup style (thumb image from thumbs/) but link to the full-size image under photos/ rather than a per-photo view page, since the view-page name is not derivable outside the album pagination loop. The photos and thumbs dir names are fixed to match generate()'s render_album_pages call. Cameras are walked in LC_ALL=C label order for reproducible output, and cameras with no recorded photos are skipped. Adds the camera_name/camera_thumbs render field specs and a tests/cli.sh case covering per-camera files, slug collisions, HTML escaping, thumbnail link targets, and determinism. This provides the function + template + tests only; wiring into generate() and the STATS_PAGE toggle remain for rm0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14pm0 add stats page template and render functionPaul Buetow
Render the aggregated STATS_* counters from the om0 module into a static stats.html. render_stats_page builds the variable-length body (camera leaderboard, temporal/exposure/dimension histograms, format and enum breakdowns) as HTML and hands it to the new stats.tmpl through a raw context field, wrapping it with the shared header/footer the way view/details pages do. Bars are pure CSS so the page stays JavaScript-free. - new template share/templates/default/stats.tmpl (page chrome + body) - render_stats_page <html_dir> <backhref> [page_name] plus small section builders in src/lib/stats.source.sh; EXIF-derived labels are HTML-escaped - new TEMPLATE_RENDER_FIELD_SPECS field render_stats_body_html (context_raw) and 'stats' added to render_backhref_html's required list - camera leaderboard entries link to camera-<slug>.html (um0 owns those) - Stats nav link wired into the shared header bar - tests cover leaderboard links, counts/percentages, a histogram section, &/< escaping, and omission of empty categories Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-146m0 re-raise interrupt to $BASHPID, not $$, in render subshellPaul Buetow
The signal handler in source_template_file re-raised the caught signal with `kill -s $sig $$`. Because the function commonly runs inside backgrounded render subshells (queue_album_view_render_job), $$ expands to the main shuriken PID, not the subshell's own. On interrupt the handler therefore signalled the main shell -- which had already cleared its own staging traps -- killing it mid-cleanup (leaking the staging dir) instead of terminating just the render subshell. Re-raise to $BASHPID, the current (sub)shell's real PID, which equals $$ in the foreground case so it is correct everywhere. Extend test_template_interrupt_removes_context_file to run the render in a backgrounded subshell and assert the parent shell survives the re-raise; verified it fails against a $$-based build and passes with $BASHPID. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-146m0 clean up BASH_ENV context tempfile via traps on interruptPaul Buetow
source_template_file created its context tempfile with mktemp but only ran rm -f on the success and explicit-failure paths. When a render was interrupted by a signal (terminate_active_generation SIGTERMs the backgrounded render subtree on Ctrl-C/abort) the tempfile leaked. Register cleanup in exactly one place, inline in source_template_file's body: - A RETURN trap covers normal and error returns and clears all of these traps (including itself) so it cannot linger and fire on an enclosing function's return against the out-of-scope context_file local (set -u). The trap must be set in the function body, not a helper: without functrace a RETURN trap is not function-scoped and would fire when a helper returns, deleting the file before the render runs. - INT/TERM/HUP handlers remove the file, clear the traps and re-raise so the process still exits with the signal's default disposition. PIPE is not trapped (the internal context-build pipeline emits legitimate SIGPIPE); SIGKILL is untrappable, leaving only the OS-reaped KILL-escalation case. The redundant rm -f calls are removed. Adds test_template_interrupt_removes_context_file, which runs an in-flight render and SIGTERMs it, asserting the context file is gone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13jm0: allow minimal refresh configsPaul Buetow
2026-06-13jm0: centralize runtime config defaultsPaul Buetow
2026-06-12Harden status-tested validation paths for bm0Paul Buetow
2026-06-12Fix remaining errexit masking for bm0Paul Buetow
2026-06-12Capture action and template setup failures for bm0Paul Buetow
2026-06-12Clean template context files on failure for bm0Paul Buetow
2026-06-12Guard template render setup failures for bm0Paul Buetow
2026-06-12Unify validator fail-fast contract for bm0Paul Buetow
2026-06-12Fix next template required vars for gm0Paul Buetow
2026-06-12Co-locate template required context vars for gm0Paul Buetow
2026-06-11fm0 deduplicate escape wrappersPaul Buetow
2026-06-11Refactor template render fields for task 7m0Paul Buetow
2026-06-11Rename photoalbum to shurikenPaul Buetow
2026-06-11Add EXIF tooltip to details viewPaul Buetow
2026-06-11Split photoalbum source into lib modulesPaul Buetow