Packages

Macros to support Ruby-like jbuilder/builder templates to generate JSON and XML

Retired package: Release invalid

Current section

Files

Jump to
exbuilder lib exbuilder.ex
Raw

lib/exbuilder.ex

require Template.Location
defmodule ExBuilder.Template do
@moduledoc """
DSL for building complex Map structures. This is very Ruby builder-like templating.
"""
use Template.Location
templates "web/templates", "builder"
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
@before_compile unquote(__MODULE__)
end
end
defmacro __before_compile__(_env) do
templates = find_templates
ast = templates |> Enum.map(fn(path) -> compile(path) end)
quote do
unquote(ast)
end
end
defp compile(path) do
name = view_name(path)
{:ok, body} = File.read(path)
quote do
@file unquote(path)
@external_resource unquote(path)
@doc false
def unquote(name)(var!(assigns)) when is_list(var!(assigns)) do
_ = var!(assigns)
document do
unquote(Code.string_to_quoted(body))
end
end
end
end
defp postwalk({:@, [line: line], [{name, [line: line], nil}]}) do
quote do: assign(unquote(name))
end
defp postwalk(ast), do: ast
# DSL Implementation
@doc ~S"""
Document entry point. This macro is used as a wrapper for a document generation.
##Example:
iex> document do
iex> property(:name, "Alex")
iex> end
%{name: "Alex"}
"""
defmacro document(do: block) do
quote do
var!(scope) = %{}
unquote(Macro.postwalk(block, &postwalk/1))
var!(scope)
end
end
@doc ~S"""
Provides access to the variables inside 'assigns' variable in the caller's scope
##Example:
iex> var!(assigns) = [name: "Alex"]
iex> document do
iex> name = assign(:name)
iex> property(:name, name)
iex> end
%{name: "Alex"}
To simplify access to the variables, Eex stype @var_name is supported to:
iex> var!(assigns) = [name: "Alex"]
iex> document do
iex> property(:name, @name)
iex> end
%{name: "Alex"}
"""
defmacro assign(name) do
quote do
Keyword.get var!(assigns), unquote(name)
end
end
@doc ~S"""
Provides access to the variables inside 'assigns' variable in the caller's scope
##Example:
iex> document do
iex> object(:sample, %{name: "Joe"})
iex> end
%{sample: %{name: "Joe"}}
iex> document do
iex> object(:sample, %{name: "Joe"}) do
iex> object(:children) do
iex> object(:child, %{name: "Phil"})
iex> end
iex> end
iex> end
%{sample: %{name: "Joe", children: %{child: %{name: "Phil"}}}}
"""
defmacro object(name, do: block) do
quote do: object(unquote(name), %{}, do: unquote(block))
end
defmacro object(name, attributes) do
quote do: object(unquote(name), unquote(attributes), do: nil)
end
defmacro object(name, attributes, do: block) do
quote do
buffer = var!(scope)
var!(scope) = unquote(attributes)
unquote(block)
var!(scope) = Map.merge(buffer, %{unquote(name) => var!(scope)})
end
end
@doc ~S"""
Add a property to the object context
##Example:
iex> document do
iex> property(:name, "Joe")
iex> end
%{name: "Joe"}
"""
defmacro property(name, value) do
quote do
var!(scope) = Map.merge(var!(scope), %{unquote(name) => unquote(value)})
end
end
@doc ~S"""
Add an array property to the object context by iterating on list param.
Each item in the given list param is available inside array block as an 'item' variable
##Example:
iex> document do
iex> object(:work, %{name: "Development"})
iex> array(:names, ["Joe", "Phil"]) do
iex> %{name: item}
iex> end
iex> end
%{work: %{name: "Development"}, names: [%{name: "Joe"}, %{name: "Phil"}] }
"""
defmacro array(name, list, do: block) do
quote do
values = Enum.map(unquote(list), fn(var!(item)) -> unquote(block) end)
property(unquote(name), values)
end
end
end
defmodule ExBuilder.View do
@moduledoc """
This module should be included into application.
All templates are compiled into this module, with names based on a file location
Example: healthcheck/status.builder => healthcheck_status
"""
use ExBuilder.Template
def apply_template(path, assigns) do
name = view_name(path)
apply(__MODULE__, name, [assigns])
end
def render_json(path, assigns) do
{:ok, json} = apply_template(path, assigns) |> Poison.encode
json
end
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
end
end
end