defmodule AttrReader do @moduledoc """ Can define module attributes getter automatically. """ # TODO: 1.20.0  ~ Module.reserved_attributes()に切り替え @reserved_attributes [ :after_compile, :before_compile, :behaviour, :callback, :compile, :deprecated, :derive, :dialyzer, :doc, :enforce_keys, :external_resource, :file, :impl, :macrocallback, :moduledoc, :on_definition, :on_load, :opaque, :optional_callbacks, :spec, :type, :typedoc, :typep, :vsn ] @doc """ Defines getters for all custom module attributes if used. And writes getter docs. ## Examples iex> defmodule UseAttrReaderForDoc do ...> @foo "foo" ...> use AttrReader ...> @bar :bar ...> end iex> UseAttrReaderForDoc.foo() "foo" iex> UseAttrReaderForDoc.bar() :bar iex> defmodule UseAttrReaderForDoc do ...> @foo "foo" ...> use AttrReader, only: [:foo] ...> @bar :bar ...> end iex> UseAttrReaderForDoc.foo() "foo" iex> UseAttrReaderForDoc.bar() ** (UndefinedFunctionError) function AttrReaderTest.UseAttrReaderForDoc.bar/0 is undefined or private iex> defmodule UseAttrReaderForDoc do ...> @foo "foo" ...> use AttrReader, except: [:foo] ...> @bar :bar ...> end iex> UseAttrReaderForDoc.bar() :bar iex> UseAttrReaderForDoc.foo() ** (UndefinedFunctionError) function AttrReaderTest.UseAttrReaderForDoc.foo/0 is undefined or private """ defmacro __using__(opts \\ []) do only = opts |> Keyword.get(:only) except = opts |> Keyword.get(:except) quote do @before_compile_opts [only: unquote(only), except: unquote(except)] @before_compile AttrReader end end defmacro __before_compile__(env) do opts = Module.get_attribute(env.module, :before_compile_opts) only = opts |> Keyword.get(:only) except = opts |> Keyword.get(:except) attributes = attributes_in(env.module) |> Enum.reject(&(&1 in (@reserved_attributes ++ [:before_compile_opts]))) cond do only -> attributes |> Enum.filter(&(&1 in only)) except -> attributes |> Enum.reject(&(&1 in except)) true -> attributes end |> Enum.map(fn attribute -> quote do @doc """ Gets @#{unquote(attribute)}. ## Examples iex> #{unquote(env.module)}.#{unquote(attribute)}() #{unquote(Module.get_attribute(env.module, attribute)) |> inspect()} """ def unquote(attribute)() do unquote(Module.get_attribute(env.module, attribute)) end end end) end @doc """ Sets module attribute and defines getter. And writes getter docs. ## Examples iex> defmodule AttrReaderMacroForDoc do ...> AttrReader.define @foo ...> AttrReader.define @bar, "bar" ...> AttrReader.define @baz, :baz ...> end iex> AttrReaderMacroForDoc.foo() nil iex> AttrReaderMacroForDoc.bar() "bar" iex> AttrReaderMacroForDoc.baz() :baz """ defmacro define(attribute, value \\ nil) do [first | _] = elem(attribute, 2) attr_key = first |> elem(0) quote do @attar_value unquote(value) @doc """ Gets @#{unquote(attr_key)}. ## Examples iex> #{unquote(__MODULE__)}.#{unquote(attr_key)}() #{unquote(value) |> inspect()} """ def unquote(attr_key)() do @attar_value end end end defp attributes_in(module) when is_atom(module) do {set, _} = :elixir_module.data_tables(module) :ets.select(set, [{{:"$1", :_, :_}, [{:is_atom, :"$1"}], [:"$1"]}]) end end