1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
# ----------------------------------------------------------------------------
# Config field registry (single source of truth, task mr0)
# ----------------------------------------------------------------------------
# CONFIG_SPECS is the one place a config field's cross-cutting facts are
# declared. Before mr0 the same knowledge (default value, CLI-overridability,
# validation rule, how it prints) was restated in ~6 hand-maintained lists --
# apply_config_defaults, CLI_CONFIG_OVERRIDE_TARGETS, validate_common_config,
# print_config, and parts of log_configured_action / the dry-run plan -- so
# adding or renaming an option was shotgun surgery and the lists drifted (the
# TARBALL_INCLUDE default once flipped to 'no' here while shuriken.default.conf
# still said 'yes', fixed in 7r0). Those consumers now DERIVE from this registry,
# so a field's facts live in exactly one entry.
#
# Each entry is a '|'-delimited spec, the same encoding used by ACTION_SPECS
# (action.source.sh) and TEMPLATE_RENDER_FIELD_SPECS (template.source.sh):
#
# name|default|has_default|cli_overridable|validation|print_kind
#
# name the config variable name (also the env/override key).
# default the value apply_config_defaults applies via
# VAR="${VAR:-$default}" when has_default=yes. May be empty
# (e.g. FAVICON, HEIGHT default to the empty string).
# has_default yes -> apply the scalar default above. no -> never apply a
# scalar default: either a required var (TITLE, THUMBHEIGHT,
# ...) that must be set in the config, or an array
# (TAR_OPTS / SYNC_DESTINATIONS) defaulted separately via a
# `declare -p` guard in apply_config_defaults.
# cli_overridable yes -> the field appears in CLI_CONFIG_OVERRIDE_TARGETS, i.e.
# a CLI flag (declared in CLI_OPTION_SPEC) can override it. The
# rich per-flag table (argument names, flag/value pairs) stays
# in CLI_OPTION_SPEC; this facet only drives the override-target
# list that used to be a separate hand-kept copy of it.
# validation the rule validate_common_config applies. Empty means
# validate_common_config does not check this field (SYNC_DELETE,
# the timeouts only checked on their own paths, etc. are
# validated elsewhere). One of:
# required require_config_var (non-empty)
# required-posint require_config_var + positive integer
# posint positive integer (no non-empty requirement)
# opt-posint positive integer only if set (HEIGHT)
# percentage integer 0..100
# yesno literal yes or no
# favicon validate_favicon_config (readable file/empty)
# print_kind how print_config emits the field. scalar -> %s=%q. array ->
# %s=( ... ) via the resolve_*-fed array printer. Empty means
# print_config does not emit it (none today). CONFIG_SOURCE is
# printed separately (it is the resolved config path, not a
# config variable) and so is not a registry entry.
#
# Entry order is the canonical print order (print_config emits in this order).
# validate_common_config does NOT reuse this order directly: it runs all
# `required*` checks before any kind check (so a missing required var is reported
# before a malformed one -- see test_config_validators_fail_fast_without_errexit),
# which it achieves with two filtered passes over the registry.
#
# Declared -g so it survives being sourced from inside a function (the test
# harness sources the lib via test::source_shuriken_lib); a plain `declare -r`
# would be function-local and vanish on return.
declare -gra CONFIG_SPECS=(
'INCOMING_DIR||no|yes|required|scalar'
'DIST_DIR||no|yes|required|scalar'
'TEMPLATE_DIR||no|yes|required|scalar'
'FAVICON||yes|yes|favicon|scalar'
'SOURCE_URL|https://codeberg.org/snonux/shuriken.sh|yes|yes||scalar'
'TITLE||no|yes|required|scalar'
'HEIGHT||yes|yes|opt-posint|scalar'
'THUMBHEIGHT||no|yes|required-posint|scalar'
'MAXPREVIEWS||no|yes|required-posint|scalar'
'THUMB_SUBDIVIDE_PERCENT|30|yes|yes|percentage|scalar'
'THUMB_FEATURE_PERCENT|10|yes|yes|percentage|scalar'
'IMAGE_JOBS|3|yes|yes|required-posint|scalar'
'IMAGEMAGICK_TIMEOUT|60|yes|no|posint|scalar'
'RANDOM_SEED||yes|yes||scalar'
'CHRONOLOGICAL_ORDER|no|yes|yes|yesno|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'
'TAR_TIMEOUT|120|yes|no|posint|scalar'
'TAR_OPTS||no|no||array'
'SYNC_DELETE|yes|yes|yes||scalar'
'SYNC_TIMEOUT|300|yes|no|posint|scalar'
'SYNC_DESTINATIONS||no|no||array'
'ORIGINAL_BASEPATH||yes|no||scalar'
)
# Split one CONFIG_SPECS entry into the caller's named array (IFS='|' read), the
# same accessor pattern action_spec_field uses for ACTION_SPECS. Field indices:
# 0 name 1 default 2 has_default 3 cli_overridable 4 validation 5 print_kind
config_spec_split() {
local -r spec="$1"; shift
# shellcheck disable=SC2178
local -n fields_ref="$1"; shift
# fields_ref is a nameref output array filled for the caller; shellcheck
# cannot see the indirect use through the nameref.
# shellcheck disable=SC2034
IFS='|' read -r -a fields_ref <<< "$spec"
}
# Populate CLI_CONFIG_OVERRIDE_TARGETS from the registry: every field marked
# cli_overridable=yes. This is the list apply_cli_overrides iterates to copy
# parsed --flag values onto their config var. Declared (empty) in src/shuriken.sh
# before the libs are sourced; filled here, once CONFIG_SPECS exists. Replaces the
# hand-kept copy of CLI_OPTION_SPEC's config= targets that used to drift (mr0).
# Iteration order is registry order; it is unobservable because each override
# targets a distinct variable, so no field can shadow another.
build_cli_config_override_targets() {
local spec
local -a fields=()
CLI_CONFIG_OVERRIDE_TARGETS=()
for spec in "${CONFIG_SPECS[@]}"; do
config_spec_split "$spec" fields
if [ "${fields[3]}" = yes ]; then
CLI_CONFIG_OVERRIDE_TARGETS+=("${fields[0]}")
fi
done
}
# Build the override-target list at source time so it is ready before any CLI
# parsing. Guarded so sourcing this module without the shuriken.sh-level
# declaration (e.g. a narrowly scoped unit test) is a no-op rather than an error.
if declare -p CLI_CONFIG_OVERRIDE_TARGETS >/dev/null 2>&1; then
build_cli_config_override_targets
fi
|