defmodule Witchcraft.Internal do
@moduledoc """
A module for handling `use Witchcraft` and other modules
Provides support for overriding `Kernel` functions and for auto dependencies using via `__using__/1` macro.
"""
@doc ~S"""
Generates `Kernel` import and `__using__/1` macro in module where used.
## Options
- `:overrides` – List of overrides in module where used
- `:deps` – List of modules that will be used in generated `__using__/1` macro
## Generated `__using__(opts \\ [])` macro supports the following options:
- `:override_kernel` – If true, overrides function from `Kernel` in module where used. Defaults to true
- `:except` and `:only` – Keyword of functions (Like in [import](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#import/2-selector)). Example: `[fun: <arity>]` where `<arity>` is function `fun` arity
"""
defmacro __using__(opts \\ []) do
overrides = Keyword.get(opts, :overrides, [])
deps = Keyword.get(opts, :deps, [])
quote do
@overrides unquote(overrides)
import Kernel, except: unquote(overrides)
defmacro __using__(opts \\ []) do
Witchcraft.Internal.import_helper(opts, unquote(overrides), unquote(deps), __MODULE__)
end
end
end
@doc false
def import_helper(opts, overrides, deps, module) do
excepts = Keyword.get(opts, :except, [])
only = Keyword.get(opts, :only)
deps_quoted = use_multi(deps, opts)
if Keyword.get(opts, :override_kernel, true) do
kernel_imports = kernel_imports(overrides -- excepts, only)
module_imports = module_imports(excepts, only)
quote do
import Kernel, unquote(kernel_imports)
unquote(deps_quoted)
import unquote(module), unquote(module_imports)
end
else
module_imports = module_imports(Enum.uniq(overrides ++ excepts), only)
quote do
unquote(deps_quoted)
import unquote(module), unquote(module_imports)
end
end
end
defp use_multi(modules, opts) do
for module <- modules do
quote do
use unquote(module), unquote(opts)
end
end
end
defp kernel_imports(excepts, nil), do: Macro.escape(except: excepts)
defp kernel_imports(_, only), do: Macro.escape(except: only)
defp module_imports(excepts, nil), do: Macro.escape(except: excepts)
defp module_imports(excepts, only), do: Macro.escape(only: only -- excepts)
end