summaryrefslogtreecommitdiff
path: root/src/lib/template.source.sh
AgeCommit message (Collapse)Author
2026-06-27rr0: data-driven config_html via indirect expansion of source_namePaul Buetow
Replace the hardcoded per-name case in prepare_template_render_var__config_html (HEIGHT/MAXPREVIEWS/ ORIGINAL_BASEPATH/SOURCE_URL/STATS_PAGE/THUMBHEIGHT/TITLE) with a single indirect read of the spec's source_name: context_value=${!source_name-}. The handler is now fully data-driven and never needs editing when a new config render-var is added (Open/Closed), matching the module's spec-driven dispatch. source_name is already passed to every handler by prepare_template_render_vars, so no dispatch contract changed. Output is byte-identical for all reachable states: apply_config_defaults always runs before render (HEIGHT/ORIGINAL_BASEPATH/SOURCE_URL/STATS_PAGE/ TITLE always set, so the old defensive :- fallbacks like STATS_PAGE :-no were unreachable), and the unset-safe '-' default reproduces the empty result for refresh-only MAXPREVIEWS/THUMBHEIGHT. Dropping the literal ${TITLE:-} re-surfaced a previously-suppressed SC2153 on $TITLE in generation-metadata.source.sh (lowercase 'title' locals in the stats modules); add a targeted shellcheck disable there. Regenerated bin/shuriken. just test/shellcheck/check-generated all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27Fix src/shuriken.sh lib source list missing 5 modulesPaul Buetow
Running `bash src/shuriken.sh --generate ...` directly from a source checkout printed "command not found" for camera_label_from_make_model, photo_exif_values_to and cached_photo_identify_output for every photo, silently emptying EXIF tooltips/details and, with STATS_PAGE=yes, omitting the whole stats/ tree. The hand-maintained source list inside the SHURIKEN_LIB_SOURCES_BEGIN/END marker block had drifted from the authoritative Justfile LIB_SOURCES, missing metadata-label, metadata-cache, stats-aggregate, stats-render and stats-filter-album. `just build` replaces the marker block with LIB_SOURCES when generating bin/shuriken, so the installed binary and the bin-based test suite never noticed; only direct src execution was affected. - Add the 5 missing `source` lines to the marker block in the same order as Justfile LIB_SOURCES, so the two lists now match exactly. - Add tests/cli.sh case test_lib_sources_match_justfile_lib_sources that extracts the marker-block module names and asserts they equal the Justfile LIB_SOURCES (same set and order) to prevent future drift. - shellcheck --check-sourced now follows the 5 newly-sourced libs; suppress the cross-module nameref false positives (SC2178/SC2128/ SC2154) with explained directives and genuinely fix SC2004 (counts_ref[$key] -> [key]) and quote the TITLE default (${TITLE:-}). bin/shuriken changes only by these propagated lib edits; the marker-block source list it generates is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25Flush preview-grid rows: fixed breakpoint columns + multiple-of-12 pagesPaul Buetow
The overview grid used auto-fill columns (an unpredictable count at view time) while tiles per page were fixed at generation, so the last row was ragged -- an empty, cut-off bottom-right corner, made worse by a 2x2 feature tile. CSS (header.tmpl): replace auto-fill with a FIXED column count per width breakpoint -- 2 (phone) / 3 / 4 / 6 -- all divisors of 12. THUMBHEIGHT no longer drives the grid (it only sizes the thumbnail files), so its obsolete render-var is dropped (template.source.sh). Generator (album-tile-layout, album-thumbnail-html): append_preview_grid now decides a page's tiles, then snaps the grid-cell total onto a multiple of 12 before emitting, via two photo-preserving levers -- _grid_split_subdivides_to_add (round up: split subdivided tiles into singles; preferred, abundant) and _grid_merge_singles_to_remove (round down: merge adjacent singles). Because 2/3/4/6 all divide 12, a multiple-of-12 page tiles into a COMPLETE rectangle at every breakpoint: a flush last row at any window width, with no image distortion (object-fit: cover). Per-photo preview numbers and all navigation redirects are unchanged. Tiny pages (a short final page or small stats mini-album) are left as-is. Decrements use assignment, not bare "(( --k ))": under set -euo pipefail an arithmetic command evaluating to 0 returns status 1 and would abort generate. Helper namerefs are uniquely named and arrays are forwarded by name to avoid bash circular-nameref errors. Tests: add test_album_grid_cells_align_to_multiple_of_12 (both levers); update the two tests that pinned the old auto-fill CSS / render-var list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24Unify escape/date helper API; fix current_date_text cachingPaul Buetow
Make the template.source.sh escape/date helper family follow one consistent shape: each escaper now has a nameref <name>_to form (hot path, writes a named variable) plus a thin printf wrapper <name> that delegates to it. The leading "_" now exclusively marks private helpers; the public escape API (called from sibling modules) is unprefixed. - current_date_text now delegates to current_date_text_to so both forms share the SHURIKEN_CURRENT_DATE_TEXT cache; the printf form no longer silently re-execs `date` on every direct call. Output unchanged. - Drop the misleading "_" prefix on the public escape API and update all callers: _html_escape->html_escape, _css_string_escape-> css_string_escape, _json_string->json_string, _json_bool->json_bool, _json_string_escape->json_string_escape. - Add the missing JSON nameref forms: json_string_escape_to, json_string_to, json_bool_to (printf wrappers delegate to them). - Add tests: JSON printf-vs-nameref parity and a current_date_text caching + nameref-parity check. No escaping/encoding or date output changes -- API-shape/perf only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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