diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-16 18:00:47 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-16 18:00:47 +0300 |
| commit | cd7cf797bc633700c5ee7e986077f366c000f0b9 (patch) | |
| tree | db7bda7af7a71359cba6bc6c0557552e8659fe8c /src/lib | |
| parent | b58d9b3f656f2e6be803a1d2d567a066bb8b902a (diff) | |
hn0 make template render kind dispatch extensible (OCP)
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>
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/template.source.sh | 265 |
1 files changed, 177 insertions, 88 deletions
diff --git a/src/lib/template.source.sh b/src/lib/template.source.sh index dd62c42..13a2988 100644 --- a/src/lib/template.source.sh +++ b/src/lib/template.source.sh @@ -454,19 +454,183 @@ serialize_template_render_context() { done } +# --- Per-kind render handlers (Open/Closed dispatch) -------------------------- +# +# Each TEMPLATE_RENDER_FIELD_SPECS "kind" is implemented by one +# prepare_template_render_var__<kind> function. prepare_template_render_vars +# resolves the handler by name (prepare_template_render_var__$kind) and calls +# it, so a NEW kind is added simply by defining a new handler function with the +# matching name -- the core loop never needs editing (Open/Closed Principle). +# +# Every handler shares the same calling convention so the loop can invoke them +# uniformly: +# prepare_template_render_var__<kind> <render_value_out> \ +# <context_name> <source_name> +# The first argument is an output nameref the handler writes the final +# render_value into; handlers that do not need the context name or source_name +# simply ignore those arguments. Handlers reproduce exactly what the old +# "case $kind" arms produced (same escaping and defaults), so rendered output +# is byte-identical. +# +# Note: SC2317 cannot see these handlers being called -- the dispatch is by +# computed name -- so they would look unreachable/unused. The disable directive +# on each handler documents that it is reached dynamically via declare -F lookup. + +# shellcheck disable=SC2317 # called dynamically by prepare_template_render_vars +prepare_template_render_var__context_css() { + local -n out_ref="$1"; shift + local -r context_name="$1"; shift + local -r source_name="$1"; shift + local context_value + + template_context_value_to context_value "$context_name" "$source_name" + css_string_escape_to out_ref "$context_value" +} + +# shellcheck disable=SC2317 # called dynamically by prepare_template_render_vars +prepare_template_render_var__context_html() { + local -n out_ref="$1"; shift + local -r context_name="$1"; shift + local -r source_name="$1"; shift + local context_value + + template_context_value_to context_value "$context_name" "$source_name" + html_escape_to out_ref "$context_value" +} + +# shellcheck disable=SC2317 # called dynamically by prepare_template_render_vars +prepare_template_render_var__context_raw() { + local -n out_ref="$1"; shift + local -r context_name="$1"; shift + local -r source_name="$1"; shift + + template_context_value_to out_ref "$context_name" "$source_name" +} + +# shellcheck disable=SC2317 # called dynamically by prepare_template_render_vars +prepare_template_render_var__current_date_html() { + local -n out_ref="$1"; shift + local context_value + + current_date_text_to context_value + html_escape_to out_ref "$context_value" +} + +# config_html resolves its value from a named config variable. The inner +# source_name dispatch (HEIGHT/MAXPREVIEWS/TITLE/etc.) stays here so config +# handling is self-contained; its behavior is identical to the old arm. +# shellcheck disable=SC2317 # called dynamically by prepare_template_render_vars +prepare_template_render_var__config_html() { + local -n out_ref="$1"; shift + local -r _context_name="$1"; shift + local -r source_name="$1"; shift + local context_value + + case "$source_name" in + HEIGHT) + context_value="$HEIGHT" + ;; + MAXPREVIEWS) + # Refresh-only configs do not require generation + # sizing fields; render absent values as empty. + context_value="${MAXPREVIEWS:-}" + ;; + ORIGINAL_BASEPATH) + context_value="$ORIGINAL_BASEPATH" + ;; + STATS_PAGE) + # Always defaulted by apply_config_defaults; degrade to + # "no" (link hidden) if somehow unset so the header bar + # never references a stats page that was not generated. + context_value="${STATS_PAGE:-no}" + ;; + THUMBHEIGHT) + # Refresh-only configs do not require generation + # sizing fields; render absent values as empty. + context_value="${THUMBHEIGHT:-}" + ;; + TITLE) + context_value="$TITLE" + ;; + *) + config_error "unknown template render config $source_name" + return 1 + ;; + esac + html_escape_to out_ref "$context_value" +} + +# shellcheck disable=SC2317 # called dynamically by prepare_template_render_vars +prepare_template_render_var__original_basepath_is_set() { + # shellcheck disable=SC2034 # written through the output nameref + local -n out_ref="$1"; shift + + if [ -n "$ORIGINAL_BASEPATH" ]; then + out_ref='yes' + else + out_ref='no' + fi +} + +# shellcheck disable=SC2317 # called dynamically by prepare_template_render_vars +prepare_template_render_var__preview_num_next_html() { + # shellcheck disable=SC2034 # written through the output nameref + local -n out_ref="$1"; shift + local -r context_name="$1"; shift + local -r source_name="$1"; shift + local context_value + + template_context_value_to context_value "$context_name" "$source_name" + if [ -n "$context_value" ]; then + html_escape_to out_ref "$(( context_value + 1 ))" + else + out_ref='' + fi +} + +# shellcheck disable=SC2317 # called dynamically by prepare_template_render_vars +prepare_template_render_var__preview_num_prev_html() { + # shellcheck disable=SC2034 # written through the output nameref + local -n out_ref="$1"; shift + local -r context_name="$1"; shift + local -r source_name="$1"; shift + local context_value + + template_context_value_to context_value "$context_name" "$source_name" + if [ -n "$context_value" ]; then + html_escape_to out_ref "$(( context_value - 1 ))" + else + out_ref='' + fi +} + +# shellcheck disable=SC2317 # called dynamically by prepare_template_render_vars +prepare_template_render_var__tarball_include() { + local -n out_ref="$1"; shift + + # shellcheck disable=SC2034 # written through the output nameref + out_ref="$TARBALL_INCLUDE" +} + +# Core render loop. For each field spec it resolves the per-kind handler by name +# (prepare_template_render_var__<kind>) and dispatches to it; adding a kind means +# defining a new handler function, never touching this loop. An unknown kind -- +# one with no matching handler function -- is a config error, preserving the old +# "unknown template render field kind" behavior. prepare_template_render_vars() { local -r render_vars_name="$1"; shift local -r context_name="$1"; shift # shellcheck disable=SC2178 local -n render_vars_ref="$render_vars_name" - local context_value local field_spec + local handler local kind local render_value local render_var local _required_context_var local _required_templates local source_name + local -i status=0 render_vars_ref=() @@ -474,93 +638,18 @@ prepare_template_render_vars() { IFS='|' read -r render_var kind source_name \ _required_context_var _required_templates <<< "$field_spec" - case "$kind" in - context_css) - template_context_value_to \ - context_value "$context_name" "$source_name" - css_string_escape_to render_value "$context_value" - ;; - context_html) - template_context_value_to \ - context_value "$context_name" "$source_name" - html_escape_to render_value "$context_value" - ;; - context_raw) - template_context_value_to \ - render_value "$context_name" "$source_name" - ;; - current_date_html) - current_date_text_to context_value - html_escape_to render_value "$context_value" - ;; - config_html) - case "$source_name" in - HEIGHT) - context_value="$HEIGHT" - ;; - MAXPREVIEWS) - # Refresh-only configs do not require generation - # sizing fields; render absent values as empty. - context_value="${MAXPREVIEWS:-}" - ;; - ORIGINAL_BASEPATH) - context_value="$ORIGINAL_BASEPATH" - ;; - STATS_PAGE) - # Always defaulted by apply_config_defaults; degrade to - # "no" (link hidden) if somehow unset so the header bar - # never references a stats page that was not generated. - context_value="${STATS_PAGE:-no}" - ;; - THUMBHEIGHT) - # Refresh-only configs do not require generation - # sizing fields; render absent values as empty. - context_value="${THUMBHEIGHT:-}" - ;; - TITLE) - context_value="$TITLE" - ;; - *) - config_error \ - "unknown template render config $source_name" - return 1 - ;; - esac - html_escape_to render_value "$context_value" - ;; - original_basepath_is_set) - if [ -n "$ORIGINAL_BASEPATH" ]; then - render_value='yes' - else - render_value='no' - fi - ;; - preview_num_next_html) - template_context_value_to \ - context_value "$context_name" "$source_name" - if [ -n "$context_value" ]; then - html_escape_to render_value "$(( context_value + 1 ))" - else - render_value='' - fi - ;; - preview_num_prev_html) - template_context_value_to \ - context_value "$context_name" "$source_name" - if [ -n "$context_value" ]; then - html_escape_to render_value "$(( context_value - 1 ))" - else - render_value='' - fi - ;; - tarball_include) - render_value="$TARBALL_INCLUDE" - ;; - *) - config_error "unknown template render field kind $kind" - return 1 - ;; - esac + handler="prepare_template_render_var__$kind" + if ! declare -F "$handler" > /dev/null; then + config_error "unknown template render field kind $kind" + return 1 + fi + + render_value='' + "$handler" render_value "$context_name" "$source_name" + status=$? + if (( status != 0 )); then + return "$status" + fi render_vars_ref["$render_var"]="$render_value" done |
