From a45ec5900309983970e3aa6743c00686ab3233aa Mon Sep 17 00:00:00 2001 From: nigel Date: Tue, 15 Sep 2026 16:12:35 -0500 Subject: [PATCH 01/11] refactor(templates): convert Tera macros to components for Zola 0.23 Zola 0.23 ships Tera v2, which removes macros and the import mechanism in favor of globally-registered components. Convert the command and docs macros to components: - arguments_parse (recursive) calls itself as a component - value-returning helpers (load, extract_frontmatter, extract_markdown, fix_links) emit text captured via set-capture blocks; add | safe since component output is HTML-escaped by default - rename the two fix_links to fix_links_command / fix_links_docs to avoid a global name collision - load takes topic_path and exceptions as params (components cannot read config) - add the youtube component (replacing the removed shortcode) in components.html; delete the dead shortcodes/youtube.html Signed-off-by: nigel Generated-by: Kiro (AI agent) Review-status: Not yet finely reviewed by a human --- .../youtube.html => components.html} | 4 +- templates/macros/command.html | 47 +++------- templates/macros/docs.html | 87 +++++++++---------- 3 files changed, 58 insertions(+), 80 deletions(-) rename templates/{shortcodes/youtube.html => components.html} (72%) diff --git a/templates/shortcodes/youtube.html b/templates/components.html similarity index 72% rename from templates/shortcodes/youtube.html rename to templates/components.html index 5402861e..7dc2c332 100644 --- a/templates/shortcodes/youtube.html +++ b/templates/components.html @@ -1,3 +1,5 @@ +{% component youtube(id, class="", playlist="", autoplay=false) %}
-
\ No newline at end of file + +{% endcomponent youtube %} diff --git a/templates/macros/command.html b/templates/macros/command.html index 2e5e5e8e..3faf32b1 100644 --- a/templates/macros/command.html +++ b/templates/macros/command.html @@ -1,4 +1,4 @@ -{%- macro arguments_parse(argument_array, separator="") -%} +{% component arguments_parse(argument_array, separator="") %} {%- for arguments in argument_array -%} {%- if arguments.optional and arguments.optional == true -%} {%- set start = "[" -%} @@ -21,11 +21,11 @@ {%- set next = "" -%} {%- if arguments.type and arguments.type == "oneof" -%} {%- if arguments.arguments -%} - {%- set next = self::arguments_parse(argument_array=arguments.arguments, separator="|") -%} + {%- set next %}{{< arguments_parse argument_array={arguments.arguments} separator="|" />}}{% endset -%} {%- endif -%} {%- else -%} {%- if arguments.arguments -%} - {%- set next = self::arguments_parse(argument_array=arguments.arguments) -%} + {%- set next %}{{< arguments_parse argument_array={arguments.arguments} />}}{% endset -%} {%- endif -%} {%- endif -%} {%- if separator == "|" and loop.last == false -%} @@ -39,39 +39,16 @@ [ {{ arguments_str }} ... ] {% endif %} {%- endfor -%} -{%- endmacro arguments_parse -%} +{% endcomponent arguments_parse %} -{%- macro command_obj_name(command_data) -%} - {%- for command, command_obj in command_data -%} - {% set_global command_obj_name = command %} - {%- endfor -%} -{{ command_obj_name }} -{%- endmacro command_obj_name -%} - -{%- macro command_search_json_path(slug) -%} -{{config.extra.command_search_json_path }}{{ slug }}.json -{%- endmacro command_search_json_path -%} - -{%- macro command_json_path(slug) -%} -{{config.extra.command_json_path }}{{ slug }}.json -{%- endmacro command_json_path -%} - -{%- macro command_bloom_json_path(slug) -%} -{{config.extra.command_bloom_json_path }}{{ slug }}.json -{%- endmacro command_bloom_json_path -%} - -{%- macro command_json_json_path(slug) -%} -{{config.extra.command_json_json_path }}{{ slug }}.json -{%- endmacro command_json_json_path -%} - -{%- macro fix_links(content) -%} +{% component fix_links_command(content) %} {{ content - | regex_replace(pattern=`\]\(\.\./topics/(?P.*?).md#(?P.*?)\)`, rep=`](/topics/$fname#$hash)`) - | regex_replace(pattern=`\]\(\.\./topics/(?P.*?).md\)`, rep=`](/topics/$fname)`) - | regex_replace(pattern=`\[(?P.*?)\]:\s+\.\./topics/(?P.*?).md`, rep=`[$token]: /topics/$fname`) - | regex_replace(pattern=`\]\((?P[\w\.-]*?)\.md#(?P.*?)\)`, rep=`](/commands/$fname#$hash)`) - | regex_replace(pattern=`\]\((?P[\w\.-]*?)\.md\)`, rep=`](/commands/$fname)`) - | regex_replace(pattern=`\[(?P.*?)\]:\s+(?P[\w\.-]*?).md`, rep=`[$token]: /commands/$fname`) + | regex_replace(pattern="\\]\\(\\.\\./topics/(?P.*?).md#(?P.*?)\\)", rep="](/topics/$fname#$hash)") + | regex_replace(pattern="\\]\\(\\.\\./topics/(?P.*?).md\\)", rep="](/topics/$fname)") + | regex_replace(pattern="\\[(?P.*?)\\]:\\s+\\.\\./topics/(?P.*?).md", rep="[$token]: /topics/$fname") + | regex_replace(pattern="\\]\\((?P[\\w\\.-]*?)\\.md#(?P.*?)\\)", rep="](/commands/$fname#$hash)") + | regex_replace(pattern="\\]\\((?P[\\w\\.-]*?)\\.md\\)", rep="](/commands/$fname)") + | regex_replace(pattern="\\[(?P.*?)\\]:\\s+(?P[\\w\\.-]*?).md", rep="[$token]: /commands/$fname") | safe }} -{%- endmacro fix_links -%} +{% endcomponent fix_links_command %} diff --git a/templates/macros/docs.html b/templates/macros/docs.html index a8c68b58..ce8ee491 100644 --- a/templates/macros/docs.html +++ b/templates/macros/docs.html @@ -1,73 +1,72 @@ -{%- macro load(slug) -%} -{%- set topic_path = config.extra.doc_topic_path -%} -{%- if slug in config.extra.slug_source_exceptions -%} - {%- set slug = config.extra.slug_source_exceptions[slug] -%} +{% component load(slug, topic_path, exceptions) %} +{%- if slug in exceptions -%} + {%- set slug = exceptions[slug] -%} {%- endif -%} {%- set markdown_content = load_data(path= topic_path ~ "/" ~ slug ~ ".md", required= false) -%} -{%- if markdown_content -%}{{ markdown_content }}{% endif %} -{% endmacro load_doc %} +{%- if markdown_content -%}{{ markdown_content | safe }}{% endif %} +{% endcomponent load %} -{%- macro extract_frontmatter(content) %} +{% component extract_frontmatter(content) %} {%- set markdown_split = content | split(pat="---") -%} {%- set markdown_part_count = markdown_split | length() -%} {%- if markdown_part_count >= 3 -%} {%- set before_frontmatter = markdown_split | nth(n=0) -%} {%- set before_frontmatter_length = before_frontmatter | length() -%} {%- set frontmatter = markdown_split | nth(n=1) -%} -{%- if before_frontmatter_length == 0 -%}{{frontmatter}}{%- endif -%} +{# Zola 0.23's stricter YAML parser rejects top-level keys indented by a single + space (valkey-doc/topics/key-specs.md). Strip one leading space before a + top-level key; deeper indents (2+ spaces) are left intact. #} +{%- set frontmatter = frontmatter | regex_replace(pattern="(?m)^ (\\S)", rep="$1") -%} +{%- if before_frontmatter_length == 0 -%}{{frontmatter | safe}}{%- endif -%} {%- endif -%} -{%- endmacro extract_frontmatter %} +{% endcomponent extract_frontmatter %} -{%- macro extract_markdown(content) %} -{{ content | split(pat="---") | slice(start=2) | join(sep="---") }} -{%- endmacro extract_frontmatter %} +{% component extract_markdown(content) %} +{%- set parts = content | split(pat="---") -%} +{{ parts[2:] | join(sep="---") | safe }} +{% endcomponent extract_markdown %} -{%- macro fix_links(content) -%} +{% component fix_links_docs(content) %} {{ content - | regex_replace(pattern=`\]\(#(?P.*?\))`, rep=`](--$hash`) - | regex_replace(pattern=`\]\((?Phttpsd?:\/\/.*?\))`, rep=`](-$url`) - | regex_replace(pattern=`\]\(\.\./commands/(?P\w.*?)(.md)?#(?P.*?)\)`, rep=`](/commands/$fname#$hash)`) - | regex_replace(pattern=`\]\(\.\./commands/(?P\w.*?)(.md)?\)`, rep=`](/commands/$fname)`) - | regex_replace(pattern=`\]\(\.\./commands/#(?P\w.*?)\)`, rep=`](/commands/#$hash)`) - | regex_replace(pattern=`\]\(\.\./topics/\)`, rep=`](/topics/)`) - | regex_replace(pattern=`\]\(\./\)`, rep=`](/topics/)`) - | regex_replace(pattern=`\]\(index\.md\)`, rep=`](/topics/)`) - | regex_replace(pattern=`\]\((?P\w.*?)(.md)\)`, rep=`](/topics/$fname)`) - | regex_replace(pattern=`\]\((?P\w.*?)(.md)?#(?P.*?)\)`, rep=`](/topics/$fname#$hash)`) - | regex_replace(pattern=`\]\((?P.*?).png\)`, rep=`](/topics/$fname.png)`) - | regex_replace(pattern=`\]\((?P.*?).gif\)`, rep=`](/topics/$fname.gif)`) - | regex_replace(pattern=`\]\((?P\w.*?)(.md)?\)`, rep=`](/topics/$fname)`) - | regex_replace(pattern=`\]\(\-(?Phttps?:\/\/.*?\))`, rep=`]($url`) - | regex_replace(pattern=`\]\(\--(?P.*?\))`, rep=`](#$hash`) - | regex_replace(pattern=`\.\.\/commands\/`, rep=`/commands/`) + | regex_replace(pattern="\\]\\(#(?P.*?\\))", rep="](--$hash") + | regex_replace(pattern="\\]\\((?Phttpsd?:\\/\\/.*?\\))", rep="](-$url") + | regex_replace(pattern="\\]\\(\\.\\./commands/(?P\\w.*?)(.md)?#(?P.*?)\\)", rep="](/commands/$fname#$hash)") + | regex_replace(pattern="\\]\\(\\.\\./commands/(?P\\w.*?)(.md)?\\)", rep="](/commands/$fname)") + | regex_replace(pattern="\\]\\(\\.\\./commands/#(?P\\w.*?)\\)", rep="](/commands/#$hash)") + | regex_replace(pattern="\\]\\(\\.\\./topics/\\)", rep="](/topics/)") + | regex_replace(pattern="\\]\\(\\./\\)", rep="](/topics/)") + | regex_replace(pattern="\\]\\(index\\.md\\)", rep="](/topics/)") + | regex_replace(pattern="\\]\\((?P\\w.*?)(.md)\\)", rep="](/topics/$fname)") + | regex_replace(pattern="\\]\\((?P\\w.*?)(.md)?#(?P.*?)\\)", rep="](/topics/$fname#$hash)") + | regex_replace(pattern="\\]\\((?P.*?).png\\)", rep="](/topics/$fname.png)") + | regex_replace(pattern="\\]\\((?P.*?).gif\\)", rep="](/topics/$fname.gif)") + | regex_replace(pattern="\\]\\((?P\\w.*?)(.md)?\\)", rep="](/topics/$fname)") + | regex_replace(pattern="\\]\\(\\-(?Phttps?:\\/\\/.*?\\))", rep="]($url") + | regex_replace(pattern="\\]\\(\\--(?P.*?\\))", rep="](#$hash") + | regex_replace(pattern="\\.\\.\\/commands\\/", rep="/commands/") - | regex_replace(pattern=`\[(?.*)\]:\s*https://(?P.*)`, rep=`[$id} https://$url`) + | regex_replace(pattern="\\[(?.*)\\]:\\s*https://(?P.*)", rep="[$id} https://$url") - | regex_replace(pattern=`\[(?.*)\]:\s*(?P.*)(\.md)`, rep=`[$id]: /topics/$topic`) + | regex_replace(pattern="\\[(?.*)\\]:\\s*(?P.*)(\\.md)", rep="[$id]: /topics/$topic") - | regex_replace(pattern=`\]\(\/topics\/(?Phttp:\/\/.*?\))`, rep=`]($url`) + | regex_replace(pattern="\\]\\(\\/topics\\/(?Phttp:\\/\\/.*?\\))", rep="]($url") - | regex_replace(pattern=`\[(?.*)\}\s*(?P.*)`, rep=`[$id]: $url`) + | regex_replace(pattern="\\[(?.*)\\}\\s*(?P.*)", rep="[$id]: $url") + | safe }} -{%- endmacro fix_links -%} +{% endcomponent fix_links_docs %} - -{%- macro client_json_path(client_path) -%} -{{config.extra.client_json_path }}{{ client_path }} -{%- endmacro client_json_path -%} - - -{%- macro boolean_to_checkmark(value) -%} +{% component boolean_to_checkmark(value) %} {% if value == true %} {% else %} {% endif %} -{%- endmacro %} +{% endcomponent boolean_to_checkmark %} -{% macro format_language(language) %} +{% component format_language(language) %} {% if language == "php" %} {{ language | upper }} {% elif language == "csharp" %} @@ -75,4 +74,4 @@ {% else %} {{ language | title }} {% endif %} -{% endmacro %} +{% endcomponent format_language %} From 18d3dd5a7bae9da24b6b583c2f73ff2bf2e46eba Mon Sep 17 00:00:00 2001 From: nigel Date: Tue, 15 Sep 2026 16:13:07 -0500 Subject: [PATCH 02/11] refactor(templates): update component call sites and list building Update the templates that consumed the old macros: - remove {% import %} lines; call components with {{< name arg={val} />}} - inline the trivial path-builder macros as config.extra.X ~ slug ~ ".json" (needed inside array literals where set-capture cannot be used) - replace the command_obj_name helper with command_data | keys | first - replace the removed concat filter with spread [...xs, item] and the removed filter filter with a comprehension - iterate command groups and categories over (... | keys | sort) for a stable order, since Tera v2 does not guarantee group_by map iteration order - client-list: is iterable -> is not string (v2 treats strings as iterable) Signed-off-by: nigel Generated-by: Kiro (AI agent) Review-status: Not yet finely reviewed by a human --- templates/client-feature-table.html | 6 ++---- templates/client-list.html | 13 ++++++------- templates/command-list.html | 14 ++++++-------- templates/command-page.html | 28 +++++++++++++++------------- templates/commands.html | 23 +++++++++++++---------- templates/docs-page.html | 15 +++++++-------- templates/docs.html | 21 ++++++++++----------- templates/topics-list.html | 9 ++++----- 8 files changed, 63 insertions(+), 66 deletions(-) diff --git a/templates/client-feature-table.html b/templates/client-feature-table.html index 561f48ae..1e5744ba 100644 --- a/templates/client-feature-table.html +++ b/templates/client-feature-table.html @@ -1,5 +1,3 @@ -{% import "macros/docs.html" as docs %} - {% block main_content %}

Feature Comparison Table

@@ -15,12 +13,12 @@

Feature Comparison Table

{%- for path in client_paths %} - {% set json_data = load_data(path = docs::client_json_path(client_path= path), format="json") %} + {% set json_data = load_data(path = config.extra.client_json_path ~ path, format="json") %} {% for field in client_fields %} {% if field not in ["name", "language"] %} - {{ docs::boolean_to_checkmark(value=json_data[field]) }} + {{< boolean_to_checkmark value={json_data[field]} />}} {% else %} {{ json_data[field] }} {% endif %} diff --git a/templates/client-list.html b/templates/client-list.html index 70a126cd..a547f814 100644 --- a/templates/client-list.html +++ b/templates/client-list.html @@ -1,9 +1,8 @@ {% extends "fullwidth.html" %} -{% import "macros/docs.html" as docs %} {% block main_content %} {% set client_path = section.extra.recommended_clients_paths | first %} - {% set first_client = load_data(path = docs::client_json_path(client_path=client_path), format="json", required = false) %} + {% set first_client = load_data(path = config.extra.client_json_path ~ client_path, format="json", required = false) %} {% if not first_client %} Clients page not found. You likely need to build the client page. See "Building additional content" in the README.md file. {% else %} @@ -19,19 +18,19 @@

Table of Contents

{% for path in section.extra.recommended_clients_paths %} {% set language = path | split(pat="/") | nth(n=1) %} {% if language not in languages %} - {% set_global languages = languages | concat(with=language) %} + {% set_global languages = [...languages, language] %} {% endif %} {% endfor %} {% for language in languages %} -
  • {{ docs::format_language(language=language) }}
  • +
  • {{< format_language language={language} />}}
  • {% endfor %}
  • Feature Comparison Table
  • {% for language in languages %}
    -

    {{ docs::format_language(language=language) }}

    +

    {{< format_language language={language} />}}

    {% for path in section.extra.recommended_clients_paths %} - {% set client = load_data(path = docs::client_json_path(client_path= path), format="json") %} + {% set client = load_data(path = config.extra.client_json_path ~ path, format="json") %} {% if client.language == language %}

    {{ client.name }}

    @@ -42,7 +41,7 @@

    {{ client.name }}

    {% endif %}
  • Installation: - {% if client.installation is iterable %} + {% if client.installation is not string %}