Hey all, in our project we use @moduledoc
and @doc
to generate docs by running mix docs
.
I want to do something similar using custom attributes to generate a living style guide. For example running mix styleguide
which will read the @styledoc
attributes from modules, and compile them into static pages.
What would be a good approach to get custom attributes from modules?
####Attempt #1
Docs attributes can be retrieved using Code.get_docs/2
. For example:
{line, text} = Code.get_docs(MyModule, :moduledoc)
But this doesn’t work for custom attributes because the allowed kinds
are fixed.
Also the ExDc
chunk identifier seems to skip everything but docs related attributes.
####Attempt #2
I’ve tried Module.register_attribute/3
and Module.get_attribute/2
:
defmodule MyModule do
# Register the :styledoc attribute
Module.register_attribute(__MODULE__, :styledoc, accumulate: true, persist: true)
@styledoc "I need this from outside my module"
end
Module.get_attribute(MyModule, :styledoc)
But this doesn’t seem to be available during runtime:
could not call get_attribute on module MyModule because it was already compiled
####Attempt #3
Based on the source of Code.get_docs/2
I got a working example!
defmodule Styleguide do
@styledoc_kinds [:styledoc]
def get_styledoc(module, kind) when is_atom(module) and kind in @styledoc_kinds do
case :code.get_object_code(module) do
{_module, bin, _beam_path} ->
do_get_styledoc(bin, kind)
:error -> nil
end
end
@styledoc_chunk 'Attr'
defp do_get_styledoc(bin_or_path, kind) do
case :beam_lib.chunks(bin_or_path, [@styledoc_chunk]) do
{:ok, {_module, [{@styledoc_chunk, bin}]}} ->
lookup_styledoc(:erlang.binary_to_term(bin), kind)
{:error, :beam_lib, {:missing_chunk, _, @styledoc_chunk}} -> nil
end
end
defp lookup_styledoc(doc, kind) do
Keyword.get_values(doc, kind)
|> Enum.reduce(&Enum.into/2)
end
end
Which allows me to retrieve the :styledoc
attributes like this:
Styleguide.get_styledoc(MyModule, :styledoc)
Note that I had to use the Attr
chunk identifier instead of ExDc
.
Also the :styledoc
attribute has to be registered for the module (see example in Attempt #2).
Other ideas on how to do this are welcome!