Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find all modules that adopted behavior

Tags:

elixir

Is it possible to find each loaded module that have adopted certain behavior?

I'm building a very simple chat Bot and I want to make some cool commands, but to achieve that i need a way to implement multiple commands, preferably without hardcoding them.

Each command would be a function that takes three params (message, author, chat_channel_ref) and returns true or false whether it matched and does something.

When i browsing through the Elixir tutorial i found Behaviors which may nicely suit my needs if i could found all modules that have adopted them. Have anyone of you did it before? What else could i do? I also thought about "use" (in use i would execute code to add current module to a list held by agent).

like image 998
Haito Avatar asked Apr 05 '16 17:04

Haito


1 Answers

This is an excerpt from my exrm project, which does basically exactly that: it finds any module which implements the plugin behaviour:

  @doc """
  Loads all plugins in all code paths.
  """
  @spec load_all() :: [] | [atom]
  def load_all, do: get_plugins(ReleaseManager.Plugin)

  # Loads all modules that extend a given module in the current code path.
  @spec get_plugins(atom) :: [] | [atom]
  defp get_plugins(plugin_type) when is_atom(plugin_type) do
    available_modules(plugin_type) |> Enum.reduce([], &load_plugin/2)
  end

  defp load_plugin(module, modules) do
    if Code.ensure_loaded?(module), do: [module | modules], else: modules
  end

  defp available_modules(plugin_type) do
    # Ensure the current projects code path is loaded
    Mix.Task.run("loadpaths", [])
    # Fetch all .beam files
    Path.wildcard(Path.join([Mix.Project.build_path, "**/ebin/**/*.beam"]))
    # Parse the BEAM for behaviour implementations
    |> Stream.map(fn path ->
      {:ok, {mod, chunks}} = :beam_lib.chunks('#{path}', [:attributes])
      {mod, get_in(chunks, [:attributes, :behaviour])}
    end)
    # Filter out behaviours we don't care about and duplicates
    |> Stream.filter(fn {_mod, behaviours} -> is_list(behaviours) && plugin_type in behaviours end)
    |> Enum.uniq
    |> Enum.map(fn {module, _} -> module end)
  end
like image 179
bitwalker Avatar answered Oct 17 '22 17:10

bitwalker