# subst A markdown-aware `{{ dotted.path | filter(args) }}` value-substitution engine. [mnw-assumptions](../mnw-assumptions) is its only consumer today. The engine is domain-free: it knows nothing about where values come from. Callers populate a table and register whatever filters they need. ```rust use subst::{Substituter, Value}; let s = Substituter::new() .with_value("price.basic", Value::Int(16)) .with_value("stripe.percent", Value::Float(0.029)); let out = s.substitute("Basic is ${{ price.basic }} ({{ stripe.percent | percent }} fee).")?; assert_eq!(out, "Basic is $16 (2.9% fee)."); ``` ## Markers A marker is `{{ path (| filter (args)?)* }}`. The path is a dotted key looked up verbatim in the table; filters run left-to-right, each taking the previous filter's output. Arguments are integer, float, or quoted string literals (single or double): `{{ x | round(2) | money("$") }}`. Markers inside inline code spans and fenced code blocks are **left verbatim**, so documentation showing literal template syntax survives untouched. That is the whole reason the pass is markdown-aware; everything else about it is plain text substitution. ## Values and filters `Value` is `Int(i64) | Float(f64) | String(String)`. Built-in filters: | Filter | Effect | |--------|--------| | `int` | Truncate toward zero, render as an integer | | `ceil` / `floor` / `round(n?)` | Round up / down / to `n` decimal places (0..=10, default 0) | | `money(symbol?)` | Two decimals behind a symbol, `$` unless given: `0.3` becomes `$0.30` | | `percent(n?)` | Multiply by 100, `n` decimals (default 1), trailing `%`: `0.029` becomes `2.9%` | | `upper` / `lower` | ASCII case conversion | Register a custom filter with `with_filter(name, f)`. `Filter` is a single-method trait (`apply(Value, &[FilterArg]) -> Result`) with a blanket impl over `Fn`, so a closure works without writing a struct. A custom filter overrides a built-in of the same name. ## Errors `substitute` returns `SubstError::Unresolved(Vec)` listing every path that was missing from the table, or every filter that failed, formatted for a log line. Unresolved markers are left in place in the returned string, so a caller can grep the output rather than diff it against the input to find them. ## Dependencies `regex-lite`, and nothing else. The `code_span_ranges` helper is duplicated from docengine rather than shared. Depending on docengine here would invert the separation this crate exists to keep. ## License MIT.