Current section
Files
Jump to
Current section
Files
lib/knigge/code.ex
defmodule Knigge.Code do
@moduledoc """
Internal module responsible of injecting the delegations into the calling module.
Injects the actual delegations to the implementing module. For this it gets
registered as a `before_compile`-hook from where it fetches all callbacks from
the behaviour and generates delegating functions for each callback.
It also defines the `defdefault` macro.
"""
alias Knigge.Code.{Default, Delegate}
alias Knigge.Error
alias Knigge.Warnings, as: Warn
defmacro defdefault({_name, _meta, _args} = definition, do: block) do
do_defdefault(definition, do: block)
end
defp do_defdefault({name, _meta, args}, do: block) when is_list(args) do
key = {name, length(args)}
value = {Macro.escape(args), Macro.escape(block)}
quote do
@__knigge__ {:defdefault, {unquote(key), unquote(value)}}
end
end
# The `args` are not a list for definitions like `defdefault my_default, do: :ok`
# where no parenthesis follow after `my_default`
defp do_defdefault({name, meta, _args}, do: block) do
do_defdefault({name, meta, []}, do: block)
end
defmacro __before_compile__(%{module: module} = env) do
generate(module, env)
end
# TODO: Refactor this into a separate module and pass in the arguments as a struct to make it more easily testable
def generate(module, env) do
behaviour = get_behaviour(module, env)
callbacks = get_callbacks(behaviour)
optional_callbacks = get_optional_callbacks(behaviour)
delegate_at_runtime? = get_option(module, :delegate_at_runtime?)
do_not_delegate = get_option(module, :do_not_delegate)
definitions = get_definitions(module)
defaults = get_defaults(module)
for callback <- callbacks do
cond do
callback in do_not_delegate ->
:ok
callback in definitions ->
Warn.definition_matching_callback(module, callback)
has_default?(defaults, callback) ->
if callback not in optional_callbacks do
Error.default_for_required_callback!(env)
end
for default <- get_defaults(defaults, callback) do
Default.callback_to_defdefault(callback,
from: module,
default: default,
delegate_at_runtime?: delegate_at_runtime?
)
end
true ->
Delegate.callback_to_defdelegate(callback,
from: module,
delegate_at_runtime?: delegate_at_runtime?
)
end
end
end
defp get_behaviour(module, env) do
module
|> Knigge.options!()
|> Knigge.Behaviour.fetch!()
|> Knigge.Module.ensure_exists!(env)
end
defp get_callbacks(module) do
Knigge.Behaviour.callbacks(module)
end
defp get_optional_callbacks(module) do
Knigge.Behaviour.optional_callbacks(module)
end
defp get_option(module, key) do
module
|> Knigge.options!()
|> Map.get(key)
end
defp get_definitions(module) do
Module.definitions_in(module)
end
defp get_defaults(module) do
module
|> Module.get_attribute(:__knigge__)
|> Keyword.get_values(:defdefault)
end
defp has_default?(defaults, callback) do
default = Enum.find(defaults, &match?({^callback, _}, &1))
not is_nil(default)
end
defp get_defaults(defaults, callback) do
defaults
|> Enum.filter(&match?({^callback, _}, &1))
|> Enum.map(fn {_, default} -> default end)
|> Enum.reverse()
end
end