# `PtcRunner.Mustache`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/mustache.ex#L1)

Standalone Mustache template parser and expander.

Supports a subset of Mustache spec:
- Simple variables: `{{name}}`
- Dot notation: `{{user.name}}`
- Current element: `{{.}}`
- Comments: `{{! comment }}`
- Sections (lists): `{{#items}}...{{/items}}`
- Sections (maps/context push): `{{#user}}...{{/user}}`
- Inverted sections: `{{^items}}...{{/items}}`
- Standalone tag whitespace control

No dependencies on other PtcRunner modules.

## Examples

    iex> {:ok, ast} = PtcRunner.Mustache.parse("Hello {{name}}")
    iex> PtcRunner.Mustache.expand(ast, %{name: "Alice"})
    {:ok, "Hello Alice"}

    iex> PtcRunner.Mustache.render("{{#items}}- {{name}}\n{{/items}}", %{items: [%{name: "A"}, %{name: "B"}]})
    {:ok, "- A\n- B\n"}

# `ast`

```elixir
@type ast() :: [ast_node()]
```

Abstract syntax tree

# `ast_node`

```elixir
@type ast_node() ::
  {:text, String.t()}
  | {:variable, path :: [String.t()], loc :: location()}
  | {:current, loc :: location()}
  | {:comment, String.t(), loc :: location()}
  | {:section, name :: String.t(), inner :: ast(), loc :: location()}
  | {:inverted_section, name :: String.t(), inner :: ast(), loc :: location()}
```

AST node types

# `location`

```elixir
@type location() :: %{line: pos_integer(), col: pos_integer()}
```

Location in the template

# `option`

```elixir
@type option() :: {:max_depth, pos_integer()}
```

Options for expand/3 and render/3

# `variable_info`

```elixir
@type variable_info() :: %{
  type: :simple | :section | :inverted_section,
  path: [String.t()],
  fields: [variable_info()] | nil,
  loc: location()
}
```

Variable info for extraction

# `expand`

```elixir
@spec expand(ast(), map(), [option()]) :: {:ok, String.t()} | {:error, term()}
```

Expand AST with context map.

Returns `{:ok, string}` on success or `{:error, reason}` on expansion errors.

## Options

- `:max_depth` - Maximum recursion depth (default: 20)

## Examples

    iex> {:ok, ast} = PtcRunner.Mustache.parse("Hello {{name}}")
    iex> PtcRunner.Mustache.expand(ast, %{name: "World"})
    {:ok, "Hello World"}

    iex> {:ok, ast} = PtcRunner.Mustache.parse("{{.}}")
    iex> {:error, {:dot_on_map, _, _}} = PtcRunner.Mustache.expand(ast, %{}, [])
    {:error, {:dot_on_map, %{line: 1, col: 1}, "{{.}} requires scalar value, got map on line 1, col 1. Use {{.field}} or pre-format the data."}}

# `extract_variables`

```elixir
@spec extract_variables(ast()) :: [variable_info()]
```

Extract all variables from AST for validation.

Returns a list of variable info maps including location and type.

## Examples

    iex> {:ok, ast} = PtcRunner.Mustache.parse("{{name}} {{user.email}}")
    iex> PtcRunner.Mustache.extract_variables(ast)
    [
      %{type: :simple, path: ["name"], fields: nil, loc: %{line: 1, col: 1}},
      %{type: :simple, path: ["user", "email"], fields: nil, loc: %{line: 1, col: 10}}
    ]

# `parse`

```elixir
@spec parse(String.t()) :: {:ok, ast()} | {:error, String.t()}
```

Parse template string into AST.

Returns `{:ok, ast}` on success or `{:error, message}` on parse errors.

## Examples

    iex> PtcRunner.Mustache.parse("Hello {{name}}")
    {:ok, [{:text, "Hello "}, {:variable, ["name"], %{line: 1, col: 7}}]}

    iex> PtcRunner.Mustache.parse("{{#items}}{{/items}}")
    {:ok, [{:section, "items", [], %{line: 1, col: 1}}]}

    iex> PtcRunner.Mustache.parse("{{#items}}")
    {:error, "unclosed section 'items' opened at line 1, col 1"}

# `render`

```elixir
@spec render(String.t(), map(), [option()]) :: {:ok, String.t()} | {:error, term()}
```

Convenience: parse and expand in one call.

## Examples

    iex> PtcRunner.Mustache.render("Hello {{name}}", %{name: "Alice"})
    {:ok, "Hello Alice"}

    iex> PtcRunner.Mustache.render("{{#items}}{{name}} {{/items}}", %{items: [%{name: "A"}, %{name: "B"}]})
    {:ok, "A B "}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
