defmodule Makina do
alias Makina.Error
import Makina.Helpers
@moduledoc File.read!("priv/docs/makina.md")
@doc false
@spec __using__(Keyword.t()) :: Macro.t()
defmacro __using__(options) do
options =
options
|> parallel_composition(__CALLER__)
|> imports(__CALLER__)
state_options =
Enum.filter(options, fn {k, _v} -> k in [:extends] end) ++ [specs: true, docs: true]
invariant_options = Enum.filter(options, fn {k, _v} -> k in [:extends] end)
command_options =
Enum.filter(options, fn {k, _v} -> k in [:extends, :implemented_by] end) ++
[specs: true, docs: true, defaults: true]
debug = Application.get_env(:makina, :debug, [])
quote do
use Makina.State, unquote(state_options)
use Makina.Invariant, unquote(invariant_options)
use Makina.Command, unquote(command_options)
use Makina.Behaviour
import unquote(__MODULE__)
unquote(debug)
@before_compile unquote(__MODULE__)
end
end
@spec __before_compile__(Macro.Env.t()) :: Macro.t()
defmacro __before_compile__(_env) do
module = __CALLER__.module
userdocs = Module.get_attribute(module, :moduledoc)
# options = Module.get_attribute(module, :options)
cmds = Module.get_attribute(module, :commands)
attrs = Module.get_attribute(module, :attributes)
invs = Module.get_attribute(module, :invariants)
docs =
docs("model_module.md",
name: module,
userdocs: userdocs,
cmds: cmds,
attrs: attrs,
invs: invs,
# TODO
extends: nil,
composed: nil,
imports: nil
)
quote do
@moduledoc unquote(docs)
end
end
@doc """
This macro rewrites the given expression into a symbolic call.
"""
@spec symbolic(Macro.t()) :: Macro.t()
defmacro symbolic(expr = {{:., _, [_, _]}, _, _}), do: to_symbolic_call(expr)
defmacro symbolic(expr) do
"error in symbolic expression: #{Macro.to_string(expr)}"
|> Error.throw_error(__CALLER__)
end
@spec to_symbolic_call(Macro.t()) :: Macro.t()
defp to_symbolic_call({{:., _, [module, function]}, _, args}) do
args =
Enum.map(args, fn
arg = {{:., _, [_, _]}, _, _} -> to_symbolic_call(arg)
arg -> arg
end)
quote do
{:call, unquote(module), unquote(function), unquote(args)}
end
end
##############################################################################
# Helpers
##############################################################################
defp parallel_composition(options, env) do
if not is_nil(options[:extends]) and is_list(options[:extends]) do
composed = :"#{env.module}.Composed"
contents =
quote do
use Makina.Composition, extends: unquote(options[:extends])
end
Module.create(composed, contents, env)
Keyword.delete(options, :extends) |> Keyword.put(:extends, composed)
else
options
end
end
defp imports(options, env) do
if not is_nil(options[:where]) or not is_nil(options[:hiding]) do
imports = :"#{env.module}.Imports"
args =
[model: options[:extends]] ++ Enum.filter(options, fn {k, _v} -> k in [:where, :hiding] end)
contents =
quote do
use Makina.Import, unquote(args)
end
Module.create(imports, contents, env)
options
|> Keyword.delete(:where)
|> Keyword.delete(:hiding)
|> Keyword.delete(:extends)
|> Keyword.put(:extends, imports)
else
options
end
end
end