139 lines
3.9 KiB
Elixir
139 lines
3.9 KiB
Elixir
defmodule Config.Reader do
|
|
@moduledoc """
|
|
API for reading config files defined with `Config`.
|
|
|
|
## As a provider
|
|
|
|
`Config.Reader` can also be used as a `Config.Provider`. A config
|
|
provider is used during releases to customize how applications are
|
|
configured. When used as a provider, it expects a single argument:
|
|
the configuration path (as outlined in `t:Config.Provider.config_path/0`)
|
|
for the file to be read and loaded during the system boot.
|
|
|
|
For example, if you expect the target system to have a config file
|
|
in an absolute path, you can add this inside the `def project` portion
|
|
of your `mix.exs`:
|
|
|
|
releases: [
|
|
demo: [
|
|
config_providers: [
|
|
{Config.Reader, "/etc/config.exs"}
|
|
]
|
|
]
|
|
]
|
|
|
|
Or if you want to read a custom path inside the release:
|
|
|
|
config_providers: [{Config.Reader, {:system, "RELEASE_ROOT", "/config.exs"}}]
|
|
|
|
You can also pass a keyword list of options to the reader,
|
|
where the `:path` is a required key:
|
|
|
|
config_providers: [
|
|
{Config.Reader,
|
|
path: "/etc/config.exs",
|
|
env: :prod,
|
|
imports: :disabled}
|
|
]
|
|
|
|
Remember Mix already loads `config/runtime.exs` by default.
|
|
For more examples and scenarios, see the `Config.Provider` module.
|
|
"""
|
|
|
|
@behaviour Config.Provider
|
|
|
|
@impl true
|
|
def init(opts) when is_list(opts) do
|
|
{path, opts} = Keyword.pop!(opts, :path)
|
|
Config.Provider.validate_config_path!(path)
|
|
{path, opts}
|
|
end
|
|
|
|
def init(path) do
|
|
init(path: path)
|
|
end
|
|
|
|
@impl true
|
|
def load(config, {path, opts}) do
|
|
merge(config, path |> Config.Provider.resolve_config_path!() |> read!(opts))
|
|
end
|
|
|
|
@doc """
|
|
Evaluates the configuration `contents` for the given `file`.
|
|
|
|
Accepts the same options as `read!/2`.
|
|
"""
|
|
@doc since: "1.11.0"
|
|
@spec eval!(Path.t(), binary, keyword) :: keyword
|
|
def eval!(file, contents, opts \\ [])
|
|
when is_binary(file) and is_binary(contents) and is_list(opts) do
|
|
Config.__eval__!(Path.expand(file), contents, opts) |> elem(0)
|
|
end
|
|
|
|
@doc """
|
|
Reads the configuration file.
|
|
|
|
## Options
|
|
|
|
* `:imports` - a list of already imported paths or `:disabled`
|
|
to disable imports
|
|
|
|
* `:env` - the environment the configuration file runs on.
|
|
See `Config.config_env/0` for sample usage
|
|
|
|
* `:target` - the target the configuration file runs on.
|
|
See `Config.config_target/0` for sample usage
|
|
|
|
"""
|
|
@doc since: "1.9.0"
|
|
@spec read!(Path.t(), keyword) :: keyword
|
|
def read!(file, opts \\ []) when is_binary(file) and is_list(opts) do
|
|
file = Path.expand(file)
|
|
Config.__eval__!(file, File.read!(file), opts) |> elem(0)
|
|
end
|
|
|
|
@doc """
|
|
Reads the given configuration file and returns the configuration
|
|
with its imports.
|
|
|
|
Accepts the same options as `read!/2`. Although note the `:imports`
|
|
option cannot be disabled in `read_imports!/2`.
|
|
"""
|
|
@doc since: "1.9.0"
|
|
@spec read_imports!(Path.t(), keyword) :: {keyword, [Path.t()]}
|
|
def read_imports!(file, opts \\ []) when is_binary(file) and is_list(opts) do
|
|
if opts[:imports] == :disabled do
|
|
raise ArgumentError, ":imports must be a list of paths"
|
|
end
|
|
|
|
file = Path.expand(file)
|
|
Config.__eval__!(file, File.read!(file), opts)
|
|
end
|
|
|
|
@doc """
|
|
Merges two configurations.
|
|
|
|
The configurations are merged together with the values in
|
|
the second one having higher preference than the first in
|
|
case of conflicts. In case both values are set to keyword
|
|
lists, it deep merges them.
|
|
|
|
## Examples
|
|
|
|
iex> Config.Reader.merge([app: [k: :v1]], [app: [k: :v2]])
|
|
[app: [k: :v2]]
|
|
|
|
iex> Config.Reader.merge([app: [k: [v1: 1, v2: 2]]], [app: [k: [v2: :a, v3: :b]]])
|
|
[app: [k: [v1: 1, v2: :a, v3: :b]]]
|
|
|
|
iex> Config.Reader.merge([app1: []], [app2: []])
|
|
[app1: [], app2: []]
|
|
|
|
"""
|
|
@doc since: "1.9.0"
|
|
@spec merge(keyword, keyword) :: keyword
|
|
def merge(config1, config2) when is_list(config1) and is_list(config2) do
|
|
Config.__merge__(config1, config2)
|
|
end
|
|
end
|