Read custom module attributes

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"

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

  @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

  defp lookup_styledoc(doc, kind) do
    Keyword.get_values(doc, kind)
    |> Enum.reduce(&Enum.into/2)

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!

1 Like

An easier way might be to have a use StyleDoc or so at the top then you could use the declarations. Instead of having them persist you could also just stuff it into a, oh, __styledoc__ function or so, then you could just iterate over all modules in the project and see if they have that function, if so then call it to get the information (I’d recommend returning a map/struct since you never know when you may want to return something else too instead of just styledoc comments).

Thanks for your reply, I’ll give the iterating over modules a go.