Guest User

Untitled

a guest
Nov 17th, 2017
72
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.93 KB | None | 0 0
  1. defmodule Default.Behaviour do
  2. @moduledoc """
  3. Creates a behaviour that carries its own default implementation.
  4.  
  5. When used into a behaviour module, when that module in turn is used, all functions
  6. defined on it are given to the using module.
  7.  
  8. This allows you to have concrete implementations of the behaviour's default functionality
  9. for testing, unlike cramming them all into a __using__ macro.
  10.  
  11. When the behaviour is used all provided functions are correctly annotated with their
  12. `@impl` referring to it, and all made overridable.
  13.  
  14. You can provide two options when using the resulting behaviour module:
  15.  
  16. - `:docs` (default: true) will copy over the documentation for each function
  17.  
  18. - `:inline` (default: false) will inline the default function's implementation,
  19. instead of just proxying to the implementation on the behaviour module, which is the
  20. standard behaviour.
  21.  
  22. Example:
  23.  
  24. ```elixir
  25. defmodule Custom.Behaviour do
  26. use Default.Behaviour
  27.  
  28. @callback foo :: atom
  29. @doc "Computes foo, returns bar."
  30. def foo, do: :bar
  31. end
  32.  
  33. defmodule Fizz do
  34. use Custom.Behaviour
  35. end
  36.  
  37. defmodule Buzz do
  38. use Custom.Behaviour
  39. @doc "Computes foo, returns baz."
  40. def foo, do: :baz
  41. end
  42.  
  43. Fizz.foo #=> :bar
  44. Buzz.foo #=> :baz
  45. ```
  46. """
  47.  
  48. def __on_definition__(env, kind, name, params, guards, body) do
  49. doc = Module.get_attribute(env.module, :doc)
  50. Module.put_attribute(env.module, :__functions__, {doc, kind, name, params, guards, body})
  51. end
  52.  
  53. defmacro __before_compile__(_env) do
  54. quote do
  55. @doc false
  56. def __functions__, do: @__functions__
  57. end
  58. end
  59.  
  60. defmacro __using__(opts \\ []) do
  61. code = Keyword.get(opts, :do)
  62. quote do
  63.  
  64. Module.register_attribute(__MODULE__, :__functions__, accumulate: true)
  65. @on_definition Default.Behaviour
  66. @before_compile Default.Behaviour
  67.  
  68. defmacro __using__(opts \\ []) do
  69. docs = Keyword.get(opts, :docs, true)
  70. inline = Keyword.get(opts, :inline, false)
  71.  
  72. defaults = for {doc, kind, name, params, guards, body} <- __MODULE__.__functions__ do
  73. info = %{module: __MODULE__, kind: kind, docs: docs, inline: inline}
  74. Default.Behaviour.compose_default(info, doc, name, params, guards, body)
  75. end
  76.  
  77. [
  78. quote(do: @behaviour __MODULE__),
  79. defaults,
  80. quote(do: defoverridable __MODULE__),
  81. unquote(code),
  82. ] |> List.flatten |> Enum.filter(&(&1))
  83.  
  84. end
  85. end
  86. end
  87.  
  88. # Ignore macros
  89. def compose_default(%{kind: kind}, _doc, _name, _params, _guards, _body)
  90. when not kind in ~w[def defp]a, do: nil
  91.  
  92. # If we are inlining, we may need any and all functions, private ones included
  93. def compose_default(%{inline: true} = info, doc, name, params, guards, body) do
  94. compose_definition(info, doc, name, params, guards, body)
  95. end
  96.  
  97. # Otherwise we are only interested in public functions
  98. def compose_default(%{kind: :def, module: module} = info, doc, name, params, guards, _body) do
  99. delegate = compose_delegate(module, name, params)
  100. [
  101. compose_module_attribute(:impl, module),
  102. compose_definition(info, doc, name, params, guards, delegate),
  103. ]
  104. end
  105.  
  106. # Throw away anything else
  107. def compose_default(_info, _doc, _name, _params, _guards, _body), do: nil
  108.  
  109. defp compose_delegate(module, name, params) do
  110. args = Enum.map(params, fn
  111. {:\\, _, [arg, _default]} -> arg
  112. arg -> arg
  113. end)
  114. compose_application(module, name, args)
  115. end
  116.  
  117. defp compose_definition(info = %{kind: :def}, doc, name, params, guards, body) do
  118. [
  119. compose_docs(info, doc),
  120. compose_function(:def, name, params, guards, body),
  121. ]
  122. end
  123.  
  124. defp compose_definition(%{kind: :defp}, _doc, name, params, guards, body) do
  125. compose_function(:defp, name, params, guards, body)
  126. end
  127.  
  128. defp compose_docs(%{docs: false} = info, _doc), do: compose_docs(info, false)
  129. defp compose_docs(_info, {_, doc}) when is_binary(doc) do
  130. compose_module_attribute(:doc, doc)
  131. end
  132. defp compose_docs(_info, _doc) do
  133. compose_module_attribute(:doc, false)
  134. end
  135.  
  136. defp compose_function(:def, name, params, guards, body) do
  137. quote do
  138. def unquote(compose_definition(name, params, guards)) do
  139. unquote(body)
  140. end
  141. end
  142. end
  143.  
  144. defp compose_function(:defp, name, params, guards, body) do
  145. quote do
  146. defp unquote(compose_definition(name, params, guards)) do
  147. unquote(body)
  148. end
  149. end
  150. end
  151.  
  152. defp compose_definition(name, params, []) do
  153. compose_call(name, params)
  154. end
  155. defp compose_definition(name, params, guards) do
  156. Enum.reduce(guards, compose_call(name, params), fn guard, node ->
  157. {:when, [], [node, guard]}
  158. end)
  159. end
  160.  
  161. defp compose_call(name, params) do
  162. {name, [], params}
  163. end
  164.  
  165. defp compose_module_attribute(attribute, value) do
  166. {:@, [], [
  167. {attribute, [], [value]}
  168. ]}
  169. end
  170.  
  171. defp compose_application(module, function, args) do
  172. {:apply, [], [module, function, args]}
  173. end
  174.  
  175. end
Add Comment
Please, Sign In to add comment