defmodule Surface.View do
@moduledoc """
Use this module on regular Phoenix views to enable .sface templates
## Examples
To make sface templates available on all your views, add to the `view` function on `lib/your_app_web.ex`:
defmodule YourAppWeb do
# ...
def view do
use Phoenix.View,
root: "lib/your_app_web/templates",
namespace: YourAppWeb
# ...
use Surface.View, root: "lib/your_app_web/templates"
end
end
If you want to make it available only on specific views, you can add a new function on the `lib/your_app_web.ex`:
defmodule YourAppWeb do
# ...
def surface_view(options \\ []) do
[
view(options),
quote do
use Surface.View, root: "lib/your_app_web/templates"
end
]
end
end
Then, replace `use YourAppWeb, :view` with `use YourAppWeb, :surface_view` on the views you want to enable sface
templates.
"""
defmacro __using__(opts \\ []) do
%{module: module} = __CALLER__
root = opts[:root] || raise(ArgumentError, "expected :root to be given as an option")
renders =
for {name, _} <- templates(module, root) do
quote do
def render("#{unquote(name)}.html", assigns) do
__render_surface__(unquote(name), assigns)
end
end
end
before_compile =
quote do
@surface_view_root unquote(root)
@before_compile unquote(__MODULE__)
end
[before_compile | renders]
end
defmacro __before_compile__(env) do
root = Module.get_attribute(env.module, :surface_view_root)
render_funs =
for {name, path} <- templates(env.module, root) do
ast =
path
|> File.read!()
|> Surface.Compiler.compile(1, env, path)
|> Surface.Compiler.to_live_struct()
quote do
@file unquote(path)
@external_resource unquote(path)
defp __render_surface__(unquote(name), var!(assigns)) do
unquote(ast)
end
end
end
mix_recompile =
quote do
# Override definition injected by Phoenix.Template
defoverridable __mix_recompile__?: 0
@doc false
def __mix_recompile__? do
super() || __surface_recompile__?()
end
@doc false
def __surface_recompile__? do
unquote(hash(env.module, root)) != Surface.View.hash(unquote(env.module), unquote(root))
end
end
[mix_recompile | render_funs]
end
defp templates(module, root) do
module
|> templates_wildcard(root)
|> Path.wildcard()
|> Enum.map(&{template_name(&1), &1})
end
defp templates_wildcard(module, root) do
view_name =
module
|> Module.split()
|> Enum.drop(1)
|> Enum.map(&Macro.underscore/1)
|> Enum.join("/")
|> String.replace_trailing("_view", "")
"#{root}/#{view_name}/*.sface"
end
defp template_name(path) do
path
|> Path.basename()
|> String.replace_trailing(".sface", "")
end
@doc """
Returns the hash of all template paths for the given view.
Used by Surface to check if a given view requires recompilation when a new template is added.
"""
def hash(module, root) do
module
|> templates_wildcard(root)
|> Path.wildcard()
|> Enum.sort()
|> :erlang.md5()
end
end