Current section
Files
Jump to
Current section
Files
lib/makina/command.ex
# TODO: docs
defmodule Makina.Command do
import Makina.Helpers
##############################################################################
# Type definitions and module docs
##############################################################################
@moduledoc false
@typedoc "Type that represents the options allowed in `#{inspect(__MODULE__)}`"
@type option() ::
{:docs, boolean()}
| {:specs, boolean()}
| {:defaults, boolean()}
| {:implemented_by, module()}
| {:extends, Macro.t()}
# Attribute that stores the name of the allowed options.
@options [:defaults, :docs, :specs, :implemented_by, :extends]
##############################################################################
# Exposed macros
##############################################################################
@doc """
Configures a module to allow command defintions. Accepts global command options, this means that
options passed to `use` are passed to every command declared in the module.
"""
@spec __using__([option()]) :: Macro.t()
defmacro __using__(options) do
# Registers an attribute that stores the names of the commands defined in the module.
Module.register_attribute(__CALLER__.module, :commands, accumulate: true)
# Registers an attribute that stores the global command global options.
Module.register_attribute(__CALLER__.module, :command_options, accumulate: true)
# Registers an attribute that stores information to avoid infinite recursion.
Module.register_attribute(__CALLER__.module, :command_processed_options, accumulate: true)
Module.register_attribute(__CALLER__.module, :commands_info, accumulate: true)
# Extracts and registers the global options passed to `use`.
register_options(__CALLER__, :command_options, @options, options)
# Extracts checker, it is needed for args generator in composition
checker = get_checker()
quote do
use unquote(checker)
import unquote(__MODULE__)
# Require command extension
require Makina.Command.Base
require Makina.Command.Defaults
require Makina.Command.Extends
require Makina.Command.ImplementedBy
# Some extensions need to inject code before compilation
@before_compile Makina.Command.Extends
@before_compile Makina.Command.Base
# After compilation retrieves the types, specs and definitions in this module.
# @after_compile {Makina.Info, :_store_commands}
end
end
@doc """
`command/1` allows to define a command with no options and no body.
"""
@spec command(Macro.t()) :: Macro.t()
defmacro command(decl), do: quote(do: command(unquote(decl), [], do: []))
@doc """
`command/2` allows to define a command without or without body.
"""
@spec command(Macro.t(), Macro.t()) :: Macro.t()
defmacro command(decl, block = [{:do, _block}]) do
context = __CALLER__.module
options = Module.get_attribute(context, :command_options, [])
quote context: context do
command(unquote(decl), unquote(options), unquote(block))
end
end
defmacro command(decl, options) do
context = __CALLER__.module
quote context: context do
command(unquote(decl), unquote(options), do: [])
end
end
@doc """
`command/3` allows to define a command with options.
"""
@spec command(Macro.t(), [option()], Macro.t()) :: Macro.t()
defmacro command(decl, opts, block) do
context = __CALLER__.module
processed = Module.get_attribute(context, :command_processed_options, [])
options = Module.get_attribute(context, :command_options, [])
options = (opts ++ options) |> Enum.filter(fn {k, _v} -> k not in processed end)
arguments = [decl, options, block]
cond do
options[:implemented_by] ->
Module.put_attribute(context, :command_processed_options, :implemented_by)
quote context: context do
Makina.Command.ImplementedBy.command(unquote_splicing(arguments))
end
options[:extends] ->
Module.put_attribute(context, :command_processed_options, :extends)
quote context: context do
Makina.Command.Extends.command(unquote_splicing(arguments))
end
options[:defaults] ->
Module.put_attribute(context, :command_processed_options, :defaults)
quote context: context do
Makina.Command.Defaults.command(unquote_splicing(arguments))
end
true ->
Module.delete_attribute(context, :command_processed_options)
Module.register_attribute(__CALLER__.module, :command_processed_options, accumulate: true)
quote context: context do
Makina.Command.Base.command(unquote_splicing(arguments))
end
end
end
end