wyrd/tools
esc

    ↑↓ Move ↵ Open toha results first Search by Pagefind
    Releases

    Jinja and values

    Toha uses MiniJinja to render text and evaluate expressions. MiniJinja follows the Jinja template language. This page explains the values and additions available inside a Toha template. The linked language guides cover loops, conditions, filters, and other general Jinja syntax.

    Where values come from

    A template can define static values under data:

    data:
      category: personal
      priorities: [ low, normal, high ]

    Each top-level key is a Jinja variable. Interview question answers and computed values also become variables. For example, after asking a question with id: title, a generated file can contain:

    # {{ title }}
    Category: {{ category }}

    If the answer to title is Weekly notes, the first line becomes # Weekly notes. category comes from data.

    Top-level data keys, question ids, and computed ids share one namespace. They must each match the same identifier pattern: start with a lowercase letter or underscore, followed by lowercase letters, digits, or underscores. No two can have the same name. A name introduced in the interview is available to later nodes and to file rendering.

    Text templates and typed expressions

    Use {{ ... }} when a field or file needs text. Toha renders string fields, file contents, and each file path segment as text:

    prompt: "File name for {{ title }}"
    path: "notes/{{ title | kebab }}.txt"

    Some fields need a boolean, number, or list instead of text. For those fields, you can supply a literal value or a quoted Jinja expression that returns the right type:

    required: true
    when: include_summary
    options: "priorities | sort"

    when, computed, and format are always expressions. Write the expression directly, without {{ ... }}. For example, when: include_summary uses a boolean value, while prompt: "Summary for {{ title }}" renders text.

    Case filters

    Toha adds six case filters. The input Weekly Notes produces these results:

    FilterResult
    kebabweekly-notes
    snakeweekly_notes
    camelweeklyNotes
    pascalWeeklyNotes
    constantWEEKLY_NOTES
    titleWeekly Notes

    Use them in text templates, expressions, or generated file names. For example, {{ title | kebab }}.txt produces weekly-notes.txt.

    JSON and YAML output

    tojson serializes a value to compact JSON. Pass an indent when you want formatted JSON. toyaml serializes a value as YAML without a leading --- or a final document newline, so you can place it inside a larger file.

    JSON: {{ priorities | tojson }}
    YAML:
    {{ priorities | toyaml | indent(2) }}

    Object keys appear in sorted order. If a value itself ends with a newline, toyaml preserves that newline.

    The interview time

    now() returns the instant captured when the interview started. Use dateformat to format it in that instant’s time zone:

    Created: {{ now() | dateformat }}
    Year: {{ now() | dateformat('%Y') }}

    Without an argument, dateformat uses %Y-%m-%d. Its format string uses strftime syntax. The same interview instant is used throughout one run, so several calls to now() agree.

    Toha context values

    Toha supplies seventeen reserved variables before the first question. Each name begins with toha_ and is present at every render surface — questions, computed values, messages, hooks, file paths, and file bodies. An unavailable value is Jinja none (never undefined). These names are reserved: a data key, question id, computed id, or each binding may not reuse one, but other toha_ names remain free.

    NameTypeValue
    toha_target_namestring or noneFinal component of the target directory; none for a filesystem root or non-Unicode name.
    toha_template_namestringShort name from the selected template.yml.
    toha_template_formal_namestringStable formal name of the selection, never an alias or short spelling.
    toha_template_aliaseslist of stringsEffective aliases of a selected named template in registry order; [] for a folder, Git address, or bundled selection.
    toha_template_sourcestring or noneSource of the selected named entry; none when the selection has no registry entry.
    toha_host_osstringLowercase OS name from Rust’s target vocabulary, such as linux.
    toha_host_archstringArchitecture from Rust’s target vocabulary, such as x86_64.
    toha_host_os_namestring or noneLinux /etc/os-release NAME; none elsewhere or when unavailable.
    toha_host_os_idstring or noneLinux /etc/os-release ID; none elsewhere or when unavailable.
    toha_host_os_id_likelist of stringsLinux /etc/os-release ID_LIKE, split on whitespace; [] elsewhere or when unavailable.
    toha_is_adminbooleanWhether the run has effective administrative authority.
    toha_is_interactivebooleanWhether the originating driver could prompt a human.
    toha_env_userstring or noneCaptured user name.
    toha_env_hostnamestring or noneCaptured host name.
    toha_env_editorstring or noneCaptured EDITOR.
    toha_env_shellstring or noneCaptured SHELL.
    toha_env_visualstring or noneCaptured VISUAL.
    {% if toha_is_interactive %}
    Preparing {{ toha_template_name }} for {{ toha_target_name }}.
    {% endif %}
    {% if toha_env_editor is not none %}
    editor = {{ toha_env_editor }}
    {% endif %}

    The trusted environment values

    The five toha_env_ values — user, host name, editor, shell, and visual — are the only values read from the environment, and only under trust. Before it asks a question, stage analyzes every Jinja surface. If any of them can be referenced, stage records nothing and refuses until you re-run with --trust:

    # No reference and no flag: the values are recorded unavailable (none).
    toha stage sample ./output --async
    
    # An explicit grant records all five once, even before a reference exists.
    toha stage sample ./output --async --trust
    
    # Later batches reuse the recorded decision and values; continue has no flag.
    toha continue ./output answers.json

    apply with a template grants the same access through an explicit --trust or a current registry approval that matches the template’s reviewed hooks. continue never re-reads the environment: it replays the recorded snapshot, so changing EDITOR or the host between batches does not change a value. A template folder edited after staging renders its current bytes under the recorded decision: with no grant a newly added toha_env_ reference is none; with a grant it reads the recorded value.

    A staged interview created before these values existed keeps the earlier behavior: none of the seventeen names are injected, reserved, or shown by debug().

    Hook results

    A hook with an id adds a variable — its result — to a narrow set of later surfaces only: a later top-level hook’s when, run[1..], args, or cwd, and messages.after-apply. Unlike answers and context values, a hook result is not available to questions, files, file paths, before-apply, or an interview hook, because it does not exist until the hook runs after files are written.

    Its value is the object {exit_code, stdout, stderr}, read as lint.exit_code, version.stdout, and so on. Each field is none until the hook runs; a producer that never ran leaves all three none, so version.exit_code is none tests whether it ran. A stream is present only when the hook declares it in capture.

    With parse: json, the id is the parsed value itself — pkg.name, pkg[0], a scalar, or none for JSON null — and the execution metadata moves to the status-id, which holds exit_code, stdout, stderr, and parsed. See Hooks and messages for the full rules and examples.

    Supported Jinja features

    Toha includes MiniJinja’s built-in filters, tests, and functions, the tojson filter, macros ({% macro %} and {% call %}), and loop controls ({% break %} and {% continue %}). A macro is visible only in the value that defines it. {% include %} is available in rendered file bodies only, described under Partials below. Jinja import, from ... import, and extends are unavailable everywhere.

    Partials

    A rendered file body can pull in another file inside the template root with {% include %}:

    {% include "partials/frontmatter.md" %}
    # {{ title }}

    The included file is a partial. Includes work in rendered file bodies only — source-tree files and files: rule sources. They do nothing in prompts, defaults, when/computed/format/options expressions, target path segments, apply messages, or hook fields; a {% include %} written on one of those surfaces is reported as unavailable outside file bodies.

    The rules an author needs:

    • The name is a /-separated path relative to the template root. It can name a root-level file ("notice.txt"), a file in any nested directory ("shared/legal/license.inc"), or a file with any extension. There is no required partials/ prefix or directory allowlist; partials/ is only a convention. The name is the same string in every file that includes it; it is not resolved relative to the including file.
    • A partial sees the same variables — answers, data, computed values, and the toha_ context values — as the file that includes it. with context and without context are accepted but change nothing.
    • A partial placed outside the source subdirectory is pulled in but never emitted as its own output file. A partial inside the source subdirectory is emitted like any other source file unless an ignore glob excludes it.
    • The target is a string literal or an ordered literal list of strings. Computed names, and lists holding a computed or non-string item, are rejected. A list selects the first candidate that exists, in author order; an earlier missing name is a fallback. If no candidate exists the include fails, unless ignore missing is present, which renders empty text. An empty list also renders empty text. ignore missing suppresses only the all-not-found result; an escape, symlink, unreadable, non-UTF-8, syntax, nested-include, or cycle error still fails.
    • Includes are confined to the template root. Absolute names, .. traversal, backslash separators, and symlinked partials are refused. A partial may itself include others; a cycle is reported by name.
    • Includes resolve when the template loads, so a missing partial, an escape, or a variable reached only through a partial is reported before the interview begins.

    template.yml and every document loaded through its YAML !include tag receive no Jinja include processing: they are configuration, parsed as data, never a render surface. A file’s role — body or configuration — follows how it is referenced, not its extension, so a generated .yml or .yaml file in the source tree (or named as a files: source:) is a file body and does get includes. Naming a configuration document as a partial target simply inlines its literal bytes; it does not parse or process it. Toha’s YAML !include tag is a separate feature for composing configuration data and is unchanged.