lithos-gotmpl-engine 0.1.0

Go text/template-inspired parser and evaluator used by the Lithos templating stack.
Documentation
# lithos-gotmpl-rs

A Rust implementation for go template and related libraries. The core surface is split into:

- `lithos-gotmpl-engine`: hand-rolled lexer/parser/runtime that evaluates Go
  text/template syntax. Apps inject function registries when building templates.
- `lithos-gotmpl-core`: default helper pack that mirrors the stock Go
  text/template built-ins (e.g. `eq`, `printf`, `index`, `default`).
- `lithos-sprig`: a Rust-friendly slice of the [`sprig`]https://github.com/Masterminds/sprig
  function library so [`gitmpl`]https://github.com/hans-d/gittmpl and similar engines can
  avoid cgo while staying behaviourally aligned with upstream Sprig.

## Project layout

- `crates/lithos-gotmpl-engine/src/` – core parser/evaluator. `Template` instances accept a
  `FunctionRegistry` and execute pipelines by delegating to registered helpers. Action nodes
  expose parsed `Pipeline`/`Command` structures, so constructs like `{{ .name | default "foo" | upper }}`
  surface as three distinct commands in the AST (field lookup, `default`, then `upper`). Branching
  forms such as `if`/`range`/`with` materialise as dedicated AST nodes with nested blocks, matching
  the shape of Go's `parse` package. The engine honours Go-style whitespace trim markers (`{{-` / `-}}`)
  and pipeline variable bindings like `{{$val := .item}}` with lexical scoping across control structures.
- `crates/lithos-gotmpl-core/src/` – base helper bundle that registers the canonical
  Go text/template built-ins atop the engine.
- `crates/lithos-sprig/src/` – sprig-style helpers layered on top of the engine/core crates.
- `test-cases/lithos-sprig.json` – shared test vectors describing calls that mirror
  Go sprig behaviour. Each entry captures both function arguments and a template snippet with
  its expected rendering.
- `test-cases/lithos-gotmpl-core.json` – vectors harvested from Go's
  `text/template` tests to validate our built-in helper semantics.
- `go-sanity/` – shared Go runner used to sanity-check test cases against the real Go implementation.
- `crates/lithos-sprig/tests/compat.rs` – integration test that drives the go-sanity runner and asserts the Rust
  implementations stay in lock-step.
- `justfile` – light CLI automation for common tasks.

## Development

You can work either inside the provided dev container or with a native toolchain:

- **Dev container** – use the [Dev Containers specification]https://containers.dev/. For example,
  with the `devcontainer` CLI:
  ```bash
  devcontainer up --workspace-folder .
  devcontainer exec --workspace-folder . just ci-test
  ```
  Editors such as VS Code or GitHub Codespaces can also consume the same definition. The container
  installs Rust (stable + nightly), Go 1.25.1, Python 3.12, and all required CLI tools via
  `.devcontainer/postCreate.sh`.
- **Native setup** – install the prerequisites manually and run `just install-ci-tools` to pull in
  the cargo utilities and linters we use in CI.

```
just go-sanity  # runs the shared Go sanity harness against sprig test cases
just test       # runs the Rust unit + integration tests
just verify     # executes both
just ci-test    # matches the GitHub Actions test job locally
just ci-behavior  # runs compat + optional property/contract suites
just ci-quality # fmt + clippy gate
just ci-security # cargo audit / cargo deny (requires tools installed)
just ci-mutation # cargo-mutants sweep (requires `cargo install cargo-mutants`)
just mutation   # alias for ci-mutation
just ci-fuzz    # smoke-tests the fuzz harnesses (requires nightly + cargo-fuzz)
just fuzz       # alias for ci-fuzz
just ci-release  # dry-run release-plz workflow
just release    # alias for ci-release
just install-ci-tools # installs cargo-audit, cargo-deny, cargo-tarpaulin, cargo-mutants, cargo-fuzz, actionlint, yamllint, release-plz
just install-cargo-audit|deny|tarpaulin|cargo-mutants|cargo-fuzz|actionlint|yamllint|release-plz # install individual extras
```

The integration test will skip itself gracefully if Go is missing. Keep the
cases in `test-cases/lithos-sprig.json` aligned with the functions implemented in Rust
for deterministic comparisons. When scenarios get more complex, add subdirectories under
`test-cases/` containing `input.tmpl`, `input.json`, and `expected.*` assets that both the
Rust harness and Go oracle can consume (see `test-cases/sprig/nested-default/` for an
example covering nested pipelines).

**Rust toolchain:** the workspace targets the 2021 edition. The minimum supported
Rust version (MSRV) is **1.70.0**; CI should exercise the latest stable release as
well as MSRV once it is wired up.

## Automation

- **GitHub Actions CI** (`.github/workflows/ci.yml`) fan-outs into dedicated jobs: the core unit
  test suite, behavioural validation (including the Go-backed compatibility harness), code quality
  (fmt, Clippy, actionlint, yamllint, optional Sonar scan), and security scans (`cargo audit` +
  `cargo deny`).
- **Deep Testing** (`.github/workflows/deep-testing.yml`) is scheduled weekly to run the fuzz
  harnesses (requires the Rust nightly toolchain) and, on the first of each month, mutation tests.
- **SonarQube / SonarCloud** (`.github/workflows/sonar.yml`) is ready to analyse the workspace.
  Provide a `SONAR_TOKEN` repository secret and, if needed, `SONAR_HOST_URL` before enabling the
  workflow; adjust `sonar-project.properties` with your actual organisation/project keys.
- **Renovate Bot** (`renovate.json`) is configured to group weekly Rust and Go dependency updates.
  Install the Renovate GitHub App (or hook through GitHub Actions) to activate automated PRs.
- **CodeRabbit** (`.coderabbit.yaml`) supplies default review hints for pull requests. Once the
  CodeRabbit GitHub App is installed, it will auto-review changes that touch the Rust crates or
  the Go oracle code.

## Using the helper crates

A typical integration wires the engine, default Go helpers, and Sprig helpers
together before parsing templates:

```rust
use lithos_gotmpl_core::{install_text_template_functions, Template};
use lithos_sprig::install_sprig_functions;

let mut builder = lithos_gotmpl_core::FunctionRegistryBuilder::new();
install_text_template_functions(&mut builder);      // Go defaults (`eq`, `printf`, ...)
install_sprig_functions(&mut builder);              // Sprig helpers (flow, strings, lists)

let registry = builder.build();
let tmpl = Template::parse_with_functions(
    "example",
    "{{ coalesce .name \"friend\" | title }}",
    registry,
)?;

let rendered = tmpl.render(&serde_json::json!({"name": null}))?;
assert_eq!(rendered, "Friend");
```

Run the ready-made example to see a fuller template in action:

```
cargo run --example flow_and_lists --package lithos-sprig
```

When you need a read-only view of helper usage, call `Template::analyze()` and
inspect the returned `TemplateAnalysis`. It reports function invocations,
variable paths, template calls, and control structures, allowing downstream
systems to reason about dynamic helper requirements without executing the
template.

```bash
cargo run --example analyze --package lithos-gotmpl-core
```

The Sprig layer is organised the same way as the upstream documentation:

- **Flow control:** `default`, `coalesce`, `ternary`, `empty`, `fail`, and JSON
  helpers (`fromJson`, `toJson`, plus the `must*` variants).
- **Strings:** case conversion, trim/affix helpers, `contains`, `replace`,
  `substr`, `trunc`, `wrap`, `indent`/`nindent`, `nospace`, and `repeat`.
- **String slices:** `splitList`, `split`, `splitn`, `join`, and `sortAlpha`.
- **Lists:** `list`, `first`/`last`/`rest`/`initial`, `append`/`prepend`/`concat`,
  `reverse`, `compact`, `uniq`, `without`, and `has`.
- **Maps:** `dict`, `merge`, `set`, `unset`, `hasKey`, `keys`, and `values`. Our
  implementation sorts the outputs of `keys` and `values` to guarantee
  deterministic results, deviating from Go’s unspecified map iteration order.

We intentionally omit Sprig helpers that rely on randomness, regular
expressions, or pluralisation until downstream demand materialises.

See `docs/function-coverage.md` for the full helper matrix, including the Go
defaults handled by `lithos-gotmpl-core`.

## Compatibility & Limitations

- Behaviour is intentionally focused on the subset of Go templates that our
  downstream tooling depends on. We do **not** support every construct that
  `text/template` provides (for example `else if`, `define`/`template`, or
  keyword helpers such as `break`). See `docs/template-syntax-coverage.md` for
  the current status.
- The project is not affiliated with or endorsed by the Go project or by the
  Masterminds/sprig maintainers. Compatibility tests rely on their public
  implementations for reference only.
- The Go-based `go-sanity` runner is an optional development aid. It requires a
  local Go toolchain and honours the upstream sprig licence. CI and local
  workflows should treat it as a verification step rather than a runtime
  dependency of the crates.

## Roadmap

- Flesh out additional helpers based on the needs of downstream consumers. Each
  new helper should add test cases and assertions to keep the Go sanity checks in sync.
- Explore publishing the helper registry as a trait or adapter
- Eventually wire in property tests for string/collection helpers once coverage
  expands.

## Attribution & Notices

This workspace includes test materials derived from the Go programming language
(`text/template`) and uses Masterminds' sprig library inside the `go-sanity`
tool. Full attribution details are recorded in [`NOTICE`](NOTICE).

## License

All crates in this workspace are dual-licensed under either the Apache License
2.0 or the MIT License. You may choose either licence to use the code. Copies of
both licences are provided in [`LICENSE-APACHE`](LICENSE-APACHE) and
[`LICENSE-MIT`](LICENSE-MIT). Unless otherwise stated, contributions are
accepted under the same dual-licence terms.