defmodule SnapFramework.Engine do
@moduledoc """
The EEx template engine.
"""
@behaviour EEx.Engine
require Logger
def encode_to_iodata!({:safe, body}), do: body
def encode_to_iodata!(body) when is_binary(body), do: body
def compile(path, assigns, info, _env) do
quoted = EEx.compile_file(path, info)
{result, _binding} = Code.eval_quoted(quoted, assigns)
result
end
def compile_string(string, assigns, info, env) do
quoted = EEx.compile_string(string, info)
{result, _binding} = Code.eval_quoted(quoted, assigns, env)
result
end
@doc false
def init(opts) do
%{
iodata: [],
dynamic: [],
vars_count: 0,
assigns: opts[:assigns] || []
}
end
@doc false
def handle_begin(state) do
Macro.var(:assigns, __MODULE__)
%{state | iodata: [], dynamic: []}
end
@doc false
def handle_end(quoted) do
quoted
|> handle_body()
end
@doc false
def handle_body(state) do
%{iodata: iodata, dynamic: dynamic} = state
safe = Enum.reverse(iodata)
{:__block__, [], Enum.reverse([safe | dynamic])}
end
@doc false
def handle_text(state, text) do
handle_text(state, [], text)
end
@doc false
def handle_text(state, _meta, text) do
%{iodata: iodata} = state
%{state | iodata: [text | iodata]}
end
@doc false
def handle_expr(state, "=", ast) do
ast = traverse(ast, state.assigns)
%{iodata: iodata, dynamic: dynamic, vars_count: vars_count} = state
var = Macro.var(:"arg#{vars_count}", __MODULE__)
ast = quote do: unquote(var) = unquote(ast)
%{state | dynamic: [ast | dynamic], iodata: [var | iodata], vars_count: vars_count + 1}
end
def handle_expr(state, "", ast) do
ast = traverse(ast, state.assigns)
%{dynamic: dynamic} = state
%{state | dynamic: [ast | dynamic]}
end
def handle_expr(state, marker, ast) do
EEx.Engine.handle_expr(state, marker, ast)
end
## Traversal
defp traverse(expr, assigns) do
expr
|> Macro.prewalk(&SnapFramework.Parser.Assigns.run(&1, assigns))
# |> Macro.prewalk(&SnapFramework.Parser.Enumeration.run/1)
|> Macro.prewalk(&SnapFramework.Parser.Graph.run/1)
|> Macro.prewalk(&SnapFramework.Parser.Component.run/1)
|> Macro.prewalk(&SnapFramework.Parser.Primitive.run/1)
|> Macro.prewalk(&SnapFramework.Parser.Outlet.run(&1, assigns))
end
@doc false
def fetch_assign!(assigns, key) do
case Access.fetch(assigns, key) do
{:ok, val} ->
val
:error ->
raise ArgumentError, """
assign @#{key} not available in eex template.
Please make sure all proper assigns have been set. If this
is a child template, ensure assigns are given explicitly by
the parent template as they are not automatically forwarded.
Available assigns: #{inspect(Enum.map(assigns, &elem(&1, 0)))}
"""
end
end
end