defmodule ALF.DSL do
alias ALF.Components.{
Basic,
Stage,
Switch,
Clone,
DeadEnd,
GotoPoint,
Goto,
Plug,
Unplug,
Decomposer,
Recomposer,
Tbd
}
alias ALF.DSLError
defmacro stage(atom, options \\ [opts: [], count: 1, name: nil]) do
count = options[:count]
opts = options[:opts]
name = options[:name]
quote do
Stage.validate_options(unquote(atom), unquote(options))
stage =
Basic.build_component(
Stage,
unquote(atom),
unquote(name),
unquote(opts),
__MODULE__
)
%{stage | count: unquote(count) || 1}
end
end
defmacro switch(atom, options \\ [opts: [], name: nil]) do
opts = options[:opts]
name = options[:name]
quote do
Switch.validate_options(unquote(atom), unquote(options))
branches = ALF.DSL.build_branches(unquote(options)[:branches], __MODULE__)
switch =
Basic.build_component(
Switch,
unquote(atom),
unquote(name),
unquote(opts),
__MODULE__
)
%{switch | branches: branches}
end
end
defmacro clone(name, options) do
quote do
Clone.validate_options(unquote(name), unquote(options))
stages = set_pipeline_module(unquote(options)[:to], __MODULE__)
%Clone{
name: unquote(name),
to: stages,
pipe_module: __MODULE__,
pipeline_module: __MODULE__
}
end
end
defmacro goto(atom, options \\ [name: nil, opts: []]) do
to = options[:to]
opts = options[:opts]
name = options[:name]
quote do
Goto.validate_options(unquote(atom), unquote(options))
goto =
Basic.build_component(
Goto,
unquote(atom),
unquote(name),
unquote(opts),
__MODULE__
)
%{goto | to: unquote(to)}
end
end
defmacro goto_point(name) do
quote do
%GotoPoint{
name: unquote(name),
pipe_module: __MODULE__,
pipeline_module: __MODULE__
}
end
end
defmacro dead_end(name) do
quote do
%DeadEnd{
name: unquote(name),
pipe_module: __MODULE__,
pipeline_module: __MODULE__
}
end
end
defmacro decomposer(atom, options \\ [opts: [], name: nil]) do
opts = options[:opts]
name = options[:name]
quote do
Decomposer.validate_options(unquote(atom), unquote(options))
Basic.build_component(
Decomposer,
unquote(atom),
unquote(name),
unquote(opts),
__MODULE__
)
end
end
defmacro recomposer(atom, options \\ [opts: [], name: nil]) do
opts = options[:opts]
name = options[:name]
quote do
Recomposer.validate_options(unquote(atom), unquote(options))
Basic.build_component(
Recomposer,
unquote(atom),
unquote(name),
unquote(opts),
__MODULE__
)
end
end
defmacro stages_from(module, options \\ [opts: [], count: 1]) do
count = options[:count]
opts = options[:opts]
quote do
validate_stages_from_options(unquote(options))
unquote(module).alf_components
|> ALF.DSL.set_pipeline_module(__MODULE__)
|> ALF.DSL.set_options(unquote(opts), unquote(count))
end
end
defmacro plug_with(module, options \\ [opts: [], name: nil], do: block) do
name = options[:name]
quote do
validate_plug_with_options(unquote(options))
name = if unquote(name), do: unquote(name), else: unquote(module)
plug = %Plug{
name: name,
module: unquote(module),
pipe_module: __MODULE__,
pipeline_module: __MODULE__
}
unplug = %Unplug{
name: name,
module: unquote(module),
pipe_module: __MODULE__,
pipeline_module: __MODULE__
}
[plug] ++ unquote(block) ++ [unplug]
end
end
defmacro tbd(atom \\ :tbd) do
quote do
Tbd.validate_name(unquote(atom))
Basic.build_component(
Tbd,
unquote(atom),
unquote(atom),
%{},
__MODULE__
)
end
end
def validate_stages_from_options(options) do
dsl_options = [:count, :opts]
wrong_options = Keyword.keys(options) -- dsl_options
if Enum.any?(wrong_options) do
raise DSLError,
"Wrong options are given for the stages_from macro: #{inspect(wrong_options)}. " <>
"Available options are #{inspect(dsl_options)}"
end
end
def validate_plug_with_options(options) do
dsl_options = [:module, :name, :opts]
wrong_options = Keyword.keys(options) -- dsl_options
if Enum.any?(wrong_options) do
raise DSLError,
"Wrong options are given for the plug_with macro: #{inspect(wrong_options)}. " <>
"Available options are #{inspect(dsl_options)}"
end
end
def build_branches(branches, module) do
branches
|> Enum.reduce(%{}, fn {key, stages}, final_specs ->
stages = set_pipeline_module(stages, module)
Map.put(final_specs, key, stages)
end)
end
def set_pipeline_module(stages, module) do
Enum.map(stages, fn stage ->
%{stage | pipeline_module: module}
end)
end
def set_options(stages, opts, count) do
Enum.map(stages, fn stage ->
opts = merge_opts(stage.opts, opts)
%{stage | opts: opts, count: count || 1}
end)
end
defp merge_opts(opts, new_opts) do
opts = if is_map(opts), do: Map.to_list(opts), else: opts
new_opts = if is_map(new_opts), do: Map.to_list(new_opts), else: new_opts
Keyword.merge(opts, new_opts)
end
defmacro __using__(opts) do
quote do
import ALF.DSL
Module.register_attribute(__MODULE__, :stages, accumulate: false)
@before_compile ALF.DSL
def __pipeline__, do: true
def __options__, do: unquote(opts)
def done!(event) do
raise ALF.DoneStatement, event
end
end
end
defmacro __before_compile__(_env) do
quote do
def alf_components, do: List.flatten(@components)
end
end
end