Use Jinja safely

Updated Aug 05, 2026

Jinja expressions are allowed in renderable string fields, including variables, target fields, and rule recipes. Structural Jinja blocks cannot reshape the YAML document. Use the dedicated foreach and when keys for manifest-time expansion.

Generate targets with `foreach` and `when`

The next complete manifest creates two targets and excludes the disabled item:

netsuke_version: "1.0.0"

vars:
  reports:
    - daily
    - weekly
    - disabled

targets:
  - foreach: reports
    when: item != 'disabled'
    name: "{{ item }}.txt"
    command: "echo {{ index }} > {{ outs }}"

defaults:
  - daily.txt
  - weekly.txt

Each expansion receives item and a zero-based index. Target-local variables take precedence over global variables, and iteration values take precedence over both.

when is evaluated while Netsuke loads the manifest. It does not create a runtime branch. Runtime decisions belong in a command or script.

Top-level actions support the same foreach and when keys.

Discover files with `glob`

glob(pattern) expands a shell-style pattern to the sorted list of matching files while Netsuke loads the manifest, so the results become part of the static build graph. It pairs naturally with foreach to generate one target per matched file; a target list such as foreach: glob('src/*.c') produces one expansion per matching source.

Matching is case-sensitive. * and ? do not cross directory separators; use ** to descend into subdirectories. Directories are excluded, so only files are returned. The quick-start guide shows a complete runnable example.

Define reusable macros

Macros return rendered text and can accept default arguments:

netsuke_version: "1.0.0"

vars:
  greeting: Hello

macros:
  - signature: "say(name, punctuation='!')"
    body: |
      {{ greeting }}, {{ name }}{{ punctuation }}

targets:
  - name: greeting.txt
    command: "echo '{{ say('Netsuke') }}' > {{ outs }}"

defaults:
  - greeting.txt

Select optional tools

which(name, **kwargs) returns an executable path and fails when the command is absent. The same helper is also available as a filter.

command_available(name, **kwargs) returns a boolean and is better for complementary branches:

netsuke_version: "1.0.0"

actions:
  - name: test-fast
    command: "cargo nextest run"
    when: command_available("cargo-nextest")

  - name: test-fast
    command: "cargo test"
    when: not command_available("cargo-nextest")

targets: []

defaults:
  - test-fast

Both helpers accept:

  • all=true: return all which matches. It does not change the boolean result from command_available.
  • canonical=true: canonicalize matching paths.
  • fresh=true: bypass the resolver cache for this lookup.
  • cwd_mode="auto"|"always"|"never": control bounded project-directory fallback searching.

The env(name) function reads one required environment variable. v0.1.0-beta1 does not accept a default argument; an absent or non-Unicode value is an error.

Inject the environment reader for tests and embedding

env() does not read std::env::var directly. Manifest parsing goes through an injectable EnvReader seam, so callers that need deterministic env() results — test suites and programs embedding Netsuke as a library — can supply their own reader instead of mutating the process environment.

  • netsuke::manifest::from_str parses a manifest using the live process environment.
  • netsuke::manifest::from_str_with_env takes an explicit EnvReader, letting the caller control every value env() returns.
  • netsuke::manifest::process_env_reader builds the process-backed reader that from_str uses by default.

A missing variable still fails the parse with a Jinja "undefined" error, and a non-Unicode value still fails with an "invalid operation" error; only the source of the values changes.

use netsuke::manifest::{EnvReader, from_str_with_env};
use std::sync::Arc;

let reader: EnvReader = Arc::new(|_| Ok(String::from("release")));
let yaml = concat!(
    "netsuke_version: \"1.0.0\"\n",
    "targets:\n",
    "  - name: \"{{ env('PROFILE') }}\"\n",
    "    command: echo hi\n",
);
let manifest = from_str_with_env(yaml, &reader).expect("parse");
assert!(format!("{:?}", manifest.targets[0].name).contains("release"));

This snippet mirrors the executable doctest on from_str_with_env in the API documentation, rather than the YAML-only examples elsewhere in this guide.