Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- defmodule Default.Behaviour do
- @moduledoc """
- Creates a behaviour that carries its own default implementation.
- When used into a behaviour module, when that module in turn is used, all functions
- defined on it are given to the using module.
- This allows you to have concrete implementations of the behaviour's default functionality
- for testing, unlike cramming them all into a __using__ macro.
- When the behaviour is used all provided functions are correctly annotated with their
- `@impl` referring to it, and all made overridable.
- You can provide two options when using the resulting behaviour module:
- - `:docs` (default: true) will copy over the documentation for each function
- - `:inline` (default: false) will inline the default function's implementation,
- instead of just proxying to the implementation on the behaviour module, which is the
- standard behaviour.
- Example:
- ```elixir
- defmodule Custom.Behaviour do
- use Default.Behaviour
- @callback foo :: atom
- @doc "Computes foo, returns bar."
- def foo, do: :bar
- end
- defmodule Fizz do
- use Custom.Behaviour
- end
- defmodule Buzz do
- use Custom.Behaviour
- @doc "Computes foo, returns baz."
- def foo, do: :baz
- end
- Fizz.foo #=> :bar
- Buzz.foo #=> :baz
- ```
- """
- def __on_definition__(env, kind, name, params, guards, body) do
- doc = Module.get_attribute(env.module, :doc)
- Module.put_attribute(env.module, :__functions__, {doc, kind, name, params, guards, body})
- end
- defmacro __before_compile__(_env) do
- quote do
- @doc false
- def __functions__, do: @__functions__
- end
- end
- defmacro __using__(opts \\ []) do
- code = Keyword.get(opts, :do)
- quote do
- Module.register_attribute(__MODULE__, :__functions__, accumulate: true)
- @on_definition Default.Behaviour
- @before_compile Default.Behaviour
- defmacro __using__(opts \\ []) do
- docs = Keyword.get(opts, :docs, true)
- inline = Keyword.get(opts, :inline, false)
- defaults = for {doc, kind, name, params, guards, body} <- __MODULE__.__functions__ do
- info = %{module: __MODULE__, kind: kind, docs: docs, inline: inline}
- Default.Behaviour.compose_default(info, doc, name, params, guards, body)
- end
- [
- quote(do: @behaviour __MODULE__),
- defaults,
- quote(do: defoverridable __MODULE__),
- unquote(code),
- ] |> List.flatten |> Enum.filter(&(&1))
- end
- end
- end
- # Ignore macros
- def compose_default(%{kind: kind}, _doc, _name, _params, _guards, _body)
- when not kind in ~w[def defp]a, do: nil
- # If we are inlining, we may need any and all functions, private ones included
- def compose_default(%{inline: true} = info, doc, name, params, guards, body) do
- compose_definition(info, doc, name, params, guards, body)
- end
- # Otherwise we are only interested in public functions
- def compose_default(%{kind: :def, module: module} = info, doc, name, params, guards, _body) do
- delegate = compose_delegate(module, name, params)
- [
- compose_module_attribute(:impl, module),
- compose_definition(info, doc, name, params, guards, delegate),
- ]
- end
- # Throw away anything else
- def compose_default(_info, _doc, _name, _params, _guards, _body), do: nil
- defp compose_delegate(module, name, params) do
- args = Enum.map(params, fn
- {:\\, _, [arg, _default]} -> arg
- arg -> arg
- end)
- compose_application(module, name, args)
- end
- defp compose_definition(info = %{kind: :def}, doc, name, params, guards, body) do
- [
- compose_docs(info, doc),
- compose_function(:def, name, params, guards, body),
- ]
- end
- defp compose_definition(%{kind: :defp}, _doc, name, params, guards, body) do
- compose_function(:defp, name, params, guards, body)
- end
- defp compose_docs(%{docs: false} = info, _doc), do: compose_docs(info, false)
- defp compose_docs(_info, {_, doc}) when is_binary(doc) do
- compose_module_attribute(:doc, doc)
- end
- defp compose_docs(_info, _doc) do
- compose_module_attribute(:doc, false)
- end
- defp compose_function(:def, name, params, guards, body) do
- quote do
- def unquote(compose_definition(name, params, guards)) do
- unquote(body)
- end
- end
- end
- defp compose_function(:defp, name, params, guards, body) do
- quote do
- defp unquote(compose_definition(name, params, guards)) do
- unquote(body)
- end
- end
- end
- defp compose_definition(name, params, []) do
- compose_call(name, params)
- end
- defp compose_definition(name, params, guards) do
- Enum.reduce(guards, compose_call(name, params), fn guard, node ->
- {:when, [], [node, guard]}
- end)
- end
- defp compose_call(name, params) do
- {name, [], params}
- end
- defp compose_module_attribute(attribute, value) do
- {:@, [], [
- {attribute, [], [value]}
- ]}
- end
- defp compose_application(module, function, args) do
- {:apply, [], [module, function, args]}
- end
- end
Add Comment
Please, Sign In to add comment