PtcRunner.Mustache (PtcRunner v0.7.0)

Copy Markdown View Source

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"}

Summary

Types

Abstract syntax tree

AST node types

Location in the template

Options for expand/3 and render/3

Variable info for extraction

Functions

Expand AST with context map.

Extract all variables from AST for validation.

Parse template string into AST.

Convenience: parse and expand in one call.

Types

ast()

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

Abstract syntax tree

ast_node()

@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()

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

Location in the template

option()

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

Options for expand/3 and render/3

variable_info()

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

Variable info for extraction

Functions

expand(ast, context, opts \\ [])

@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(ast)

@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(template)

@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(template, context, opts \\ [])

@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 "}