defmodule Kvasir do @moduledoc """ Documentation for Kvasir. """ @doc ~S""" Index all events and other registries. """ @spec create_registries :: :ok def create_registries do create_event_registry() create_auto_start_registry() if Code.ensure_compiled?(Kvasir.Agent), do: apply(Kvasir.Agent, :create_registries, []) :ok end defp create_event_registry do events = :application.loaded_applications() |> Enum.flat_map(fn {app, _, _} -> case :application.get_key(app, :modules) do :undefined -> [] {:ok, modules} -> modules end end) |> Enum.filter(&String.starts_with?(to_string(&1), "Elixir.Kvasir.Event.Registry.")) ensure_unique_events!(events) registry = Enum.into(events, %{}, &{&1.type(), &1.module()}) Code.compiler_options(ignore_module_conflict: true) Module.create( Kvasir.Event.Registry, quote do @moduledoc ~S""" Kvasir Event Registry. """ @doc ~S""" Lookup events. """ @spec lookup(String.t()) :: module | nil def lookup(type), do: Map.get(events(), type) @doc ~S""" List all events. """ @spec list :: [module] def list, do: Map.values(events()) @doc ~S""" List all events matching the filter. """ @spec list(filter :: Keyword.t()) :: [module] def list(filter) do events = events() events = if ns = filter[:namespace] do ns = to_string(ns) <> "." Enum.filter(events, &String.starts_with?(to_string(elem(&1, 1)), ns)) else events end events = if ns = filter[:type_namespace] do ns = ns <> "." Enum.filter(events, &String.starts_with?(elem(&1, 0), ns)) else events end Enum.map(events, &elem(&1, 1)) end @doc ~S""" All available events indexed on type. """ @spec events :: map def events, do: unquote(Macro.escape(registry)) end, Macro.Env.location(__ENV__) ) Code.compiler_options(ignore_module_conflict: false) end defp ensure_unique_events!(events) do duplicates = events |> Enum.group_by(& &1.type) |> Enum.filter(&(Enum.count(elem(&1, 1)) > 1)) if duplicates == [] do :ok else error = duplicates |> Enum.map(fn {k, v} -> " \"#{k}\":\n#{v |> Enum.map(&" * #{inspect(&1.module)}") |> Enum.join("\n")}" end) |> Enum.join("\n\n") raise "Duplicate Event Types\n\n" <> error end end defp create_auto_start_registry do children = :application.loaded_applications() |> Enum.flat_map(fn {app, _, _} -> case :application.get_key(app, :modules) do :undefined -> [] {:ok, modules} -> modules end end) |> Enum.filter(&String.starts_with?(to_string(&1), "Elixir.Kvasir.Util.AutoStart.")) registry = Enum.group_by(children, & &1.client, & &1.module) Code.compiler_options(ignore_module_conflict: true) Module.create( Kvasir.Util.AutoStart, quote do @moduledoc ~S""" AutoStart registry. """ @doc ~S""" Start all auto start modules for a client """ @spec start_link(module) :: term def start_link(client) do if children = auto_starts()[client] do Supervisor.start_link(children, strategy: :one_for_one, name: Module.concat(client, AutoStartSupervisor) ) end end @doc ~S""" All available auto starts based on client. """ @spec auto_starts :: map def auto_starts, do: unquote(Macro.escape(registry)) end, Macro.Env.location(__ENV__) ) Code.compiler_options(ignore_module_conflict: false) end end