| Age | Commit message (Collapse) | Author |
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|