diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-03 19:36:55 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-03 19:36:55 +0300 |
| commit | da8b0d3ba6d27c17f511da0284db8ae44e5e906b (patch) | |
| tree | 9637e0ea16d64752afc3934fc77244819744a296 | |
| parent | 53015df1627c035210f21845cacd401fae094bbf (diff) | |
Add output verbosity controls for zi0
| -rw-r--r-- | README.md | 7 | ||||
| -rwxr-xr-x | bin/photoalbum | 71 | ||||
| -rwxr-xr-x | src/photoalbum.sh | 71 | ||||
| -rwxr-xr-x | tests/cli.sh | 194 |
4 files changed, 319 insertions, 24 deletions
@@ -84,6 +84,13 @@ The following long options override config values: `--dry-run` accepts the same override options as `--generate`. `--clean` accepts the same override options, but only `--dist` changes what it removes. +Output is human-readable by default and reports routine generation progress. +Use `--quiet` to suppress routine progress while still writing errors to stderr. +Use `--verbose` for extra diagnostics, including the selected config file, +effective paths, skipped existing files, staging output directory, and tarball +decisions. If `--quiet` and `--verbose` are repeated or combined, the last output +flag wins. + ## Example usage 1. Run `photoalbum --init`. diff --git a/bin/photoalbum b/bin/photoalbum index 3e07fdb..f6123d5 100755 --- a/bin/photoalbum +++ b/bin/photoalbum @@ -6,6 +6,7 @@ set -euo pipefail declare -r VERSION='0.6.0' declare -r DEFAULTRC="${PHOTOALBUM_DEFAULT_RC:-/etc/default/photoalbum}" +PHOTOALBUM_OUTPUT_MODE="${PHOTOALBUM_OUTPUT_MODE:-normal}" usage() { cat - <<USAGE >&2 @@ -29,9 +30,35 @@ usage() { --no-shuffle --tarball --no-tarball + --verbose + --quiet USAGE } +output_is_quiet() { + [ "$PHOTOALBUM_OUTPUT_MODE" = quiet ] +} + +output_is_verbose() { + [ "$PHOTOALBUM_OUTPUT_MODE" = verbose ] +} + +log_info() { + if ! output_is_quiet; then + printf '%s\n' "$*" + fi +} + +log_verbose() { + if output_is_verbose; then + printf 'Verbose: %s\n' "$*" + fi +} + +log_warning() { + printf 'WARNING: %s\n' "$*" >&2 +} + resolve_default_rc_file() { local source_root @@ -91,7 +118,7 @@ init_config() { "$rc_file" fi - echo "Created ./$rc_file" + log_info "Created ./$rc_file" } imagemagick() { @@ -114,7 +141,7 @@ tarball() { find "$DIST_DIR" -maxdepth 1 -type f -name '*.tar' -delete base=$(basename "$INCOMING_DIR") - echo "Creating tarball $(_display_path "$DIST_DIR/$tarball_name")" \ + log_info "Creating tarball $(_display_path "$DIST_DIR/$tarball_name")" \ "from $INCOMING_DIR" ( cd "$(dirname "$INCOMING_DIR")" @@ -248,7 +275,7 @@ template() { local thumbs_dir_html local title_html - echo "Generating $(_display_path "$dist_html")/$html" + log_info "Generating $(_display_path "$dist_html")/$html" mkdir -p "$dist_html" animation_class_html=$(_html_escape "${animation_class:-}") @@ -305,10 +332,11 @@ cleanphotos() { continue fi - echo "Cleaning up $(_display_path "$photo")" + log_info "Cleaning up $(_display_path "$photo")" for sub in thumbs blurs photos; do if [ -f "$DIST_DIR/$sub/$basename" ]; then - rm -v "$DIST_DIR/$sub/$basename" + rm -f "$DIST_DIR/$sub/$basename" + log_info "removed '$(_display_path "$DIST_DIR/$sub/$basename")'" fi done done < <(find "$DIST_DIR/photos" -maxdepth 1 -type f) @@ -351,7 +379,7 @@ warn_unsupported_incoming_files() { while IFS= read -r file; do if ! is_supported_image_file "$file"; then - echo "WARNING: Ignoring unsupported incoming file: $file" >&2 + log_warning "Ignoring unsupported incoming file: $file" fi done < <(find "$INCOMING_DIR" -maxdepth 1 -type f -printf '%f\n' | sort) } @@ -367,11 +395,11 @@ scalephotos() { mkdir -p "$dirname" if [ -f "$destphoto" ]; then - echo "Already exists: $(_display_path "$destphoto")" + log_verbose "Skipped existing photo $(_display_path "$destphoto")" continue fi - echo "Processing $photo to $(_display_path "$destphoto")" + log_info "Processing $photo to $(_display_path "$destphoto")" if [ -n "${HEIGHT:-}" ]; then # Scale down size. imagemagick \ @@ -505,12 +533,13 @@ albumhtml() { if [[ -f "$DIST_DIR/$thumbs_dir/$photo" \ && -f "$DIST_DIR/$blurs_dir/$photo" ]]; then - echo "Already exists: $(_display_path "$DIST_DIR/$thumbs_dir/$photo") and" \ + log_verbose "Skipped existing thumb and blur" \ + "$(_display_path "$DIST_DIR/$thumbs_dir/$photo") and" \ "$(_display_path "$DIST_DIR/$blurs_dir/$photo")" else dirname="$DIST_DIR/$thumbs_dir" mkdir -p "$dirname" - echo "Creating thumb $(_display_path "$DIST_DIR/$thumbs_dir/$photo")" + log_info "Creating thumb $(_display_path "$DIST_DIR/$thumbs_dir/$photo")" # Double the height, as CSS scales images based on boxing too. height=$(( THUMBHEIGHT * 2 )) imagemagick \ @@ -520,7 +549,7 @@ albumhtml() { dirname="$DIST_DIR/$blurs_dir" mkdir -p "$dirname" - echo "Creating blur $(_display_path "$DIST_DIR/$blurs_dir/$photo")" + log_info "Creating blur $(_display_path "$DIST_DIR/$blurs_dir/$photo")" imagemagick \ "$DIST_DIR/$thumbs_dir/$photo" \ -flip \ @@ -696,6 +725,10 @@ generate() { if [ "${TARBALL_INCLUDE:-no}" = yes ]; then tarball_name=$(generated_tarball_name) + log_verbose \ + "Tarball enabled; planned archive: $(_display_path "$DIST_DIR/$tarball_name")" + else + log_verbose 'Tarball disabled; no archive will be created' fi warn_unsupported_incoming_files @@ -897,6 +930,8 @@ generate_staged() { local -i status=0 staging_dir=$(generation_staging_dir "$final_dist") + log_verbose "Effective output directory: $final_dist" + log_verbose "Generation staging directory: $staging_dir" PHOTOALBUM_ACTIVE_STAGING_DIR="$staging_dir" trap cleanup_generation_staging_dir EXIT trap 'cleanup_generation_staging_dir; exit 130' INT @@ -1238,6 +1273,12 @@ main() { cli_tarball_include='no' has_config_overrides='yes' ;; + --verbose) + PHOTOALBUM_OUTPUT_MODE=verbose + ;; + --quiet) + PHOTOALBUM_OUTPUT_MODE=quiet + ;; --version|--init|--clean|--generate|--dry-run) if [ -n "$action" ]; then usage @@ -1282,11 +1323,19 @@ main() { apply_cli_overrides PHOTOALBUM_CONFIG_SOURCE="$rc_file" export PHOTOALBUM_CONFIG_SOURCE + log_verbose "Selected config file: $rc_file" + log_verbose "Effective incoming directory: ${INCOMING_DIR:-}" + log_verbose "Effective output directory: ${DIST_DIR:-}" + log_verbose "Effective template directory: ${TEMPLATE_DIR:-}" + log_verbose "Effective tarball setting: ${TARBALL_INCLUDE:-no}" case "$action" in --clean) if [ -d "$DIST_DIR" ]; then + log_info "Cleaning $DIST_DIR" rm -rf "$DIST_DIR" + else + log_verbose "Output directory does not exist: $DIST_DIR" fi ;; --generate) diff --git a/src/photoalbum.sh b/src/photoalbum.sh index 5179c5c..88b6cfb 100755 --- a/src/photoalbum.sh +++ b/src/photoalbum.sh @@ -6,6 +6,7 @@ set -euo pipefail declare -r VERSION='PHOTOALBUMVERSION' declare -r DEFAULTRC="${PHOTOALBUM_DEFAULT_RC:-/etc/default/photoalbum}" +PHOTOALBUM_OUTPUT_MODE="${PHOTOALBUM_OUTPUT_MODE:-normal}" usage() { cat - <<USAGE >&2 @@ -29,9 +30,35 @@ usage() { --no-shuffle --tarball --no-tarball + --verbose + --quiet USAGE } +output_is_quiet() { + [ "$PHOTOALBUM_OUTPUT_MODE" = quiet ] +} + +output_is_verbose() { + [ "$PHOTOALBUM_OUTPUT_MODE" = verbose ] +} + +log_info() { + if ! output_is_quiet; then + printf '%s\n' "$*" + fi +} + +log_verbose() { + if output_is_verbose; then + printf 'Verbose: %s\n' "$*" + fi +} + +log_warning() { + printf 'WARNING: %s\n' "$*" >&2 +} + resolve_default_rc_file() { local source_root @@ -91,7 +118,7 @@ init_config() { "$rc_file" fi - echo "Created ./$rc_file" + log_info "Created ./$rc_file" } imagemagick() { @@ -114,7 +141,7 @@ tarball() { find "$DIST_DIR" -maxdepth 1 -type f -name '*.tar' -delete base=$(basename "$INCOMING_DIR") - echo "Creating tarball $(_display_path "$DIST_DIR/$tarball_name")" \ + log_info "Creating tarball $(_display_path "$DIST_DIR/$tarball_name")" \ "from $INCOMING_DIR" ( cd "$(dirname "$INCOMING_DIR")" @@ -248,7 +275,7 @@ template() { local thumbs_dir_html local title_html - echo "Generating $(_display_path "$dist_html")/$html" + log_info "Generating $(_display_path "$dist_html")/$html" mkdir -p "$dist_html" animation_class_html=$(_html_escape "${animation_class:-}") @@ -305,10 +332,11 @@ cleanphotos() { continue fi - echo "Cleaning up $(_display_path "$photo")" + log_info "Cleaning up $(_display_path "$photo")" for sub in thumbs blurs photos; do if [ -f "$DIST_DIR/$sub/$basename" ]; then - rm -v "$DIST_DIR/$sub/$basename" + rm -f "$DIST_DIR/$sub/$basename" + log_info "removed '$(_display_path "$DIST_DIR/$sub/$basename")'" fi done done < <(find "$DIST_DIR/photos" -maxdepth 1 -type f) @@ -351,7 +379,7 @@ warn_unsupported_incoming_files() { while IFS= read -r file; do if ! is_supported_image_file "$file"; then - echo "WARNING: Ignoring unsupported incoming file: $file" >&2 + log_warning "Ignoring unsupported incoming file: $file" fi done < <(find "$INCOMING_DIR" -maxdepth 1 -type f -printf '%f\n' | sort) } @@ -367,11 +395,11 @@ scalephotos() { mkdir -p "$dirname" if [ -f "$destphoto" ]; then - echo "Already exists: $(_display_path "$destphoto")" + log_verbose "Skipped existing photo $(_display_path "$destphoto")" continue fi - echo "Processing $photo to $(_display_path "$destphoto")" + log_info "Processing $photo to $(_display_path "$destphoto")" if [ -n "${HEIGHT:-}" ]; then # Scale down size. imagemagick \ @@ -505,12 +533,13 @@ albumhtml() { if [[ -f "$DIST_DIR/$thumbs_dir/$photo" \ && -f "$DIST_DIR/$blurs_dir/$photo" ]]; then - echo "Already exists: $(_display_path "$DIST_DIR/$thumbs_dir/$photo") and" \ + log_verbose "Skipped existing thumb and blur" \ + "$(_display_path "$DIST_DIR/$thumbs_dir/$photo") and" \ "$(_display_path "$DIST_DIR/$blurs_dir/$photo")" else dirname="$DIST_DIR/$thumbs_dir" mkdir -p "$dirname" - echo "Creating thumb $(_display_path "$DIST_DIR/$thumbs_dir/$photo")" + log_info "Creating thumb $(_display_path "$DIST_DIR/$thumbs_dir/$photo")" # Double the height, as CSS scales images based on boxing too. height=$(( THUMBHEIGHT * 2 )) imagemagick \ @@ -520,7 +549,7 @@ albumhtml() { dirname="$DIST_DIR/$blurs_dir" mkdir -p "$dirname" - echo "Creating blur $(_display_path "$DIST_DIR/$blurs_dir/$photo")" + log_info "Creating blur $(_display_path "$DIST_DIR/$blurs_dir/$photo")" imagemagick \ "$DIST_DIR/$thumbs_dir/$photo" \ -flip \ @@ -696,6 +725,10 @@ generate() { if [ "${TARBALL_INCLUDE:-no}" = yes ]; then tarball_name=$(generated_tarball_name) + log_verbose \ + "Tarball enabled; planned archive: $(_display_path "$DIST_DIR/$tarball_name")" + else + log_verbose 'Tarball disabled; no archive will be created' fi warn_unsupported_incoming_files @@ -897,6 +930,8 @@ generate_staged() { local -i status=0 staging_dir=$(generation_staging_dir "$final_dist") + log_verbose "Effective output directory: $final_dist" + log_verbose "Generation staging directory: $staging_dir" PHOTOALBUM_ACTIVE_STAGING_DIR="$staging_dir" trap cleanup_generation_staging_dir EXIT trap 'cleanup_generation_staging_dir; exit 130' INT @@ -1238,6 +1273,12 @@ main() { cli_tarball_include='no' has_config_overrides='yes' ;; + --verbose) + PHOTOALBUM_OUTPUT_MODE=verbose + ;; + --quiet) + PHOTOALBUM_OUTPUT_MODE=quiet + ;; --version|--init|--clean|--generate|--dry-run) if [ -n "$action" ]; then usage @@ -1282,11 +1323,19 @@ main() { apply_cli_overrides PHOTOALBUM_CONFIG_SOURCE="$rc_file" export PHOTOALBUM_CONFIG_SOURCE + log_verbose "Selected config file: $rc_file" + log_verbose "Effective incoming directory: ${INCOMING_DIR:-}" + log_verbose "Effective output directory: ${DIST_DIR:-}" + log_verbose "Effective template directory: ${TEMPLATE_DIR:-}" + log_verbose "Effective tarball setting: ${TARBALL_INCLUDE:-no}" case "$action" in --clean) if [ -d "$DIST_DIR" ]; then + log_info "Cleaning $DIST_DIR" rm -rf "$DIST_DIR" + else + log_verbose "Output directory does not exist: $DIST_DIR" fi ;; --generate) diff --git a/tests/cli.sh b/tests/cli.sh index 3a6be1a..939600e 100755 --- a/tests/cli.sh +++ b/tests/cli.sh @@ -502,6 +502,181 @@ test_generate_cli_no_tarball_overrides_config() { test::teardown } +test_default_output_reports_routine_progress() { + local config_file + local fake_bin + local output + + test::setup + fake_bin="$TEST_TMPDIR/bin" + config_file="$TEST_TMPDIR/photoalbum.conf" + + test::install_fake_imagemagick "$fake_bin" + PATH="$fake_bin:$PATH" \ + test::generate_fixture_images "$TEST_TMPDIR/incoming" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Default output album' 40 + + output=$( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" --generate + ) + + test::assert_contains 'Processing 01-landscape.jpg to' "$output" + test::assert_contains 'Generating ' "$output" + test::assert_contains 'Creating thumb ' "$output" + test::assert_not_contains 'Verbose:' "$output" + test::teardown +} + +test_quiet_output_suppresses_routine_progress() { + local config_file + local fake_bin + local output + + test::setup + fake_bin="$TEST_TMPDIR/bin" + config_file="$TEST_TMPDIR/photoalbum.conf" + + test::install_fake_imagemagick "$fake_bin" + PATH="$fake_bin:$PATH" \ + test::generate_fixture_images "$TEST_TMPDIR/incoming" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Quiet output album' 40 + + output=$( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" --quiet --generate + ) + + test::assert_not_contains 'Processing ' "$output" + test::assert_not_contains 'Generating ' "$output" + test::assert_not_contains 'Creating thumb ' "$output" + test::assert_file_exists "$TEST_TMPDIR/dist/photoalbum.json" + test::teardown +} + +test_quiet_output_keeps_errors_on_stderr() { + local config_file + local output_file + local stderr_file + local stdout + local stderr + local -i status=0 + + test::setup + config_file="$TEST_TMPDIR/photoalbum.conf" + output_file="$TEST_TMPDIR/stdout" + stderr_file="$TEST_TMPDIR/stderr" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/missing" "$TEST_TMPDIR/dist" \ + 'Quiet error album' 40 + + set +e + ( + cd "$TEST_TMPDIR" + "$TEST_PHOTOALBUM" --quiet --generate \ + > "$output_file" 2> "$stderr_file" + ) + status=$? + set -e + + if (( status == 0 )); then + echo 'FAIL: expected quiet generation to fail' >&2 + exit 1 + fi + + stdout=$(<"$output_file") + stderr=$(<"$stderr_file") + test "$stdout" = '' + test::assert_contains \ + "ERROR: You have to create $TEST_TMPDIR/missing first" \ + "$stderr" + test::teardown +} + +test_verbose_output_reports_processing_decisions() { + local config_file + local fake_bin + local output + + test::setup + fake_bin="$TEST_TMPDIR/bin" + config_file="$TEST_TMPDIR/photoalbum.conf" + + test::install_fake_imagemagick "$fake_bin" + PATH="$fake_bin:$PATH" \ + test::generate_fixture_images "$TEST_TMPDIR/incoming" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Verbose output album' 40 + + ( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" --generate >/dev/null + ) + output=$( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" --verbose --generate + ) + + test::assert_contains 'Verbose: Selected config file: ./photoalbum.conf' \ + "$output" + test::assert_contains "Verbose: Effective incoming directory: $TEST_TMPDIR/incoming" \ + "$output" + test::assert_contains "Verbose: Effective output directory: $TEST_TMPDIR/dist" \ + "$output" + test::assert_contains \ + "Verbose: Effective template directory: $TEST_REPO_ROOT/share/templates/default" \ + "$output" + test::assert_contains \ + 'Verbose: Tarball disabled; no archive will be created' \ + "$output" + test::assert_contains \ + "Verbose: Skipped existing photo $TEST_TMPDIR/dist/photos/01-landscape.jpg" \ + "$output" + test::assert_contains 'Verbose: Skipped existing thumb and blur' "$output" + test::teardown +} + +test_repeated_output_flags_use_last_value() { + local config_file + local fake_bin + local quiet_last_output + local verbose_last_output + + test::setup + fake_bin="$TEST_TMPDIR/bin" + config_file="$TEST_TMPDIR/photoalbum.conf" + + test::install_fake_imagemagick "$fake_bin" + PATH="$fake_bin:$PATH" \ + test::generate_fixture_images "$TEST_TMPDIR/incoming" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Repeated output flags album' 40 + + verbose_last_output=$( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" \ + --quiet --verbose --generate + ) + quiet_last_output=$( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" \ + --verbose --quiet --generate + ) + + test::assert_contains 'Verbose: Selected config file: ./photoalbum.conf' \ + "$verbose_last_output" + test::assert_not_contains 'Verbose:' "$quiet_last_output" + test::assert_not_contains 'Processing ' "$quiet_last_output" + test::assert_not_contains 'Generating ' "$quiet_last_output" + test::teardown +} + test_dry_run_reports_cli_overrides_without_writes() { local config_file local dist_dir @@ -1366,14 +1541,14 @@ test_generate_preserves_space_filename_without_reprocessing() { ) second_output=$( cd "$TEST_TMPDIR" - PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" --generate + PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" --verbose --generate ) test::assert_file_exists "$TEST_TMPDIR/dist/photos/$photo_name" test::assert_path_absent "$TEST_TMPDIR/dist/photos/a_b.jpg" test::assert_contains "Processing $photo_name to" "$first_output" test::assert_contains \ - "Already exists: $TEST_TMPDIR/dist/photos/$photo_name" \ + "Verbose: Skipped existing photo $TEST_TMPDIR/dist/photos/$photo_name" \ "$second_output" test::assert_not_contains "Processing $photo_name to" "$second_output" @@ -1510,6 +1685,21 @@ main() { '--generate --no-tarball overrides config' \ test_generate_cli_no_tarball_overrides_config test::run_case \ + 'default output reports routine progress' \ + test_default_output_reports_routine_progress + test::run_case \ + '--quiet suppresses routine progress' \ + test_quiet_output_suppresses_routine_progress + test::run_case \ + '--quiet keeps errors on stderr' \ + test_quiet_output_keeps_errors_on_stderr + test::run_case \ + '--verbose reports processing decisions' \ + test_verbose_output_reports_processing_decisions + test::run_case \ + 'repeated output flags use last value' \ + test_repeated_output_flags_use_last_value + test::run_case \ '--dry-run reports CLI overrides without writes' \ test_dry_run_reports_cli_overrides_without_writes test::run_case \ |
