lib/cloister/manager.ex

defmodule Cloister.Manager do
  @moduledoc """
  Use this module to start _Cloister_ manually inside the application
  supervision tree instead of running it as an application (default.)

  This is not recommended and requires better understanding of internals.
  Also, `:cloister` must be put into `:included_applications` section
  of your application `mix.exs` to prevent the application from starting up
  during the dependent applications starting phase.
  """
  use Supervisor

  @doc "Starts the cloister manager process in the supervision tree"
  @spec start_link(opts :: keyword()) :: GenServer.on_start()
  def start_link(opts) do
    :ok = Application.ensure_started(:libring, :permanent)

    {state, opts} = Keyword.pop(opts, :state, [])
    Supervisor.start_link(__MODULE__, state, Keyword.put_new(opts, :name, __MODULE__))
  end

  @doc false
  @impl Supervisor
  def init(state) do
    {monitor_opts, state} = Keyword.pop(state, :monitor_opts, [])

    {additional_modules, state} =
      Keyword.pop(
        state,
        :additional_modules,
        Application.get_env(:cloister, :additional_modules, [])
      )

    additional_modules = Enum.filter(additional_modules, &ensure_compiled?/1)

    children =
      [
        {Cloister.Monitor, [{:state, state} | monitor_opts]},
        Cloister,
        Cloister.Node
      ] ++ additional_modules

    Supervisor.init(children, strategy: :rest_for_one)
  end

  @spec ensure_compiled?(module()) :: boolean()
  defp ensure_compiled?(module),
    do: match?({:module, ^module}, Code.ensure_compiled(module))
end