# --- Escape helper family: one consistent API shape ------------------------- # # Every escaper in this file follows the SAME two-form convention: # # _to nameref form, the hot path. Writes the escaped # result into the named variable with no # subshell/command substitution. # thin printf convenience wrapper. Writes the # escaped result to stdout (for $(...) callers). # It just delegates to _to and prints. # # Naming convention for the "_" prefix in THIS file: # - A leading "_" marks a genuinely PRIVATE/internal helper (only called from # within the same module). # - Helpers that are part of the public escape API -- called from other # modules -- carry NO leading "_". html_escape / css_string_escape / # json_string / json_bool are all called from sibling lib modules, so they # are public and unprefixed. (They used to be "_"-prefixed, which wrongly # signalled "private" for what is effectively a public API.) # # Only the escaping/encoding logic is authoritative; the printf wrappers add no # behavior of their own beyond appending a trailing newline for stdout use. html_escape_to() { local -n output_ref="$1"; shift local text="$1"; shift text=${text//&/\&} text=${text///\>} text=${text//\"/\"} text=${text//\'/\'} output_ref="$text" } # Thin printf wrapper around html_escape_to for $(...) callers. html_escape() { local -r text="$1"; shift local escaped_text html_escape_to escaped_text "$text" printf '%s\n' "$escaped_text" } css_string_escape_to() { local -n output_ref="$1"; shift local text="$1"; shift text=${text//\\/\\\\} text=${text//&/\\000026} text=${text///\\00003e} text=${text//\"/\\000022} text=${text//\'/\\000027} output_ref="$text" } # Thin printf wrapper around css_string_escape_to for $(...) callers. css_string_escape() { local -r text="$1"; shift local escaped_text css_string_escape_to escaped_text "$text" printf '%s\n' "$escaped_text" } json_string_escape_to() { local -n output_ref="$1"; shift local text="$1"; shift local char local escaped='' local escaped_char local -i code local -i i local LC_ALL=C for (( i = 0; i < ${#text}; i++ )); do char="${text:i:1}" case "$char" in $'\\') escaped+=$'\\\\' ;; '"') escaped+='\"' ;; $'\b') escaped+='\b' ;; $'\f') escaped+='\f' ;; $'\n') escaped+='\n' ;; $'\r') escaped+='\r' ;; $'\t') escaped+='\t' ;; *) code=$(printf '%d' "'$char") if (( code < 32 )); then printf -v escaped_char '\\u%04x' "$code" escaped+="$escaped_char" else escaped+="$char" fi ;; esac done output_ref="$escaped" } # Thin printf wrapper around json_string_escape_to for $(...) callers. json_string_escape() { local -r text="$1"; shift local escaped_text json_string_escape_to escaped_text "$text" printf '%s\n' "$escaped_text" } # Nameref form: write the JSON-quoted ("...") string into out_var. json_string_to() { local -n string_output_ref="$1"; shift local -r text="$1"; shift local escaped_text json_string_escape_to escaped_text "$text" # shellcheck disable=SC2034 # written through the output nameref string_output_ref="\"$escaped_text\"" } # Thin printf wrapper around json_string_to for $(...) callers. json_string() { local -r text="$1"; shift local quoted_text json_string_to quoted_text "$text" printf '%s' "$quoted_text" } # Nameref form: write the JSON boolean literal into out_var. json_bool_to() { local -n bool_output_ref="$1"; shift local -r value="$1"; shift # shellcheck disable=SC2034 # written through the output nameref case "$value" in yes) bool_output_ref='true' ;; *) bool_output_ref='false' ;; esac } # Thin printf wrapper around json_bool_to for $(...) callers. json_bool() { local -r value="$1"; shift local bool_literal json_bool_to bool_literal "$value" printf '%s' "$bool_literal" } _display_path() { local -r path="$1"; shift local -r final_dist="${SHURIKEN_FINAL_DIST_DIR:-}" if [[ -n "$final_dist" && "$path" == "$DIST_DIR"* ]]; then printf '%s%s\n' "$final_dist" "${path#"$DIST_DIR"}" else printf '%s\n' "$path" fi } # Nameref form: write the current date text into out_var, caching it in # SHURIKEN_CURRENT_DATE_TEXT so repeated calls within a run do not re-exec date # (the value is constant for a generation run). The wrapper below delegates here # so BOTH forms share this cache -- a hot-path caller using the printf form no # longer silently re-runs `date` every call. current_date_text_to() { local -n output_ref="$1"; shift if [ -z "$SHURIKEN_CURRENT_DATE_TEXT" ]; then if random_seed_is_set; then SHURIKEN_CURRENT_DATE_TEXT='Thu Jan 1 00:00:00 UTC 1970' else SHURIKEN_CURRENT_DATE_TEXT=$(command date) fi fi output_ref="$SHURIKEN_CURRENT_DATE_TEXT" } # Thin printf wrapper around current_date_text_to for stdout/$(...) callers. # Delegates so it shares the SHURIKEN_CURRENT_DATE_TEXT cache (output unchanged). current_date_text() { local date_text current_date_text_to date_text printf '%s\n' "$date_text" } # Each entry: render_var|kind|source_name|context_var|required_templates # # render_var name of the render_* variable handed to the .tmpl # kind handler suffix (prepare_template_render_var__) # source_name config var / context key the handler reads (kind-specific) # context_var context key validated by template_required_context_vars # (empty for fields not sourced from the render context, e.g. # config_html / derived kinds) # required_templates which templates actually reference this render_var in their # .tmpl: '*' = every template, '' = no template (always # serialized regardless, see prepare_template_render_vars), # else a space-separated template list. # # IMPORTANT: required_templates is the authoritative per-template render-var set # and MUST match what each .tmpl references (verified by # test_template_render_var_subsetting_matches_templates). It drives both # prepare_template_render_vars (which computes only the needed subset) and # serialize_template_render_context (which emits only that subset into BASH_ENV). # It is independent of context_var: context_var still drives # template_required_context_vars (input-context validation), which is why # correcting the required_templates field of context_var-less specs does not # change that function's output. declare -ra TEMPLATE_RENDER_FIELD_SPECS=( 'render_animation_class_html|context_html|animation_class|animation_class|preview details view' 'render_backhref_css|context_css|backhref|backhref|header splash' 'render_backhref_html|context_html|backhref|backhref|footer header preview splash details view stats camera' 'render_background_image_css|context_css|background_image|background_image|header splash' 'render_blurs_dir_css|context_css|blurs_dir|blurs_dir|header splash' 'render_camera_name_html|context_html|camera_name|camera_name|camera' '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' 'render_height_html|config_html|HEIGHT||header' 'render_html_dir_html|context_html|html_dir|html_dir|*' 'render_maxpreviews_html|config_html|MAXPREVIEWS||next prev' 'render_next_html|context_html|next|next|next' 'render_original_basepath_is_set|original_basepath_is_set|||view' 'render_original_basepath_html|config_html|ORIGINAL_BASEPATH||view' 'render_page_num_html|context_html|page_num|page_num|preview details view' 'render_photo_html|context_html|photo|photo|preview splash details view' 'render_photos_dir_html|context_html|photos_dir|photos_dir|splash details view' 'render_prev_html|context_html|prev|prev|prev' 'render_preview_num_html|context_html|preview_num|preview_num|preview details view' 'render_preview_thumbs_html|context_raw|preview_thumbs|preview_thumbs|previewpage' 'render_redirect_page_html|context_html|redirect_page|redirect_page|redirect' 'render_show_header_bar|context_raw|show_header_bar|show_header_bar|header' 'render_source_url_html|config_html|SOURCE_URL||header' 'render_stats_body_html|context_raw|stats_body|stats_body|stats' 'render_stats_page_html|config_html|STATS_PAGE||header' 'render_tarball_include|tarball_include|||footer' 'render_tarball_name_html|context_html|tarball_name|tarball_name|footer' 'render_thumbs_dir_html|context_html|thumbs_dir|thumbs_dir|preview' 'render_title_html|config_html|TITLE||camera header splash stats' 'render_view_next_html|preview_num_next_html|preview_num||details view' 'render_view_prev_html|preview_num_prev_html|preview_num||details view' ) current_timestamp_slug() { if random_seed_is_set; then printf '1970-01-01-000000\n' else command date +'%Y-%m-%d-%H%M%S' fi } current_timestamp_iso() { if random_seed_is_set; then printf '1970-01-01T00:00:00Z\n' else command date -u +'%Y-%m-%dT%H:%M:%SZ' fi } template_context_value() { # shellcheck disable=SC2178 local -n context_ref="$1"; shift local -r name="$1"; shift printf '%s\n' "${context_ref[$name]:-}" } template_context_value_to() { local -n output_ref="$1"; shift # shellcheck disable=SC2178 local -n context_ref="$1"; shift local -r name="$1"; shift # shellcheck disable=SC2034 output_ref="${context_ref[$name]:-}" } require_template_context_vars() { local -r template_name="$1"; shift # shellcheck disable=SC2178 local -n context_ref="$1"; shift local name for name in "$@"; do if [ -z "${context_ref[$name]+x}" ]; then config_error "template $template_name requires render variable $name" return 1 fi done } template_render_field_is_required_for() { local -r template_name="$1"; shift local -r required_template_names="$1"; shift case "$required_template_names" in '*') return 0 ;; '') return 1 ;; *) [[ " $required_template_names " == *" $template_name "* ]] ;; esac } # Build the set of render_* variables a template actually needs, driven by the # required_templates (5th) field of each spec. There are NO render-var -> # render-var dependencies (every handler reads from the input context array or a # config global, never from another already-computed render_var), so this direct # per-template set IS the full closure -- no transitive expansion is required. # Fields with an empty required_templates ('') are intentionally treated as # "needed by no template" here; the only such remaining spec is the special # render_html_dir_html ('*', always-needed) case which is covered by the '*' arm # of template_render_field_is_required_for. template_needed_render_vars_to() { # shellcheck disable=SC2178 # shellcheck disable=SC2178 local -n needed_vars_ref="$1"; shift local -r template_name="$1"; shift local field_spec local render_var local _kind local _source_name local _required_context_var local required_template_names needed_vars_ref=() for field_spec in "${TEMPLATE_RENDER_FIELD_SPECS[@]}"; do IFS='|' read -r render_var _kind _source_name \ _required_context_var required_template_names <<< "$field_spec" if template_render_field_is_required_for \ "$template_name" "$required_template_names"; then # shellcheck disable=SC2034 # written through the output nameref needed_vars_ref["$render_var"]=yes fi done } template_required_context_vars_to() { # shellcheck disable=SC2178 local -n required_vars_ref="$1"; shift local -r template_name="$1"; shift local -A required_var_seen=() local field_spec local _kind local _render_var local required_context_var local required_template_names local _source_name required_vars_ref=() for field_spec in "${TEMPLATE_RENDER_FIELD_SPECS[@]}"; do IFS='|' read -r _render_var _kind _source_name \ required_context_var required_template_names <<< "$field_spec" if [ -n "$required_context_var" ] \ && template_render_field_is_required_for \ "$template_name" "$required_template_names" \ && [ -z "${required_var_seen[$required_context_var]+x}" ]; then required_vars_ref+=("$required_context_var") required_var_seen["$required_context_var"]=yes fi done } template_required_context_vars() { local -r template_name="$1"; shift local -a required_vars=() template_required_context_vars_to required_vars "$template_name" if (( ${#required_vars[@]} > 0 )); then printf '%s\n' "${required_vars[@]}" fi } validate_template_context() { local -r template_name="$1"; shift local -r context_name="$1"; shift local -i status=0 local -a required_vars=() template_required_context_vars_to required_vars "$template_name" require_template_context_vars "$template_name" "$context_name" \ "${required_vars[@]}" status=$? if (( status != 0 )); then return "$status" fi } source_template_file() { local -r template_path="$1"; shift local -r output_path="$1"; shift local -r render_vars_name="$1"; shift local context_file local sig local -i status=0 context_file=$(mktemp) status=$? if (( status != 0 )); then return "$status" fi # Single cleanup point for the BASH_ENV context tempfile. The traps MUST be # registered here in source_template_file's own body (not in a helper): a # RETURN trap is not function-scoped unless functrace is enabled, so a trap # set inside a helper would fire when that helper returns and delete the file # before the render even runs. # # The RETURN trap covers normal and error returns (errexit unwinds through # it) and clears ALL of these traps, including itself, so a lingering RETURN # trap cannot fire again on an enclosing function's return against the now # out-of-scope context_file local (which would trip set -u). # # A RETURN trap alone does NOT fire when a signal terminates the shell with # its default disposition. source_template_file also runs in backgrounded # render subshells (see queue_album_view_render_job) that get SIGTERM'd by # terminate_active_generation on interrupt, so we additionally trap the # terminating signals: each handler removes the file, clears the traps and # re-raises the original signal so the process still exits with that signal's # default disposition. # # SIGKILL cannot be trapped, so a KILL escalation may still leak a file that # the OS tmp reaper later clears; that is the only unavoidable residual case. trap 'rm -f "$context_file"; trap - INT TERM HUP RETURN' RETURN for sig in INT TERM HUP; do # $sig is intentionally expanded now (so each handler re-raises its own # signal); $context_file and $BASHPID are escaped to expand when the trap # runs. We re-raise to $BASHPID, not $$: source_template_file commonly # runs in backgrounded render subshells where $$ is the main shuriken # PID, so kill -s $sig $$ would terminate the main shell (mid-cleanup, # after its own staging traps were cleared) instead of this subshell. # $BASHPID is the current (sub)shell's real PID and equals $$ in the # foreground case, so it is correct everywhere. # shellcheck disable=SC2064 trap "rm -f \"\$context_file\"; trap - INT TERM HUP RETURN; \ kill -s $sig \"\$BASHPID\"" "$sig" done # Build the BASH_ENV context file directly in the current shell. The .tmpl # is run via "env -i bash" with BASH_ENV pointing at this file, so the file # only needs the render_* variable assignments the template references plus a # trailing "unset BASH_ENV" (so the template's own children do not re-source # it). We deliberately avoid the old "declare -f | bash" approach: that # dumped all ~5000 lines of shuriken functions into a fresh bash subprocess # per page just to run serialize_template_render_context. Calling that # serializer in-process and redirecting its %q-quoted output is identical in # result but skips the function dump and subprocess spawn on every page. # Status-test the serializer with "if" so a failure returns normally through # the RETURN trap above (which removes the partial context file) instead of # letting errexit tear the shell down without running the trap. The serializer # returns its own non-zero status explicitly, so this works even when # source_template_file itself runs inside a status-tested ("if template ...") # call chain where bash would otherwise suppress an inner errexit abort. if serialize_template_render_context "$render_vars_name" \ > "$context_file"; then status=0 else status=$? fi if (( status != 0 )); then return "$status" fi # Appended only after the render vars serialized cleanly so the template's # own child shells do not re-source this BASH_ENV context file. printf 'unset BASH_ENV\n' >> "$context_file" if env -i PATH="$PATH" BASH_ENV="$context_file" \ bash -euo pipefail -- "$template_path" >> "$output_path"; then status=0 else status=$? fi if (( status != 0 )); then return "$status" fi } parse_template_context() { local -r template_name="$1"; shift local -n context_ref="$1"; shift local context_key local context_value while (( $# > 0 )); do if (( $# < 2 )); then config_error "template $template_name render context is incomplete" return 1 fi context_key="$1" context_value="$2" shift 2 if [[ ! "$context_key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then config_error \ "template $template_name render variable $context_key is invalid" return 1 fi # shellcheck disable=SC2034 context_ref["$context_key"]="$context_value" done } serialize_template_render_var() { local -r name="$1"; shift local -r value="$1"; shift printf '%s=%q\n' "$name" "$value" } # Emit "render_var=value" assignments on stdout, each %q-quoted via # serialize_template_render_var so the values survive being re-sourced by # "env -i bash". Runs in the current shell (called by source_template_file) # reading the caller's render_vars associative array by name and the top-level # TEMPLATE_RENDER_FIELD_SPECS constant. # # Only the render_vars that prepare_template_render_vars actually computed (i.e. # the subset the target template needs) are present in render_vars_ref, so we # iterate the spec order but emit only the keys present. This keeps a stable, # spec-driven emission order while serializing exactly the per-template subset -- # no spec entry for an absent (non-needed) field is written into BASH_ENV. # # Returns the status of the failing write explicitly rather than relying on # errexit: source_template_file (and its production callers) may run inside an # "if"/status-tested context where bash suppresses a called function's own # errexit, so an explicit non-zero return is the only reliable failure signal. serialize_template_render_context() { local -r render_vars_name="$1"; shift # shellcheck disable=SC2178 local -n render_vars_ref="$render_vars_name" local field_spec local _kind local render_var local _required_context_var local _required_templates local _source_name local -i status=0 for field_spec in "${TEMPLATE_RENDER_FIELD_SPECS[@]}"; do IFS='|' read -r render_var _kind _source_name \ _required_context_var _required_templates <<< "$field_spec" if [ -z "${render_vars_ref[$render_var]+x}" ]; then continue fi serialize_template_render_var \ "$render_var" "${render_vars_ref[$render_var]}" status=$? if (( status != 0 )); then return "$status" fi done } # --- Per-kind render handlers (Open/Closed dispatch) -------------------------- # # Each TEMPLATE_RENDER_FIELD_SPECS "kind" is implemented by one # prepare_template_render_var__ 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__ \ # # 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 the config variable named by the spec's # source_name (HEIGHT/MAXPREVIEWS/ORIGINAL_BASEPATH/SOURCE_URL/STATS_PAGE/ # THUMBHEIGHT/TITLE), then HTML-escapes it. The value is fetched by indirect # expansion of source_name -- ${!source_name-} -- so this handler stays fully # data-driven and never has to be edited when a new config render-var is added # (Open/Closed): the spec's source_name is the single source of truth. # # The "-" default keeps the read unset-safe under set -u and makes an unset # config var render as empty. That is byte-identical to the previous per-name # case for every reachable state: # - apply_config_defaults (action.source.sh) always runs before any render, # so HEIGHT/ORIGINAL_BASEPATH/SOURCE_URL/STATS_PAGE/TITLE are always set -- # the old "degrade if somehow unset" fallbacks (e.g. STATS_PAGE :-no) were # defensive and unreachable, so dropping them changes no produced output. # - MAXPREVIEWS/THUMBHEIGHT are not defaulted (refresh-only configs omit the # generation sizing fields); an unset value renders as empty, exactly as the # old ${MAXPREVIEWS:-}/${THUMBHEIGHT:-} arms did. # Indirect expansion also sidesteps the SC2153 false positive the old literal # $TITLE arm needed a :- default to silence (a lowercase "title" local in # another --check-sourced module made shellcheck suspect a misspelling). # 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 -r context_value="${!source_name-}" 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" # Guard the arithmetic: only a non-negative integer is safe to feed to # $(( )). An empty or non-numeric context value (e.g. a stray string) would # otherwise throw a bash arithmetic syntax error and, under set -e, abort the # whole script. Treat any invalid/missing preview_num as "no neighbour" and # emit an empty render value, matching the missing-value behaviour. if [[ "$context_value" =~ ^[0-9]+$ ]]; 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" # Guard the arithmetic: only a non-negative integer is safe to feed to # $(( )). An empty or non-numeric context value would otherwise throw a bash # arithmetic syntax error and, under set -e, abort the whole script. Treat # any invalid/missing preview_num as "no neighbour" and emit an empty render # value, mirroring the next-html handler. if [[ "$context_value" =~ ^[0-9]+$ ]]; 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__) 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. # # Subsetting (task kn0): only the render_vars the target template_name actually # references are computed; the rest are skipped entirely. The needed set is built # from the required_templates (5th) spec field via template_needed_render_vars_to # and is the FULL dependency closure -- handlers read from the input context # array or config globals, never from another computed render_var, so there are # no render-var -> render-var deps to expand transitively. Every handler is a # pure value computation with no externally-relied-upon side effect (no RANDOM / # seed consumption; current_date_html only primes the deterministic # SHURIKEN_CURRENT_DATE_TEXT cache, which any later header render re-primes), so # skipping non-needed fields cannot change any other field's value or the output. prepare_template_render_vars() { local -r render_vars_name="$1"; shift local -r context_name="$1"; shift local -r template_name="$1"; shift # shellcheck disable=SC2178 local -n render_vars_ref="$render_vars_name" 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 local -A needed_render_vars=() template_needed_render_vars_to needed_render_vars "$template_name" render_vars_ref=() for field_spec in "${TEMPLATE_RENDER_FIELD_SPECS[@]}"; do IFS='|' read -r render_var kind source_name \ _required_context_var _required_templates <<< "$field_spec" # Skip fields this template does not reference -- not computed, not # serialized into BASH_ENV (serialize_template_render_context emits only # the keys present here). if [ -z "${needed_render_vars[$render_var]+x}" ]; then continue fi 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 } validate_template_render_request() { local -r template_name="$1"; shift local -r context_name="$1"; shift local -r template_path="$TEMPLATE_DIR/$template_name.tmpl" local -i status=0 if [ ! -r "$template_path" ]; then config_error "template file $template_path must be readable" return 1 fi validate_template_context "$template_name" "$context_name" status=$? if (( status != 0 )); then return "$status" fi } render_template() { local -r template_name="$1"; shift local -r html="$1"; shift local -r context_name="$1"; shift local -r template_path="$TEMPLATE_DIR/$template_name.tmpl" local dist_html local html_dir local -i status=0 # Passed by name to prepare_template_render_vars and source_template_file. # shellcheck disable=SC2034 local -A render_vars=() html_dir=$(template_context_value "$context_name" html_dir) dist_html="$DIST_DIR/$html_dir" log_info \ "Rendering $template_name template into $(_display_path "$dist_html")/$html" mkdir -p "$dist_html" status=$? if (( status != 0 )); then return "$status" fi prepare_template_render_vars render_vars "$context_name" "$template_name" status=$? if (( status != 0 )); then return "$status" fi source_template_file "$template_path" "$dist_html/$html" render_vars status=$? if (( status != 0 )); then return "$status" fi } template() { local -r template_name="$1"; shift local -r html="$1"; shift local -i status=0 # shellcheck disable=SC2034 local -A render_context=() parse_template_context "$template_name" render_context "$@" status=$? if (( status != 0 )); then return "$status" fi validate_template_render_request "$template_name" render_context status=$? if (( status != 0 )); then return "$status" fi render_template "$template_name" "$html" render_context status=$? if (( status != 0 )); then return "$status" fi }