diff options
| author | Paul Buetow <paul@buetow.org> | 2026-07-18 12:19:40 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-07-18 12:19:40 +0300 |
| commit | 3c35381b71f992eb7a86552697ea647bb76b68b1 (patch) | |
| tree | 56e478547e0cb47ac7403304c997846fb3fea8a4 /bin | |
| parent | a7588bf270ef2c2f6dd2893d0129e526cbf8d89c (diff) | |
Add DETAILS_PAGE setting to make per-photo details pages optional
Album owners can now set DETAILS_PAGE=no (or pass --no-details) to skip
generating each photo's *-details.html EXIF summary page and its "Details"
navigation redirects, without touching the normal thumbnail overview,
per-photo view pages, EXIF tooltips, or STATS_PAGE, which all stay
independently controlled. Every "Details" link (on view pages and stats
filter mini-album view pages) and every "-details" redirect stub is gated
on the setting so no generated page ever links to a file that was not
rendered. Wired the new field through CONFIG_SPECS (registry-driven
defaults/validation/print-config/CLI override), the --details/--no-details
CLI flags, usage() help, the verbose effective-config log, shuriken.json
generation metadata, and the dry-run plan. DETAILS_PAGE=yes (the default)
keeps prior output byte-for-byte identical.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'bin')
| -rwxr-xr-x | bin/shuriken | 203 |
1 files changed, 153 insertions, 50 deletions
diff --git a/bin/shuriken b/bin/shuriken index 4e23419..a475a3a 100755 --- a/bin/shuriken +++ b/bin/shuriken @@ -55,6 +55,8 @@ declare -Ar CLI_OPTION_SPEC=( [--no-shuffle]='kind=flag value=no config=SHUFFLE' [--splash]='kind=flag value=yes config=SPLASH_PAGE' [--no-splash]='kind=flag value=no config=SPLASH_PAGE' + [--details]='kind=flag value=yes config=DETAILS_PAGE' + [--no-details]='kind=flag value=no config=DETAILS_PAGE' [--stats]='kind=flag value=yes config=STATS_PAGE' [--no-stats]='kind=flag value=no config=STATS_PAGE' [--tarball]='kind=flag value=yes config=TARBALL_INCLUDE' @@ -212,6 +214,8 @@ usage() { --random-seed VALUE --splash --no-splash + --details + --no-details --stats --no-stats --shuffle @@ -834,6 +838,7 @@ declare -ra TEMPLATE_RENDER_FIELD_SPECS=( 'render_cameraview_body_html|context_raw|cameraview_body|cameraview_body|cameraview' 'render_camera_thumbs_html|context_raw|camera_thumbs|camera_thumbs|camera' 'render_current_date_text|current_date_html|||header' + 'render_details_page_html|config_html|DETAILS_PAGE||view' 'render_enter_page_html|context_html|enter_page|enter_page|splash' 'render_exif_details_html|context_raw|exif_details|exif_details|details' 'render_exif_tooltip_html|context_html|exif_tooltip|exif_tooltip|details view' @@ -2523,6 +2528,7 @@ _collect_generation_metadata() { _GENERATION_METADATA["settings_random_seed"]="$RANDOM_SEED" _GENERATION_METADATA["settings_shuffle"]="$SHUFFLE" _GENERATION_METADATA["settings_splash_page"]="$SPLASH_PAGE" + _GENERATION_METADATA["settings_details_page"]="$DETAILS_PAGE" _GENERATION_METADATA["settings_stats_page"]="$STATS_PAGE" _GENERATION_METADATA["settings_original_basepath"]="$ORIGINAL_BASEPATH" } @@ -2594,6 +2600,8 @@ _generation_metadata_json_settings() { "$(json_bool "${_GENERATION_METADATA["settings_shuffle"]}")" printf ' "splash_page": %s,\n' \ "$(json_bool "${_GENERATION_METADATA["settings_splash_page"]}")" + printf ' "details_page": %s,\n' \ + "$(json_bool "${_GENERATION_METADATA["settings_details_page"]}")" printf ' "stats_page": %s,\n' \ "$(json_bool "${_GENERATION_METADATA["settings_stats_page"]}")" printf ' "original_basepath": %s\n' \ @@ -2660,9 +2668,14 @@ collect_dry_run_page_plan() { # files are touched here, so dry-run stays side-effect free. page_count=$(album_page_count_for_image_count "$image_count") redirect_count=$(album_redirect_count_for_page_count "$page_count") - plan_ref["details_count"]="$image_count" plan_ref["page_count"]="$page_count" plan_ref["redirect_count"]="$redirect_count" + # One details page per photo, but only when DETAILS_PAGE=yes -- mirrors + # render_photo_view_and_details skipping render_details_page entirely, + # so the plan cannot drift from what --generate actually writes. + if [ "$DETAILS_PAGE" = yes ]; then + plan_ref["details_count"]="$image_count" + fi fi } @@ -2707,6 +2720,7 @@ collect_dry_run_plan() { plan_ref["random_seed"]="$RANDOM_SEED" plan_ref["shuffle"]="$SHUFFLE" plan_ref["splash_page"]="$SPLASH_PAGE" + plan_ref["details_page"]="$DETAILS_PAGE" plan_ref["stats_page"]="$STATS_PAGE" plan_ref["image_count"]="$image_count" plan_ref["tarball_include"]="$TARBALL_INCLUDE" @@ -2741,14 +2755,17 @@ _print_dry_run_settings() { printf 'Random seed: %s\n' "${plan_ref["random_seed"]}" printf 'Shuffle: %s\n' "${plan_ref["shuffle"]}" printf 'Splash page: %s\n' "${plan_ref["splash_page"]}" + printf 'Details page: %s\n' "${plan_ref["details_page"]}" printf 'Stats page: %s\n' "${plan_ref["stats_page"]}" printf 'Image count: %s\n' "${plan_ref["image_count"]}" printf 'Tarball setting: %s\n' "${plan_ref["tarball_include"]}" printf 'Tarball name plan: %s\n' "${plan_ref["tarball_name_plan"]}" } -# Print the planned directories and generated-files listing (index/favicon/json, -# image dirs, page/view/details/redirect counts, optional stats + tarball lines). +# Print the planned directories, plus the generated-files lines that are always +# present regardless of any page toggle (index/favicon/json/image dirs/page/view +# counts). Split from the optional-lines half below (_print_dry_run_optional_files) +# so each stays around 30 lines, matching the generation-metadata JSON split. _print_dry_run_files() { local -r plan_name="$1"; shift # shellcheck disable=SC2178 @@ -2780,8 +2797,21 @@ _print_dry_run_files() { "${plan_ref["dist_dir"]}" "${plan_ref["page_count"]}" printf ' %s/[page]-[image].html (%s view pages)\n' \ "${plan_ref["dist_dir"]}" "${plan_ref["image_count"]}" - printf ' %s/[page]-[image]-details.html (%s details pages)\n' \ - "${plan_ref["dist_dir"]}" "${plan_ref["details_count"]}" +} + +# Print the generated-files lines gated behind a page toggle (details, the +# navigation redirect count that itself depends on DETAILS_PAGE, stats, and the +# tarball). Split out of _print_dry_run_files (see its comment) purely to keep +# both halves short; output is unchanged from the previous single function. +_print_dry_run_optional_files() { + local -r plan_name="$1"; shift + # shellcheck disable=SC2178 + local -n plan_ref="$plan_name" + + if [ "${plan_ref["details_page"]}" = yes ]; then + printf ' %s/[page]-[image]-details.html (%s details pages)\n' \ + "${plan_ref["dist_dir"]}" "${plan_ref["details_count"]}" + fi printf ' %s/[redirect].html (%s navigation redirects)\n' \ "${plan_ref["dist_dir"]}" "${plan_ref["redirect_count"]}" if [ "${plan_ref["stats_page"]}" = yes ]; then @@ -2805,6 +2835,7 @@ print_dry_run_plan() { _print_dry_run_settings "$plan_name" _print_dry_run_files "$plan_name" + _print_dry_run_optional_files "$plan_name" } # Inlined from src/lib/album-tile-layout.source.sh @@ -3712,6 +3743,10 @@ render_view_page() { tarball_name "$tarball_name" } +# Render one photo's *-details.html page. Only called by +# render_photo_view_and_details when DETAILS_PAGE=yes; callers must not invoke +# this directly for a photo when details are disabled, or a dangling file would +# be produced with no page linking to it. render_details_page() { local -r html_dir="$1"; shift local -r photos_dir="$1"; shift @@ -3774,15 +3809,22 @@ render_photo_view_and_details() { "$page_num" \ "$preview_num" \ "$photo" - render_details_page \ - "$html_dir" \ - "$photos_dir" \ - "$blurs_dir" \ - "$backhref" \ - "$tarball_name" \ - "$page_num" \ - "$preview_num" \ - "$photo" + + # DETAILS_PAGE=no skips the *-details.html file entirely (view.tmpl's Details + # link is likewise suppressed via render_details_page_html, so nothing links + # to it). This is the only place a photo's details page would be rendered, so + # skipping the call here is sufficient to omit the file for every photo. + if [ "$DETAILS_PAGE" = yes ]; then + render_details_page \ + "$html_dir" \ + "$photos_dir" \ + "$blurs_dir" \ + "$backhref" \ + "$tarball_name" \ + "$page_num" \ + "$preview_num" \ + "$photo" + fi } record_rendered_view_page() { @@ -3799,32 +3841,78 @@ record_rendered_view_page() { last_views_ref["$page"]="$preview" } -# Navigation-redirect count single source of truth (task nr0). Every view page -# gets ALBUM_REDIRECTS_PER_PAGE wrap-around redirect files: the prev stub -# (N-0.html) and its details twin, plus the next stub (N-(last+1).html) and its -# details twin -- four files, emitted by render_page_view_redirects below for -# every page. The LAST page additionally emits ALBUM_REDIRECTS_LAST_PAGE_EXTRA -# files: the 0-MAXPREVIEWS / 0-MAXPREVIEWS-details entry stubs that bounce into -# the album. Keep these two numbers in lockstep with render_page_view_redirects; -# the dry-run plan predicts redirect_count from them via -# album_redirect_count_for_page_count instead of a magic "*4+2". -declare -gri ALBUM_REDIRECTS_PER_PAGE=4 -declare -gri ALBUM_REDIRECTS_LAST_PAGE_EXTRA=2 +# Navigation-redirect count single source of truth (task nr0; extended for +# DETAILS_PAGE by task 6v0). Every view page gets ALBUM_VIEW_REDIRECTS_PER_PAGE +# wrap-around redirect files: the prev stub (N-0.html) and the next stub +# (N-(last+1).html). When DETAILS_PAGE=yes, each of those also gets a +# "-details" twin, adding ALBUM_DETAILS_REDIRECTS_PER_PAGE more -- so +# render_page_view_redirects emits 2 files per page with details disabled, 4 +# with them enabled. The LAST page additionally emits +# ALBUM_VIEW_REDIRECTS_LAST_PAGE_EXTRA entry stubs (plus their details twins +# when enabled): the 0-MAXPREVIEWS / loop-to-1 stubs that bounce into the +# album. Keep these numbers in lockstep with render_page_view_redirects; the +# dry-run plan predicts redirect_count from them via +# album_redirect_count_for_page_count instead of a magic formula. +declare -gri ALBUM_VIEW_REDIRECTS_PER_PAGE=2 +declare -gri ALBUM_DETAILS_REDIRECTS_PER_PAGE=2 +declare -gri ALBUM_VIEW_REDIRECTS_LAST_PAGE_EXTRA=1 +declare -gri ALBUM_DETAILS_REDIRECTS_LAST_PAGE_EXTRA=1 + +# Per-page / last-page-extra redirect file counts for the CURRENT DETAILS_PAGE +# setting: the view-only counts, plus the details counts when DETAILS_PAGE=yes. +# Split out of album_redirect_count_for_page_count so that function stays a +# short arithmetic one-liner. +album_redirects_per_page() { + local -i count=$ALBUM_VIEW_REDIRECTS_PER_PAGE + + if [ "$DETAILS_PAGE" = yes ]; then + count+=$ALBUM_DETAILS_REDIRECTS_PER_PAGE + fi + printf '%d\n' "$count" +} + +album_redirects_last_page_extra() { + local -i count=$ALBUM_VIEW_REDIRECTS_LAST_PAGE_EXTRA + + if [ "$DETAILS_PAGE" = yes ]; then + count+=$ALBUM_DETAILS_REDIRECTS_LAST_PAGE_EXTRA + fi + printf '%d\n' "$count" +} # Total navigation redirects a run produces for a given number of preview pages: -# four per page plus the last page's extra entry stubs. Zero pages -> zero -# redirects (render_view_redirects returns early on an empty album). This is the -# count render_page_view_redirects actually writes across all pages, expressed -# once so the dry-run plan cannot drift from real generation. +# album_redirects_per_page per page plus the last page's extra entry stubs. +# Zero pages -> zero redirects (render_view_redirects returns early on an empty +# album). This is the count render_page_view_redirects actually writes across +# all pages, expressed once so the dry-run plan cannot drift from real +# generation. album_redirect_count_for_page_count() { local -ri page_count="$1"; shift + local -i per_page + local -i last_page_extra if (( page_count <= 0 )); then printf '0\n' return fi - printf '%d\n' "$(( page_count * ALBUM_REDIRECTS_PER_PAGE \ - + ALBUM_REDIRECTS_LAST_PAGE_EXTRA ))" + per_page=$(album_redirects_per_page) + last_page_extra=$(album_redirects_last_page_extra) + printf '%d\n' "$(( page_count * per_page + last_page_extra ))" +} + +# Render one details-redirect stub, but only when DETAILS_PAGE=yes (mirrors +# render_photo_view_and_details skipping render_details_page entirely). A no-op +# under DETAILS_PAGE=no keeps every "-details.html" navigation stub from ever +# being written, so no generated page can link to a missing one. +_render_details_redirect() { + local -r html_dir="$1"; shift + local -r filename="$1"; shift + local -r target="$1"; shift + + if [ "$DETAILS_PAGE" != yes ]; then + return + fi + template redirect "$filename" html_dir "$html_dir" redirect_page "$target" } # Render every navigation redirect for a single view page (the prev/next @@ -3834,7 +3922,7 @@ album_redirect_count_for_page_count() { # files for distinct pages are produced here. The wrap-around redirects for the # very last page (0-MAXPREVIEWS and the loop-to-1 links) are emitted as part of # that page's group. Per-page / last-page file counts are fixed by -# ALBUM_REDIRECTS_PER_PAGE / ALBUM_REDIRECTS_LAST_PAGE_EXTRA above. +# album_redirects_per_page / album_redirects_last_page_extra above. render_page_view_redirects() { local -r html_dir="$1"; shift local -ri page="$1"; shift @@ -3846,30 +3934,26 @@ render_page_view_redirects() { template redirect "$prevredirect.html" \ html_dir "$html_dir" \ redirect_page "$(( page - 1 ))-${MAXPREVIEWS}" - template redirect "$prevredirect-details.html" \ - html_dir "$html_dir" \ - redirect_page "$(( page - 1 ))-${MAXPREVIEWS}-details" + _render_details_redirect "$html_dir" "$prevredirect-details.html" \ + "$(( page - 1 ))-${MAXPREVIEWS}-details" if (( page == max_page )); then template redirect "0-$MAXPREVIEWS.html" \ html_dir "$html_dir" \ redirect_page "${page}-$lastview" - template redirect "0-$MAXPREVIEWS-details.html" \ - html_dir "$html_dir" \ - redirect_page "${page}-$lastview-details" + _render_details_redirect "$html_dir" "0-$MAXPREVIEWS-details.html" \ + "${page}-$lastview-details" template redirect "$nextredirect.html" \ html_dir "$html_dir" \ redirect_page '1-1' - template redirect "$nextredirect-details.html" \ - html_dir "$html_dir" \ - redirect_page '1-1-details' + _render_details_redirect "$html_dir" "$nextredirect-details.html" \ + '1-1-details' else template redirect "$nextredirect.html" \ html_dir "$html_dir" \ redirect_page "$(( page + 1 ))-1" - template redirect "$nextredirect-details.html" \ - html_dir "$html_dir" \ - redirect_page "$(( page + 1 ))-1-details" + _render_details_redirect "$html_dir" "$nextredirect-details.html" \ + "$(( page + 1 ))-1-details" fi } @@ -5460,7 +5544,9 @@ render_stats_page() { # within that filter. The "--<index>" suffix cannot collide with another gallery # name because a pagebase never contains "--". All pages reuse the album's shared # photos/thumbs/blurs assets (only the HTML differs); view pages link "Details" -# to the album's own details page via the album_view_page_for_photo accessor. +# to the album's own details page via the album_view_page_for_photo accessor, +# but only when DETAILS_PAGE=yes actually rendered that page (see +# _stats_build_filterview_body below). # Pages render in # parallel through the shared job pool, throttled to IMAGE_JOBS. The galleries # reuse camera.tmpl and the view pages reuse cameraview.tmpl. @@ -5603,7 +5689,11 @@ _stats_build_filterview_body() { tooltip_attr=" title=\"$(html_escape "$tooltip")\"" fi view_page=$(album_view_page_for_photo "$photo") - if [ -n "$view_page" ]; then + # Only link to the album's details page when it was actually rendered + # (DETAILS_PAGE=yes); otherwise album_view_page_for_photo resolving a page + # would still point at a details file that render_photo_view_and_details + # never wrote, leaving a dangling link. + if [ -n "$view_page" ] && [ "$DETAILS_PAGE" = yes ]; then details_link=$(printf \ ' <a href="%s/%s-details.html">Details</a> <span class="nav-sep">|</span>' \ "$backhref_html" "$view_page") @@ -5772,6 +5862,7 @@ declare -gra CONFIG_SPECS=( 'RANDOM_SEED||yes|yes||scalar' 'SHUFFLE|no|yes|yes|yesno|scalar' 'SPLASH_PAGE|yes|yes|yes|yesno|scalar' + 'DETAILS_PAGE|yes|yes|yes|yesno|scalar' 'STATS_PAGE|no|yes|yes|yesno|scalar' 'TARBALL_INCLUDE|yes|yes|yes|yesno|scalar' 'TARBALL_SUFFIX|.tar|yes|no||scalar' @@ -6517,8 +6608,20 @@ validate_template_file() { validate_template_dir() { local template_name - local -a required_templates=( - details + local -a required_templates=() + + validate_template_dir_access || return + + # details.tmpl is only needed when DETAILS_PAGE=yes actually renders it + # (mirrors the SPLASH_PAGE=yes conditional below), so an album that opts out + # of details pages does not need to keep a details.tmpl around at all. Kept + # first in the list (as it always was) so the reporting order for the + # unconditional templates below is unchanged from before DETAILS_PAGE + # existed. + if [ "$DETAILS_PAGE" = yes ]; then + required_templates+=(details) + fi + required_templates+=( footer header next @@ -6529,8 +6632,6 @@ validate_template_dir() { view ) - validate_template_dir_access || return - if [ "$SPLASH_PAGE" = yes ]; then required_templates+=(splash) fi @@ -6694,6 +6795,7 @@ validate_common_config() { SYNC_TIMEOUT SHUFFLE SPLASH_PAGE + DETAILS_PAGE STATS_PAGE TARBALL_INCLUDE FAVICON @@ -7115,6 +7217,7 @@ log_configured_action() { log_verbose "Effective ImageMagick timeout: ${IMAGEMAGICK_TIMEOUT}s" log_verbose "Effective tar timeout: ${TAR_TIMEOUT}s" log_verbose "Effective splash page setting: $SPLASH_PAGE" + log_verbose "Effective details page setting: $DETAILS_PAGE" log_verbose "Effective stats page setting: $STATS_PAGE" log_verbose "Effective tarball setting: $TARBALL_INCLUDE" log_verbose "Effective sync delete setting: $SYNC_DELETE" |
