defmodule ALF.DSL do alias ALF.Components.{ Basic, Stage, Switch, DeadEnd, GotoPoint, Goto, Done, Plug, Unplug, Composer, Tbd } alias ALF.DSLError defmacro stage(atom, options \\ [opts: []]) do count = options[:count] || 1 opts = options[:opts] quote do Stage.validate_options(unquote(atom), unquote(options)) Basic.build_component(Stage, unquote(atom), unquote(count), unquote(opts), __MODULE__) end end defmacro switch(atom, options \\ [opts: []]) do opts = options[:opts] || [] count = options[:count] || 1 quote do Switch.validate_options(unquote(atom), unquote(options)) branches = build_branches(unquote(options)[:branches]) switch = Basic.build_component(Switch, unquote(atom), unquote(count), unquote(opts), __MODULE__) %{switch | branches: branches} end end defmacro goto(name, options \\ [opts: []]) do to = options[:to] opts = options[:opts] || [] count = options[:count] || 1 quote do Goto.validate_options(unquote(name), unquote(options)) goto = Basic.build_component(Goto, unquote(name), unquote(count), unquote(opts), __MODULE__) %{goto | to: unquote(to)} end end defmacro goto_point(name, options \\ [count: 1]) do count = options[:count] || 1 quote do Basic.build_component(GotoPoint, unquote(name), unquote(count), %{}, __MODULE__) end end defmacro dead_end(name, options \\ [count: 1]) do count = options[:count] || 1 quote do Basic.build_component(DeadEnd, unquote(name), unquote(count), %{}, __MODULE__) end end defmacro done(name, options \\ [opts: []]) do count = options[:count] || 1 opts = options[:opts] || [] quote do Done.validate_options(unquote(name), unquote(options)) Basic.build_component(Done, unquote(name), unquote(count), unquote(opts), __MODULE__) end end defmacro composer(name, options \\ [opts: []]) do opts = options[:opts] || [] memo = options[:memo] count = options[:count] || 1 quote do Composer.validate_options(unquote(name), unquote(options)) composer = Basic.build_component(Composer, unquote(name), unquote(count), unquote(opts), __MODULE__) %{composer | memo: unquote(memo)} end end defmacro from(module, options \\ [opts: []]) do count = options[:count] || 1 opts = options[:opts] || [] quote do validate_stages_from_options(unquote(options)) set_options(unquote(module).alf_components, unquote(opts), unquote(count)) end end defmacro plug_with(module, options \\ [count: 1], do: block) do count = options[:count] || 1 quote do validate_plug_with_options(unquote(options)) plug = Basic.build_component(Plug, unquote(module), unquote(count), %{}, __MODULE__) unplug = Basic.build_component(Unplug, unquote(module), unquote(count), %{}, __MODULE__) [plug] ++ unquote(block) ++ [unplug] end end defmacro tbd() do quote do Basic.build_component(Tbd, unquote(:tbd), 1, %{}, __MODULE__) end end defmacro tbd(name, opts \\ []) do count = opts[:count] || 1 quote do Tbd.validate_name(unquote(name)) tbd = Basic.build_component(Tbd, unquote(name), unquote(count), %{}, __MODULE__) %{tbd | count: unquote(count)} 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 '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, :count] 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) do branches |> Enum.reduce(%{}, fn {key, stages}, final_specs -> Map.put(final_specs, key, stages) end) end def set_options(components, opts, count) do Enum.map(components, &%{&1 | opts: merge_opts(&1.opts, opts), count: count}) 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 @before_compile ALF.DSL @spec start() :: :ok def start() do ALF.Manager.start(__MODULE__, []) end @spec start(list) :: :ok def start(opts) when is_list(opts) do ALF.Manager.start(__MODULE__, opts) end @spec started?() :: true | false def started?() do ALF.Manager.started?(__MODULE__) end @spec stop() :: :ok | {:exit, {atom, any}} def stop do ALF.Manager.stop(__MODULE__) end @spec call(any, Keyword.t()) :: any | [any] | nil def call(event, opts \\ [debug: false]) do ALF.Manager.call(event, __MODULE__, opts) end @spec call(any, Keyword.t()) :: reference def cast(event, opts \\ [debug: false, send_result: false]) do ALF.Manager.cast(event, __MODULE__, opts) end @spec stream(Enumerable.t(), Keyword.t()) :: Enumerable.t() def stream(stream, opts \\ [debug: false]) do ALF.Manager.stream(stream, __MODULE__, opts) end @spec flow(map(), list(), Keyword.t()) :: Enumerable.t() def flow(flow, names, opts \\ [debug: false]) def flow(flow, names, opts) when is_map(flow) and is_list(names) do Enum.reduce(flow, %{}, fn {name, stream}, acc -> if name in names do stream = ALF.Manager.stream(stream, __MODULE__, opts) Map.put(acc, name, stream) else Map.put(acc, name, stream) end end) end def flow(flow, name, opts) when is_map(flow) do flow(flow, [name], opts) end @spec components() :: list(map()) def components() do ALF.Manager.components(__MODULE__) end end end defmacro __before_compile__(_env) do quote do def alf_components, do: List.flatten(@components) end end end