lib/kuddle/config/directory_provider.ex

defmodule Kuddle.Config.DirectoryProvider do
  @moduledoc """
  Use Kuddle as a config provider for a directory, if you want to load a single file see
  Kuddle.Config.Provider instead

  ## Example

      defp releases do
        [
          application: [
            config_providers: [
              {Kuddle.Config.Provider, [
                paths: [
                  {:system, "PATH_TO_CONFIG", "/default/path/to/kdl/config"}
                ],
                extensions: [".kdl", ".kuddle"]
              ]}
            ]
          ]
        ]
      end

  """
  import Kuddle.Config.Utils

  @behaviour Config.Provider

  @typedoc """
  The directory provider allows specifying multiple paths to get config files from, this path
  will be joined with the wildcard **/* and postfixed with the extensions.

  Example:

      [
        paths: [
          "/etc/my_application/config"
        ]
      ]

  """
  @type paths_option :: {:paths, [Config.Provider.config_path()]}

  @typedoc """
  Specify a list of extensions that should be treated as kdl files.

  Default: [".kdl"]

  Example:

      [
        extensions: [".kdl", ".kuddle"]
      ]

  """
  @type extensions_option :: {:extensions, [String.t()]}

  @type option :: paths_option()
                | extensions_option()

  @typedoc """
  Options supported by the DirectoryProvider

  Example:

      [
        paths: [
          "/etc/my_application/config"
        ],
        extensions: [".cfg.kdl", ".cfg.kuddle"]
      ]

  """
  @type options :: [option()]

  @impl true
  @spec init(options()) :: any()
  def init(opts) do
    config =
      Enum.reduce(opts, %{paths: [], extensions: [".kdl"]}, fn
        {:paths, paths}, acc ->
          %{acc | paths: paths}

        {:extensions, exts}, acc ->
          %{acc | extensions: exts}
      end)

    config
  end

  @impl true
  def load(config, state) do
    Enum.reduce(state.paths, config, fn root_path, config ->
      case resolve_root_path(root_path) do
        nil ->
          raise Kuddle.ConfigError, message: "unresolved path", reason: {:error, :unresolved_path}

        path ->
          case File.stat(path) do
            {:ok, %{type: :regular}} ->
              raise Kuddle.ConfigError, message: "path is directory", reason: {:error, :eisfile}

            {:ok, %{type: :directory}} ->
              Kuddle.Config.load_config_directory(path, state.extensions, config)

            {:error, reason} ->
              raise Kuddle.ConfigError, message: "path is inaccessible", reason: {:error, reason}
          end
      end
    end)
  end
end