diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-24 10:45:28 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-24 10:45:28 +0300 |
| commit | 6697431d3855d3030b35c2ce232c9ea0e9282ba7 (patch) | |
| tree | 8e36cce1db02a40ee0954bd552ed43cb4ff2fe21 | |
| parent | 6dfc0f1a9521cc4afe4941fc1328eec014b760a7 (diff) | |
Split album-metadata.source.sh into focused modules (6r0)
album-metadata.source.sh aggregated six unrelated concerns. Move each
along its existing seam (pure mechanical move, no logic changes):
- EXIF presentation (photo_exif_details_html, tooltip helpers, the
_photo_exif_values_to wrapper) stays in album-metadata.source.sh,
which is now EXIF-presentation only.
- File counting (count_files, count_incoming_images, count_tree_files)
-> image.source.sh, which already owns incoming_image_files;
count_incoming_images is a direct wrapper of it.
- Tarball naming (tarball_name_plan, generated_tarball_name) ->
archive.source.sh, which already owns tarball()/resolve_tar_opts.
- Generation metadata + JSON (_collect_generation_metadata,
_generation_metadata_json, write_generation_metadata) -> new
generation-metadata.source.sh.
- Dry-run (dry_run, collect_dry_run_*, print_dry_run_plan) -> new
dry-run.source.sh.
- clear_exif_cache -> metadata-cache.source.sh, next to the cache
primitive cached_photo_identify_output.
LIB_SOURCES (Justfile + src/shuriken.sh): insert generation-metadata
and dry-run right after album-metadata, before album-render/album.
They depend on image, archive, template and metadata-cache (all earlier
or runtime-only calls), and are consumed by the album coordinator and
the dry-run CLI action, which come later. bin/shuriken regenerated via
just build. File-header comments updated to reflect the new homes.
just test, just shellcheck, just check-generated and git diff --check
all pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| -rw-r--r-- | Justfile | 2 | ||||
| -rwxr-xr-x | bin/shuriken | 165 | ||||
| -rw-r--r-- | src/lib/album-metadata.source.sh | 308 | ||||
| -rw-r--r-- | src/lib/archive.source.sh | 28 | ||||
| -rw-r--r-- | src/lib/dry-run.source.sh | 142 | ||||
| -rw-r--r-- | src/lib/generation-metadata.source.sh | 120 | ||||
| -rw-r--r-- | src/lib/image.source.sh | 38 | ||||
| -rw-r--r-- | src/lib/metadata-cache.source.sh | 13 | ||||
| -rwxr-xr-x | src/shuriken.sh | 4 |
9 files changed, 463 insertions, 357 deletions
@@ -7,7 +7,7 @@ PREFIX := env_var_or_default("PREFIX", "/usr") BINDIR := env_var_or_default("BINDIR", PREFIX + "/bin") DATADIR := env_var_or_default("DATADIR", PREFIX + "/share") SYSCONFDIR := env_var_or_default("SYSCONFDIR", "/etc/default") -LIB_SOURCES := "src/lib/logging.source.sh src/lib/bootstrap.source.sh src/lib/paths.source.sh src/lib/imagemagick.source.sh src/lib/process.source.sh src/lib/archive.source.sh src/lib/template.source.sh src/lib/job-pool.source.sh src/lib/image.source.sh src/lib/random.source.sh src/lib/metadata-label.source.sh src/lib/metadata-cache.source.sh src/lib/image-pipeline.source.sh src/lib/album-metadata.source.sh src/lib/album-render.source.sh src/lib/album.source.sh src/lib/stats-aggregate.source.sh src/lib/stats-render.source.sh src/lib/stats-filter-album.source.sh src/lib/config.source.sh src/lib/config.print.source.sh src/lib/config.sync.source.sh src/lib/config.staging.source.sh src/lib/config.validate.source.sh src/lib/config.cli.source.sh src/lib/action.source.sh" +LIB_SOURCES := "src/lib/logging.source.sh src/lib/bootstrap.source.sh src/lib/paths.source.sh src/lib/imagemagick.source.sh src/lib/process.source.sh src/lib/archive.source.sh src/lib/template.source.sh src/lib/job-pool.source.sh src/lib/image.source.sh src/lib/random.source.sh src/lib/metadata-label.source.sh src/lib/metadata-cache.source.sh src/lib/image-pipeline.source.sh src/lib/album-metadata.source.sh src/lib/generation-metadata.source.sh src/lib/dry-run.source.sh src/lib/album-render.source.sh src/lib/album.source.sh src/lib/stats-aggregate.source.sh src/lib/stats-render.source.sh src/lib/stats-filter-album.source.sh src/lib/config.source.sh src/lib/config.print.source.sh src/lib/config.sync.source.sh src/lib/config.staging.source.sh src/lib/config.validate.source.sh src/lib/config.cli.source.sh src/lib/action.source.sh" default: build diff --git a/bin/shuriken b/bin/shuriken index 2cc4b9c..169bdd4 100755 --- a/bin/shuriken +++ b/bin/shuriken @@ -465,6 +465,34 @@ run_with_timeout() { } # Inlined from src/lib/archive.source.sh +# Tarball creation plus the naming helpers that decide what the archive file is +# called. The naming helpers (tarball_name_plan / generated_tarball_name) were +# moved here from album-metadata.source.sh (task 6r0): naming the archive is part +# of this module's tarball concern, alongside tarball() which writes it. The +# planned-name variant is consumed by the dry-run preview; the generated variant +# (with a real timestamp slug) is consumed by the album coordinator when it +# actually creates the archive. Behaviour and signatures are unchanged. + +# Print the tarball name as it will appear in the dry-run plan: the incoming +# directory's basename, a literal "<timestamp>" placeholder, and the suffix. +tarball_name_plan() { + local base + + base=$(basename "$INCOMING_DIR") + printf '%s-<timestamp>%s\n' "$base" "$TARBALL_SUFFIX" +} + +# Print the real tarball name used at generation time: the incoming directory's +# basename, the current timestamp slug, and the configured suffix. +generated_tarball_name() { + local base + local timestamp + + base=$(basename "$INCOMING_DIR") + timestamp=$(current_timestamp_slug) + printf '%s-%s%s\n' "$base" "$timestamp" "$TARBALL_SUFFIX" +} + tarball() { local -r tarball_name="$1"; shift local -r tarball_suffix="$TARBALL_SUFFIX" @@ -1624,6 +1652,44 @@ incoming_image_files() { | sort } +# File counters for the source tree and the generated dist. Moved here from +# album-metadata.source.sh (task 6r0): counting incoming/generated files is part +# of this module's file-enumeration concern -- count_incoming_images is a direct +# wrapper of incoming_image_files above, and count_files/count_tree_files are the +# generic siblings the generation-metadata and dry-run modules use to tally the +# produced photos, thumbs and HTML pages. Behaviour and signatures are unchanged. +count_files() { + local -r dir="$1"; shift + local -r name="${1:-}"; shift || true + + if [ ! -d "$dir" ]; then + printf '0\n' + return + fi + + if [ -n "$name" ]; then + find "$dir" -maxdepth 1 -type f -name "$name" | wc -l + else + find "$dir" -maxdepth 1 -type f | wc -l + fi +} + +count_incoming_images() { + incoming_image_files | wc -l +} + +count_tree_files() { + local -r dir="$1"; shift + local -r name="$1"; shift + + if [ ! -d "$dir" ]; then + printf '0\n' + return + fi + + find "$dir" -type f -name "$name" | wc -l +} + warn_unsupported_incoming_files() { local file @@ -1952,6 +2018,19 @@ photo_exif_values_to() { done } +# Empty the volatile EXIF cache (./cache/exif, parallel to ./dist) so a --force +# run re-runs `identify` from scratch. Done once up front; the cache then +# repopulates and is reused for the rest of the run (one identify per photo). +# Moved here from album-metadata.source.sh (task 6r0): clearing the cache is the +# lifecycle counterpart of cached_photo_identify_output above, so the cache's +# creation and destruction now live in the same module. +clear_exif_cache() { + local -r cache_dir="$(dirname "$DIST_DIR")/cache/exif" + + log_verbose "Force generation; clearing EXIF cache $cache_dir" + rm -rf "$cache_dir" +} + # Inlined from src/lib/image-pipeline.source.sh create_photo_derivatives() { local -r photos_dir="$1"; shift @@ -2039,6 +2118,14 @@ prepare_generation_photo_assets() { } # Inlined from src/lib/album-metadata.source.sh +# Album EXIF presentation: turn a photo's parsed EXIF values into the per-photo +# details table and hover tooltip the album pages render. This module now holds +# ONLY the EXIF presentation concern -- the file counting, tarball naming, +# generation-metadata/JSON, dry-run and cache-lifecycle helpers that used to live +# here were split out into their own modules (task 6r0): counting -> image, tarball +# naming -> archive, generation metadata -> generation-metadata, dry-run -> dry-run, +# clear_exif_cache -> metadata-cache. +# # The EXIF identify cache primitive (cached_photo_identify_output and its # private helpers photo_cache_signature / print_cached_photo_identify_output) # was promoted to the shared metadata-cache.source.sh module (task pn0): it is a @@ -2187,53 +2274,18 @@ photo_exif_tooltip_text() { _photo_exif_tooltip_text_from_values exif_values } -count_files() { - local -r dir="$1"; shift - local -r name="${1:-}"; shift || true - - if [ ! -d "$dir" ]; then - printf '0\n' - return - fi - - if [ -n "$name" ]; then - find "$dir" -maxdepth 1 -type f -name "$name" | wc -l - else - find "$dir" -maxdepth 1 -type f | wc -l - fi -} - -count_incoming_images() { - incoming_image_files | wc -l -} - -tarball_name_plan() { - local base - - base=$(basename "$INCOMING_DIR") - printf '%s-<timestamp>%s\n' "$base" "$TARBALL_SUFFIX" -} - -generated_tarball_name() { - local base - local timestamp - - base=$(basename "$INCOMING_DIR") - timestamp=$(current_timestamp_slug) - printf '%s-%s%s\n' "$base" "$timestamp" "$TARBALL_SUFFIX" -} - -count_tree_files() { - local -r dir="$1"; shift - local -r name="$1"; shift - - if [ ! -d "$dir" ]; then - printf '0\n' - return - fi - - find "$dir" -type f -name "$name" | wc -l -} +# Inlined from src/lib/generation-metadata.source.sh +# Generation metadata: collect a snapshot of the run (generator version, source +# and generated file counts, effective settings) and serialise it to the +# dist/shuriken.json sidecar. Moved out of album-metadata.source.sh (task 6r0): +# describing the generation run is its own concern, distinct from the EXIF +# presentation that stays in the album module. The collector depends on the file +# counters (count_files / count_incoming_images / count_tree_files, now in +# image.source.sh), current_timestamp_iso (template.source.sh) and the JSON +# helpers (_json_string / _json_bool, template.source.sh); all are sourced before +# this module, and these are runtime calls anyway, so source order documents the +# dependency without affecting availability. Behaviour and signatures are +# unchanged by the move. _collect_generation_metadata() { local -r tarball_file="$1"; shift @@ -2344,15 +2396,16 @@ write_generation_metadata() { } > "$DIST_DIR/shuriken.json" } -# Empty the volatile EXIF cache (./cache/exif, parallel to ./dist) so a --force -# run re-runs `identify` from scratch. Done once up front; the cache then -# repopulates and is reused for the rest of the run (one identify per photo). -clear_exif_cache() { - local -r cache_dir="$(dirname "$DIST_DIR")/cache/exif" - - log_verbose "Force generation; clearing EXIF cache $cache_dir" - rm -rf "$cache_dir" -} +# Inlined from src/lib/dry-run.source.sh +# Dry-run plan: describe what a generation run WOULD produce (directories, page +# and redirect counts, the planned tarball name) without writing anything. Moved +# out of album-metadata.source.sh (task 6r0): previewing a run is its own concern, +# distinct from the EXIF presentation that stays in the album module. The plan +# depends on count_incoming_images (image.source.sh) for the image tally and +# tarball_name_plan (archive.source.sh) for the planned archive name; both are +# sourced before this module and called at runtime, so source order documents the +# dependency without affecting availability. dry_run is the CLI entry point wired +# up from action.source.sh. Behaviour and signatures are unchanged by the move. dry_run() { # shellcheck disable=SC2034 diff --git a/src/lib/album-metadata.source.sh b/src/lib/album-metadata.source.sh index 767ec4c..b96c817 100644 --- a/src/lib/album-metadata.source.sh +++ b/src/lib/album-metadata.source.sh @@ -1,3 +1,11 @@ +# Album EXIF presentation: turn a photo's parsed EXIF values into the per-photo +# details table and hover tooltip the album pages render. This module now holds +# ONLY the EXIF presentation concern -- the file counting, tarball naming, +# generation-metadata/JSON, dry-run and cache-lifecycle helpers that used to live +# here were split out into their own modules (task 6r0): counting -> image, tarball +# naming -> archive, generation metadata -> generation-metadata, dry-run -> dry-run, +# clear_exif_cache -> metadata-cache. +# # The EXIF identify cache primitive (cached_photo_identify_output and its # private helpers photo_cache_signature / print_cached_photo_identify_output) # was promoted to the shared metadata-cache.source.sh module (task pn0): it is a @@ -145,303 +153,3 @@ photo_exif_tooltip_text() { _photo_exif_values_to exif_values "$photo" "$photo_path" _photo_exif_tooltip_text_from_values exif_values } - -count_files() { - local -r dir="$1"; shift - local -r name="${1:-}"; shift || true - - if [ ! -d "$dir" ]; then - printf '0\n' - return - fi - - if [ -n "$name" ]; then - find "$dir" -maxdepth 1 -type f -name "$name" | wc -l - else - find "$dir" -maxdepth 1 -type f | wc -l - fi -} - -count_incoming_images() { - incoming_image_files | wc -l -} - -tarball_name_plan() { - local base - - base=$(basename "$INCOMING_DIR") - printf '%s-<timestamp>%s\n' "$base" "$TARBALL_SUFFIX" -} - -generated_tarball_name() { - local base - local timestamp - - base=$(basename "$INCOMING_DIR") - timestamp=$(current_timestamp_slug) - printf '%s-%s%s\n' "$base" "$timestamp" "$TARBALL_SUFFIX" -} - -count_tree_files() { - local -r dir="$1"; shift - local -r name="$1"; shift - - if [ ! -d "$dir" ]; then - printf '0\n' - return - fi - - find "$dir" -type f -name "$name" | wc -l -} - -_collect_generation_metadata() { - local -r tarball_file="$1"; shift - - declare -gA _GENERATION_METADATA=() - _GENERATION_METADATA["generator_name"]='shuriken' - _GENERATION_METADATA["generator_version"]="$VERSION" - _GENERATION_METADATA["generated_at"]=$(current_timestamp_iso) - _GENERATION_METADATA["config_source"]="$SHURIKEN_CONFIG_SOURCE" - _GENERATION_METADATA["template_name"]=$(basename "$TEMPLATE_DIR") - _GENERATION_METADATA["template_directory"]="$TEMPLATE_DIR" - _GENERATION_METADATA["source_incoming_dir"]="$INCOMING_DIR" - _GENERATION_METADATA["source_image_count"]=$(count_incoming_images) - _GENERATION_METADATA["generated_photo_count"]=$(count_files "$DIST_DIR/photos") - _GENERATION_METADATA["generated_thumb_count"]=$(count_files "$DIST_DIR/thumbs") - _GENERATION_METADATA["generated_html_count"]=$( - count_tree_files "$DIST_DIR" '*.html' - ) - _GENERATION_METADATA["tarball_included"]="$TARBALL_INCLUDE" - _GENERATION_METADATA["tarball_file"]="$tarball_file" - _GENERATION_METADATA["settings_title"]="$TITLE" - _GENERATION_METADATA["settings_height"]="$HEIGHT" - _GENERATION_METADATA["settings_thumbheight"]="$THUMBHEIGHT" - _GENERATION_METADATA["settings_maxpreviews"]="$MAXPREVIEWS" - _GENERATION_METADATA["settings_subdivide_percent"]="$THUMB_SUBDIVIDE_PERCENT" - _GENERATION_METADATA["settings_feature_percent"]="$THUMB_FEATURE_PERCENT" - _GENERATION_METADATA["settings_image_jobs"]="$IMAGE_JOBS" - _GENERATION_METADATA["settings_random_seed"]="$RANDOM_SEED" - _GENERATION_METADATA["settings_shuffle"]="$SHUFFLE" - _GENERATION_METADATA["settings_splash_page"]="$SPLASH_PAGE" - _GENERATION_METADATA["settings_stats_page"]="$STATS_PAGE" - _GENERATION_METADATA["settings_original_basepath"]="$ORIGINAL_BASEPATH" -} - -_generation_metadata_json() { - printf '{\n' - printf ' "generator": {\n' - printf ' "name": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["generator_name"]}")" - printf ' "version": %s\n' \ - "$(_json_string "${_GENERATION_METADATA["generator_version"]}")" - printf ' },\n' - printf ' "generated_at": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["generated_at"]}")" - printf ' "config_source": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["config_source"]}")" - printf ' "template": {\n' - printf ' "name": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["template_name"]}")" - printf ' "directory": %s\n' \ - "$(_json_string "${_GENERATION_METADATA["template_directory"]}")" - printf ' },\n' - printf ' "source": {\n' - printf ' "incoming_dir": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["source_incoming_dir"]}")" - printf ' "image_count": %s\n' \ - "${_GENERATION_METADATA["source_image_count"]}" - printf ' },\n' - printf ' "generated": {\n' - printf ' "photo_count": %s,\n' \ - "${_GENERATION_METADATA["generated_photo_count"]}" - printf ' "thumb_count": %s,\n' \ - "${_GENERATION_METADATA["generated_thumb_count"]}" - printf ' "html_count": %s\n' \ - "${_GENERATION_METADATA["generated_html_count"]}" - printf ' },\n' - printf ' "tarball": {\n' - printf ' "included": %s,\n' \ - "$(_json_bool "${_GENERATION_METADATA["tarball_included"]}")" - printf ' "file": %s\n' \ - "$(_json_string "${_GENERATION_METADATA["tarball_file"]}")" - printf ' },\n' - printf ' "settings": {\n' - printf ' "title": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["settings_title"]}")" - printf ' "height": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["settings_height"]}")" - printf ' "thumbheight": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["settings_thumbheight"]}")" - printf ' "maxpreviews": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["settings_maxpreviews"]}")" - printf ' "subdivide_percent": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["settings_subdivide_percent"]}")" - printf ' "feature_percent": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["settings_feature_percent"]}")" - printf ' "image_jobs": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["settings_image_jobs"]}")" - printf ' "random_seed": %s,\n' \ - "$(_json_string "${_GENERATION_METADATA["settings_random_seed"]}")" - printf ' "shuffle": %s,\n' \ - "$(_json_bool "${_GENERATION_METADATA["settings_shuffle"]}")" - printf ' "splash_page": %s,\n' \ - "$(_json_bool "${_GENERATION_METADATA["settings_splash_page"]}")" - printf ' "stats_page": %s,\n' \ - "$(_json_bool "${_GENERATION_METADATA["settings_stats_page"]}")" - printf ' "original_basepath": %s\n' \ - "$(_json_string "${_GENERATION_METADATA["settings_original_basepath"]}")" - printf ' }\n' - printf '}\n' -} - -write_generation_metadata() { - local -r tarball_file="$1"; shift - - _collect_generation_metadata "$tarball_file" - { - _generation_metadata_json - } > "$DIST_DIR/shuriken.json" -} - -# Empty the volatile EXIF cache (./cache/exif, parallel to ./dist) so a --force -# run re-runs `identify` from scratch. Done once up front; the cache then -# repopulates and is reused for the rest of the run (one identify per photo). -clear_exif_cache() { - local -r cache_dir="$(dirname "$DIST_DIR")/cache/exif" - - log_verbose "Force generation; clearing EXIF cache $cache_dir" - rm -rf "$cache_dir" -} - -dry_run() { - # shellcheck disable=SC2034 - local -A dry_run_plan=() - - collect_dry_run_plan dry_run_plan - print_dry_run_plan dry_run_plan -} - -collect_dry_run_page_plan() { - local -r plan_name="$1"; shift - # shellcheck disable=SC2178 - local -n plan_ref="$plan_name" - local -r image_count="$1"; shift - local -i page_count=0 - local -i redirect_count=0 - - plan_ref["html_index_count"]=1 - plan_ref["page_count"]=0 - plan_ref["redirect_count"]=0 - plan_ref["details_count"]=0 - - if (( image_count > 0 )); then - page_count=$(( (image_count + MAXPREVIEWS - 1) / MAXPREVIEWS )) - redirect_count=$(( page_count * 4 + 2 )) - plan_ref["details_count"]="$image_count" - plan_ref["page_count"]="$page_count" - plan_ref["redirect_count"]="$redirect_count" - fi -} - -collect_dry_run_plan() { - local -r plan_name="$1"; shift - # shellcheck disable=SC2178 - local -n plan_ref="$plan_name" - local -i image_count=0 - - image_count=$(count_incoming_images) - plan_ref=() - plan_ref["config_source"]="$SHURIKEN_CONFIG_SOURCE" - plan_ref["incoming_dir"]="$INCOMING_DIR" - plan_ref["dist_dir"]="$DIST_DIR" - plan_ref["template_dir"]="$TEMPLATE_DIR" - plan_ref["title"]="$TITLE" - plan_ref["height"]="$HEIGHT" - plan_ref["thumbheight"]="$THUMBHEIGHT" - plan_ref["maxpreviews"]="$MAXPREVIEWS" - plan_ref["subdivide_percent"]="$THUMB_SUBDIVIDE_PERCENT" - plan_ref["feature_percent"]="$THUMB_FEATURE_PERCENT" - plan_ref["image_jobs"]="$IMAGE_JOBS" - plan_ref["random_seed"]="$RANDOM_SEED" - plan_ref["shuffle"]="$SHUFFLE" - plan_ref["splash_page"]="$SPLASH_PAGE" - plan_ref["stats_page"]="$STATS_PAGE" - plan_ref["image_count"]="$image_count" - plan_ref["tarball_include"]="$TARBALL_INCLUDE" - plan_ref["tarball_name_plan"]='not planned' - - if [ "$TARBALL_INCLUDE" = yes ]; then - plan_ref["tarball_name_plan"]=$(tarball_name_plan) - fi - - collect_dry_run_page_plan "$plan_name" "$image_count" -} - -print_dry_run_plan() { - local -r plan_name="$1"; shift - # shellcheck disable=SC2178 - local -n plan_ref="$plan_name" - - printf 'Dry run: no files will be written.\n' - printf 'Config source: %s\n' "${plan_ref["config_source"]}" - printf 'Incoming directory: %s\n' "${plan_ref["incoming_dir"]}" - printf 'Output directory: %s\n' "${plan_ref["dist_dir"]}" - printf 'Template directory: %s\n' "${plan_ref["template_dir"]}" - printf 'Title: %s\n' "${plan_ref["title"]}" - printf 'Height: %s\n' "${plan_ref["height"]}" - printf 'Thumb height: %s\n' "${plan_ref["thumbheight"]}" - printf 'Max previews per page: %s\n' "${plan_ref["maxpreviews"]}" - printf 'Subdivide percent: %s\n' "${plan_ref["subdivide_percent"]}" - printf 'Feature percent: %s\n' "${plan_ref["feature_percent"]}" - printf 'Image jobs: %s\n' "${plan_ref["image_jobs"]}" - 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 '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"]}" - - printf 'Planned directories:\n' - printf ' %s\n' "${plan_ref["dist_dir"]}" - printf ' %s/photos\n' "${plan_ref["dist_dir"]}" - printf ' %s/thumbs\n' "${plan_ref["dist_dir"]}" - printf ' %s/blurs\n' "${plan_ref["dist_dir"]}" - - printf 'Planned generated files:\n' - if [ "${plan_ref["splash_page"]}" = yes ]; then - printf ' %s/index.html (%s splash page)\n' \ - "${plan_ref["dist_dir"]}" "${plan_ref["html_index_count"]}" - else - printf ' %s/index.html (%s album index redirect)\n' \ - "${plan_ref["dist_dir"]}" "${plan_ref["html_index_count"]}" - fi - printf ' %s/favicon.ico\n' "${plan_ref["dist_dir"]}" - printf ' %s/shuriken.json\n' "${plan_ref["dist_dir"]}" - printf ' %s/photos/* (%s image files)\n' \ - "${plan_ref["dist_dir"]}" "${plan_ref["image_count"]}" - printf ' %s/thumbs/* (%s image files)\n' \ - "${plan_ref["dist_dir"]}" "${plan_ref["image_count"]}" - printf ' %s/blurs/* (%s image files)\n' \ - "${plan_ref["dist_dir"]}" "${plan_ref["image_count"]}" - printf ' %s/page-*.html (%s preview pages)\n' \ - "${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"]}" - printf ' %s/[redirect].html (%s navigation redirects)\n' \ - "${plan_ref["dist_dir"]}" "${plan_ref["redirect_count"]}" - if [ "${plan_ref["stats_page"]}" = yes ]; then - # The exact filter mini-album set needs EXIF aggregation, which dry-run - # does not perform, so list them as a wildcard under stats/. - printf ' %s/stats/index.html (EXIF stats page)\n' \ - "${plan_ref["dist_dir"]}" - printf ' %s/stats/*/ (filter mini-albums)\n' \ - "${plan_ref["dist_dir"]}" - fi - if [ "${plan_ref["tarball_include"]}" = yes ]; then - printf ' %s/%s\n' \ - "${plan_ref["dist_dir"]}" "${plan_ref["tarball_name_plan"]}" - fi -} diff --git a/src/lib/archive.source.sh b/src/lib/archive.source.sh index b53a782..c27c65c 100644 --- a/src/lib/archive.source.sh +++ b/src/lib/archive.source.sh @@ -1,3 +1,31 @@ +# Tarball creation plus the naming helpers that decide what the archive file is +# called. The naming helpers (tarball_name_plan / generated_tarball_name) were +# moved here from album-metadata.source.sh (task 6r0): naming the archive is part +# of this module's tarball concern, alongside tarball() which writes it. The +# planned-name variant is consumed by the dry-run preview; the generated variant +# (with a real timestamp slug) is consumed by the album coordinator when it +# actually creates the archive. Behaviour and signatures are unchanged. + +# Print the tarball name as it will appear in the dry-run plan: the incoming +# directory's basename, a literal "<timestamp>" placeholder, and the suffix. +tarball_name_plan() { + local base + + base=$(basename "$INCOMING_DIR") + printf '%s-<timestamp>%s\n' "$base" "$TARBALL_SUFFIX" +} + +# Print the real tarball name used at generation time: the incoming directory's +# basename, the current timestamp slug, and the configured suffix. +generated_tarball_name() { + local base + local timestamp + + base=$(basename "$INCOMING_DIR") + timestamp=$(current_timestamp_slug) + printf '%s-%s%s\n' "$base" "$timestamp" "$TARBALL_SUFFIX" +} + tarball() { local -r tarball_name="$1"; shift local -r tarball_suffix="$TARBALL_SUFFIX" diff --git a/src/lib/dry-run.source.sh b/src/lib/dry-run.source.sh new file mode 100644 index 0000000..75a0655 --- /dev/null +++ b/src/lib/dry-run.source.sh @@ -0,0 +1,142 @@ +# Dry-run plan: describe what a generation run WOULD produce (directories, page +# and redirect counts, the planned tarball name) without writing anything. Moved +# out of album-metadata.source.sh (task 6r0): previewing a run is its own concern, +# distinct from the EXIF presentation that stays in the album module. The plan +# depends on count_incoming_images (image.source.sh) for the image tally and +# tarball_name_plan (archive.source.sh) for the planned archive name; both are +# sourced before this module and called at runtime, so source order documents the +# dependency without affecting availability. dry_run is the CLI entry point wired +# up from action.source.sh. Behaviour and signatures are unchanged by the move. + +dry_run() { + # shellcheck disable=SC2034 + local -A dry_run_plan=() + + collect_dry_run_plan dry_run_plan + print_dry_run_plan dry_run_plan +} + +collect_dry_run_page_plan() { + local -r plan_name="$1"; shift + # shellcheck disable=SC2178 + local -n plan_ref="$plan_name" + local -r image_count="$1"; shift + local -i page_count=0 + local -i redirect_count=0 + + plan_ref["html_index_count"]=1 + plan_ref["page_count"]=0 + plan_ref["redirect_count"]=0 + plan_ref["details_count"]=0 + + if (( image_count > 0 )); then + page_count=$(( (image_count + MAXPREVIEWS - 1) / MAXPREVIEWS )) + redirect_count=$(( page_count * 4 + 2 )) + plan_ref["details_count"]="$image_count" + plan_ref["page_count"]="$page_count" + plan_ref["redirect_count"]="$redirect_count" + fi +} + +collect_dry_run_plan() { + local -r plan_name="$1"; shift + # shellcheck disable=SC2178 + local -n plan_ref="$plan_name" + local -i image_count=0 + + image_count=$(count_incoming_images) + plan_ref=() + plan_ref["config_source"]="$SHURIKEN_CONFIG_SOURCE" + plan_ref["incoming_dir"]="$INCOMING_DIR" + plan_ref["dist_dir"]="$DIST_DIR" + plan_ref["template_dir"]="$TEMPLATE_DIR" + plan_ref["title"]="$TITLE" + plan_ref["height"]="$HEIGHT" + plan_ref["thumbheight"]="$THUMBHEIGHT" + plan_ref["maxpreviews"]="$MAXPREVIEWS" + plan_ref["subdivide_percent"]="$THUMB_SUBDIVIDE_PERCENT" + plan_ref["feature_percent"]="$THUMB_FEATURE_PERCENT" + plan_ref["image_jobs"]="$IMAGE_JOBS" + plan_ref["random_seed"]="$RANDOM_SEED" + plan_ref["shuffle"]="$SHUFFLE" + plan_ref["splash_page"]="$SPLASH_PAGE" + plan_ref["stats_page"]="$STATS_PAGE" + plan_ref["image_count"]="$image_count" + plan_ref["tarball_include"]="$TARBALL_INCLUDE" + plan_ref["tarball_name_plan"]='not planned' + + if [ "$TARBALL_INCLUDE" = yes ]; then + plan_ref["tarball_name_plan"]=$(tarball_name_plan) + fi + + collect_dry_run_page_plan "$plan_name" "$image_count" +} + +print_dry_run_plan() { + local -r plan_name="$1"; shift + # shellcheck disable=SC2178 + local -n plan_ref="$plan_name" + + printf 'Dry run: no files will be written.\n' + printf 'Config source: %s\n' "${plan_ref["config_source"]}" + printf 'Incoming directory: %s\n' "${plan_ref["incoming_dir"]}" + printf 'Output directory: %s\n' "${plan_ref["dist_dir"]}" + printf 'Template directory: %s\n' "${plan_ref["template_dir"]}" + printf 'Title: %s\n' "${plan_ref["title"]}" + printf 'Height: %s\n' "${plan_ref["height"]}" + printf 'Thumb height: %s\n' "${plan_ref["thumbheight"]}" + printf 'Max previews per page: %s\n' "${plan_ref["maxpreviews"]}" + printf 'Subdivide percent: %s\n' "${plan_ref["subdivide_percent"]}" + printf 'Feature percent: %s\n' "${plan_ref["feature_percent"]}" + printf 'Image jobs: %s\n' "${plan_ref["image_jobs"]}" + 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 '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"]}" + + printf 'Planned directories:\n' + printf ' %s\n' "${plan_ref["dist_dir"]}" + printf ' %s/photos\n' "${plan_ref["dist_dir"]}" + printf ' %s/thumbs\n' "${plan_ref["dist_dir"]}" + printf ' %s/blurs\n' "${plan_ref["dist_dir"]}" + + printf 'Planned generated files:\n' + if [ "${plan_ref["splash_page"]}" = yes ]; then + printf ' %s/index.html (%s splash page)\n' \ + "${plan_ref["dist_dir"]}" "${plan_ref["html_index_count"]}" + else + printf ' %s/index.html (%s album index redirect)\n' \ + "${plan_ref["dist_dir"]}" "${plan_ref["html_index_count"]}" + fi + printf ' %s/favicon.ico\n' "${plan_ref["dist_dir"]}" + printf ' %s/shuriken.json\n' "${plan_ref["dist_dir"]}" + printf ' %s/photos/* (%s image files)\n' \ + "${plan_ref["dist_dir"]}" "${plan_ref["image_count"]}" + printf ' %s/thumbs/* (%s image files)\n' \ + "${plan_ref["dist_dir"]}" "${plan_ref["image_count"]}" + printf ' %s/blurs/* (%s image files)\n' \ + "${plan_ref["dist_dir"]}" "${plan_ref["image_count"]}" + printf ' %s/page-*.html (%s preview pages)\n' \ + "${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"]}" + printf ' %s/[redirect].html (%s navigation redirects)\n' \ + "${plan_ref["dist_dir"]}" "${plan_ref["redirect_count"]}" + if [ "${plan_ref["stats_page"]}" = yes ]; then + # The exact filter mini-album set needs EXIF aggregation, which dry-run + # does not perform, so list them as a wildcard under stats/. + printf ' %s/stats/index.html (EXIF stats page)\n' \ + "${plan_ref["dist_dir"]}" + printf ' %s/stats/*/ (filter mini-albums)\n' \ + "${plan_ref["dist_dir"]}" + fi + if [ "${plan_ref["tarball_include"]}" = yes ]; then + printf ' %s/%s\n' \ + "${plan_ref["dist_dir"]}" "${plan_ref["tarball_name_plan"]}" + fi +} diff --git a/src/lib/generation-metadata.source.sh b/src/lib/generation-metadata.source.sh new file mode 100644 index 0000000..b75af1b --- /dev/null +++ b/src/lib/generation-metadata.source.sh @@ -0,0 +1,120 @@ +# Generation metadata: collect a snapshot of the run (generator version, source +# and generated file counts, effective settings) and serialise it to the +# dist/shuriken.json sidecar. Moved out of album-metadata.source.sh (task 6r0): +# describing the generation run is its own concern, distinct from the EXIF +# presentation that stays in the album module. The collector depends on the file +# counters (count_files / count_incoming_images / count_tree_files, now in +# image.source.sh), current_timestamp_iso (template.source.sh) and the JSON +# helpers (_json_string / _json_bool, template.source.sh); all are sourced before +# this module, and these are runtime calls anyway, so source order documents the +# dependency without affecting availability. Behaviour and signatures are +# unchanged by the move. + +_collect_generation_metadata() { + local -r tarball_file="$1"; shift + + declare -gA _GENERATION_METADATA=() + _GENERATION_METADATA["generator_name"]='shuriken' + _GENERATION_METADATA["generator_version"]="$VERSION" + _GENERATION_METADATA["generated_at"]=$(current_timestamp_iso) + _GENERATION_METADATA["config_source"]="$SHURIKEN_CONFIG_SOURCE" + _GENERATION_METADATA["template_name"]=$(basename "$TEMPLATE_DIR") + _GENERATION_METADATA["template_directory"]="$TEMPLATE_DIR" + _GENERATION_METADATA["source_incoming_dir"]="$INCOMING_DIR" + _GENERATION_METADATA["source_image_count"]=$(count_incoming_images) + _GENERATION_METADATA["generated_photo_count"]=$(count_files "$DIST_DIR/photos") + _GENERATION_METADATA["generated_thumb_count"]=$(count_files "$DIST_DIR/thumbs") + _GENERATION_METADATA["generated_html_count"]=$( + count_tree_files "$DIST_DIR" '*.html' + ) + _GENERATION_METADATA["tarball_included"]="$TARBALL_INCLUDE" + _GENERATION_METADATA["tarball_file"]="$tarball_file" + _GENERATION_METADATA["settings_title"]="$TITLE" + _GENERATION_METADATA["settings_height"]="$HEIGHT" + _GENERATION_METADATA["settings_thumbheight"]="$THUMBHEIGHT" + _GENERATION_METADATA["settings_maxpreviews"]="$MAXPREVIEWS" + _GENERATION_METADATA["settings_subdivide_percent"]="$THUMB_SUBDIVIDE_PERCENT" + _GENERATION_METADATA["settings_feature_percent"]="$THUMB_FEATURE_PERCENT" + _GENERATION_METADATA["settings_image_jobs"]="$IMAGE_JOBS" + _GENERATION_METADATA["settings_random_seed"]="$RANDOM_SEED" + _GENERATION_METADATA["settings_shuffle"]="$SHUFFLE" + _GENERATION_METADATA["settings_splash_page"]="$SPLASH_PAGE" + _GENERATION_METADATA["settings_stats_page"]="$STATS_PAGE" + _GENERATION_METADATA["settings_original_basepath"]="$ORIGINAL_BASEPATH" +} + +_generation_metadata_json() { + printf '{\n' + printf ' "generator": {\n' + printf ' "name": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["generator_name"]}")" + printf ' "version": %s\n' \ + "$(_json_string "${_GENERATION_METADATA["generator_version"]}")" + printf ' },\n' + printf ' "generated_at": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["generated_at"]}")" + printf ' "config_source": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["config_source"]}")" + printf ' "template": {\n' + printf ' "name": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["template_name"]}")" + printf ' "directory": %s\n' \ + "$(_json_string "${_GENERATION_METADATA["template_directory"]}")" + printf ' },\n' + printf ' "source": {\n' + printf ' "incoming_dir": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["source_incoming_dir"]}")" + printf ' "image_count": %s\n' \ + "${_GENERATION_METADATA["source_image_count"]}" + printf ' },\n' + printf ' "generated": {\n' + printf ' "photo_count": %s,\n' \ + "${_GENERATION_METADATA["generated_photo_count"]}" + printf ' "thumb_count": %s,\n' \ + "${_GENERATION_METADATA["generated_thumb_count"]}" + printf ' "html_count": %s\n' \ + "${_GENERATION_METADATA["generated_html_count"]}" + printf ' },\n' + printf ' "tarball": {\n' + printf ' "included": %s,\n' \ + "$(_json_bool "${_GENERATION_METADATA["tarball_included"]}")" + printf ' "file": %s\n' \ + "$(_json_string "${_GENERATION_METADATA["tarball_file"]}")" + printf ' },\n' + printf ' "settings": {\n' + printf ' "title": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["settings_title"]}")" + printf ' "height": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["settings_height"]}")" + printf ' "thumbheight": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["settings_thumbheight"]}")" + printf ' "maxpreviews": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["settings_maxpreviews"]}")" + printf ' "subdivide_percent": %s,\n' \ + "$(_json_string "${_GENERATION_METADATA["settings_subdivide_perc |
