View Source Alembic.Context (alembic v0.1.0)

Runtime variable store the Evaluator uses to resolve variable paths.

Behaves as a scoped symbol table: a stack of maps (scopes), head is the innermost scope. Inner scopes shadow outer scopes without mutating them — push_scope/2 and pop_scope/1 are pure and always return a new struct. {% assign %} variables live in assigns, consulted after every scope.

Summary

Functions

Sets a {% assign %} variable. Always lands in the root-level assigns map, so it persists across push_scope/2/pop_scope/1 cycles (e.g. it survives a {% for %} loop ending).

Sets per-call custom filter modules, layered on top of (and taking precedence over, on a name collision) whatever is registered globally via config :alembic, custom_filters: [...]. Used by Alembic.render/3's :custom_filters option — see Alembic.Filters.apply/4.

Builds the "forloop" metadata map injected into a {% for %} body's scope for one iteration.

Sets the loader function Alembic.Evaluator uses to resolve {% include %} targets. Not part of issue 1.4.1's original spec — added when integration testing (issue 1.5.4) revealed {:include, _, _} had no eval_node clause at all in the Evaluator (issue 1.4.2's task list never listed it, unlike extends/block which were explicitly deferred to Alembic.Inheritance). Threading a loader function through a new positional parameter on every eval_nodes/eval_node call would have touched far more of the Evaluator than storing it once here.

Looks up a single variable name, searching scopes from innermost to outermost, then assigns last.

Wraps an initial bindings map as the root scope.

Pops the innermost scope, restoring whatever was shadowed. Raises if called on the root scope — popping past the root is a programming error.

Pushes a new innermost scope (entering a {% for %} body or an {% include %}). Pure — returns a new struct, never mutates.

Resolves a dot-notation path, traversing nested maps, keyword lists, and list indices after the first segment.

Enables or disables strict mode: when true, an undefined variable path makes Alembic.Evaluator return {:error, {:undefined_variable, path}} instead of rendering as an empty string / evaluating as nil. Used by Alembic.render/3's strict: true option (issue 1.5.3).

Types

@type loader_fn() :: (String.t() -> {:ok, String.t()} | {:error, term()})
@type t() :: %Alembic.Context{
  assigns: map(),
  custom_filters: [module()],
  loader_fn: loader_fn() | nil,
  scopes: [map()],
  strict: boolean()
}

Functions

@spec assign(t(), String.t(), any()) :: t()

Sets a {% assign %} variable. Always lands in the root-level assigns map, so it persists across push_scope/2/pop_scope/1 cycles (e.g. it survives a {% for %} loop ending).

Examples

iex> ctx = Alembic.Context.new(%{}) |> Alembic.Context.assign("count", 10)
iex> Alembic.Context.lookup(ctx, "count")
{:ok, 10}
Link to this function

custom_filters(ctx, modules)

View Source
@spec custom_filters(t(), [module()]) :: t()

Sets per-call custom filter modules, layered on top of (and taking precedence over, on a name collision) whatever is registered globally via config :alembic, custom_filters: [...]. Used by Alembic.render/3's :custom_filters option — see Alembic.Filters.apply/4.

Examples

iex> ctx = Alembic.Context.new(%{}) |> Alembic.Context.custom_filters([MyFilter])
iex> ctx.custom_filters
[MyFilter]
Link to this function

forloop_meta(index, length, value)

View Source
@spec forloop_meta(non_neg_integer(), non_neg_integer(), any()) :: map()

Builds the "forloop" metadata map injected into a {% for %} body's scope for one iteration.

Examples

iex> Alembic.Context.forloop_meta(0, 3, "a")
%{"forloop" => %{"index" => 1, "index0" => 0, "rindex" => 3, "rindex0" => 2, "first" => true, "last" => false, "length" => 3}}
@spec loader(t(), loader_fn()) :: t()

Sets the loader function Alembic.Evaluator uses to resolve {% include %} targets. Not part of issue 1.4.1's original spec — added when integration testing (issue 1.5.4) revealed {:include, _, _} had no eval_node clause at all in the Evaluator (issue 1.4.2's task list never listed it, unlike extends/block which were explicitly deferred to Alembic.Inheritance). Threading a loader function through a new positional parameter on every eval_nodes/eval_node call would have touched far more of the Evaluator than storing it once here.

Examples

iex> ctx = Alembic.Context.new(%{}) |> Alembic.Context.loader(fn _name -> {:error, :not_found} end)
iex> is_function(ctx.loader_fn, 1)
true
@spec lookup(t(), String.t()) :: {:ok, any()} | :not_found

Looks up a single variable name, searching scopes from innermost to outermost, then assigns last.

Examples

iex> ctx = Alembic.Context.new(%{"name" => "Alice"})
iex> Alembic.Context.lookup(ctx, "name")
{:ok, "Alice"}

iex> ctx = Alembic.Context.new(%{})
iex> Alembic.Context.lookup(ctx, "missing")
:not_found
@spec new(map()) :: t()

Wraps an initial bindings map as the root scope.

Examples

iex> ctx = Alembic.Context.new(%{"name" => "Alice"})
iex> Alembic.Context.lookup(ctx, "name")
{:ok, "Alice"}
@spec pop_scope(t()) :: t()

Pops the innermost scope, restoring whatever was shadowed. Raises if called on the root scope — popping past the root is a programming error.

Examples

iex> ctx = Alembic.Context.new(%{"name" => "Alice"})
iex> ctx = ctx |> Alembic.Context.push_scope(%{"name" => "Bob"}) |> Alembic.Context.pop_scope()
iex> Alembic.Context.lookup(ctx, "name")
{:ok, "Alice"}
Link to this function

push_scope(ctx, bindings)

View Source
@spec push_scope(t(), map()) :: t()

Pushes a new innermost scope (entering a {% for %} body or an {% include %}). Pure — returns a new struct, never mutates.

Examples

iex> ctx = Alembic.Context.new(%{"name" => "Alice"})
iex> ctx = Alembic.Context.push_scope(ctx, %{"name" => "Bob"})
iex> Alembic.Context.lookup(ctx, "name")
{:ok, "Bob"}
@spec resolve_path(t(), [String.t()]) :: {:ok, any()} | :not_found

Resolves a dot-notation path, traversing nested maps, keyword lists, and list indices after the first segment.

Examples

iex> ctx = Alembic.Context.new(%{"user" => %{"address" => %{"city" => "Lisbon"}}})
iex> Alembic.Context.resolve_path(ctx, ["user", "address", "city"])
{:ok, "Lisbon"}

iex> ctx = Alembic.Context.new(%{"posts" => ["a", "b", "c"]})
iex> Alembic.Context.resolve_path(ctx, ["posts", "1"])
{:ok, "b"}
@spec strict(t(), boolean()) :: t()

Enables or disables strict mode: when true, an undefined variable path makes Alembic.Evaluator return {:error, {:undefined_variable, path}} instead of rendering as an empty string / evaluating as nil. Used by Alembic.render/3's strict: true option (issue 1.5.3).

Examples

iex> ctx = Alembic.Context.new(%{}) |> Alembic.Context.strict(true)
iex> ctx.strict
true