/
config.ex
295 lines (218 loc) · 8.1 KB
/
config.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
defmodule Mix.Config do
# TODO: Convert them to hard deprecations on v1.15
@moduledoc deprecated: "Use Config and Config.Reader instead"
@moduledoc ~S"""
A simple configuration API and functions for managing config files.
This module is deprecated, use the modules `Config` and `Config.Reader`
from Elixir's standard library instead.
## Setting configuration
Most commonly, this module is used to define your own configuration:
use Mix.Config
config :root_key,
key1: "value1",
key2: "value2"
import_config "#{Mix.env()}.exs"
`use Mix.Config` will import the functions `config/2`, `config/3`
and `import_config/1` to help you manage your configuration.
## Evaluating configuration
Once a configuration is written to a file, the functions in this
module can be used to read and merge said configuration. The `eval!/2`
function allows you to evaluate a given configuration file and the `merge/2`
function allows you to deep merge the results of multiple configurations. Those
functions should not be invoked by users writing configurations but
rather by library authors.
## Examples
The most common use of `Mix.Config` is to define application
configuration so that `Application.get_env/3` and other `Application`
functions can be used to retrieve or further change them.
Application config files are typically placed in the `config/`
directory of your Mix projects. For example, the following config
# config/config.exs
config :my_app, :key, "value"
will be automatically loaded by Mix and persisted into the
`:my_app`'s application environment, which can be accessed in
its source code as follows:
"value" = Application.fetch_env!(:my_app, :key1)
"""
@doc false
@deprecated "Use the Config module instead"
defmacro __using__(_) do
quote do
import Mix.Config, only: [config: 2, config: 3, import_config: 1]
end
end
@doc """
Configures the given `root_key`.
Keyword lists are always deep merged.
## Examples
The given `opts` are merged into the existing configuration
for the given `root_key`. Conflicting keys are overridden by the
ones specified in `opts`. For example, the application
configuration below
config :logger,
level: :warn,
backends: [:console]
config :logger,
level: :info,
truncate: 1024
will have a final configuration for `:logger` of:
[level: :info, backends: [:console], truncate: 1024]
"""
@doc deprecated: "Use the Config module instead"
defdelegate config(root_key, opts), to: Config
@doc """
Configures the given `key` for the given `root_key`.
Keyword lists are always deep merged.
## Examples
The given `opts` are merged into the existing values for `key`
in the given `root_key`. Conflicting keys are overridden by the
ones specified in `opts`. For example, the application
configuration below
config :ecto, Repo,
log_level: :warn,
adapter: Ecto.Adapters.Postgres
config :ecto, Repo,
log_level: :info,
pool_size: 10
will have a final value of the configuration for the `Repo`
key in the `:ecto` application of:
[log_level: :info, pool_size: 10, adapter: Ecto.Adapters.Postgres]
"""
@doc deprecated: "Use the Config module instead"
defdelegate config(root_key, key, opts), to: Config
@doc ~S"""
Imports configuration from the given file or files.
If `path_or_wildcard` is a wildcard, then all the files
matching that wildcard will be imported; if no file matches
the wildcard, no errors are raised. If `path_or_wildcard` is
not a wildcard but a path to a single file, then that file is
imported; in case the file doesn't exist, an error is raised.
If path/wildcard is a relative path/wildcard, it will be expanded
relatively to the directory the current configuration file is in.
## Examples
This is often used to emulate configuration across environments:
import_config "#{Mix.env()}.exs"
Or to import files from children in umbrella projects:
import_config "../apps/*/config/config.exs"
"""
@doc deprecated: "Use the Config module instead"
defmacro import_config(path_or_wildcard) do
quote do
Mix.Config.__import__!(unquote(path_or_wildcard), __DIR__)
end
end
@doc false
def __import__!(path_or_wildcard, dir) do
path_or_wildcard = Path.expand(path_or_wildcard, dir)
paths =
if String.contains?(path_or_wildcard, ~w(* ? [ {)) do
Path.wildcard(path_or_wildcard)
else
[path_or_wildcard]
end
for path <- paths do
Config.__import__!(path)
end
:ok
end
## Mix API
@doc """
Evaluates the given configuration file.
It accepts a list of `imported_paths` that should raise if attempted
to be imported again (to avoid recursive imports).
It returns a tuple with the configuration and the imported paths.
"""
@deprecated "Use Config.Reader.read_imports!/2 instead"
def eval!(file, imported_paths \\ []) do
Config.Reader.read_imports!(file,
imports: imported_paths,
env: Mix.env(),
target: Mix.target()
)
end
@doc """
Reads the configuration file.
The same as `eval!/2` but only returns the configuration
in the given file, without returning the imported paths.
It exists for convenience purposes. For example, you could
invoke it inside your `mix.exs` to read some external data
you decided to move to a configuration file:
subsystem: Mix.Config.read!("rel/subsystem.exs")
"""
@deprecated "Use Config.Reader.read!/2 instead"
@spec read!(Path.t(), [Path.t()]) :: keyword
def read!(file, imported_paths \\ []) do
Config.Reader.read!(file, imports: imported_paths, env: Mix.env(), target: Mix.target())
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> Mix.Config.merge([app: [k: :v1]], [app: [k: :v2]])
[app: [k: :v2]]
iex> Mix.Config.merge([app: [k: [v1: 1, v2: 2]]], [app: [k: [v2: :a, v3: :b]]])
[app: [k: [v1: 1, v2: :a, v3: :b]]]
iex> Mix.Config.merge([app1: []], [app2: []])
[app1: [], app2: []]
"""
@deprecated "Use Config.Reader.merge/2 instead"
def merge(config1, config2) do
Config.__merge__(config1, config2)
end
@doc """
Persists the given configuration by modifying
the configured applications environment.
`config` should be a list of `{app, app_config}` tuples or a
`%{app => app_config}` map where `app` are the applications to
be configured and `app_config` are the configuration (as key-value
pairs) for each of those applications.
Returns the configured applications.
## Examples
Mix.Config.persist(logger: [level: :error], my_app: [my_config: 1])
#=> [:logger, :my_app]
"""
@deprecated "Use Application.put_all_env/2 instead"
def persist(config) do
Application.put_all_env(config, persistent: true)
end
@doc false
@deprecated "Use the Config.Reader module instead"
def read_wildcard!(path, loaded_paths \\ []) do
paths =
if String.contains?(path, ~w(* ? [ {)) do
Path.wildcard(path)
else
[path]
end
Enum.reduce(paths, [], &merge(&2, read!(&1, loaded_paths)))
end
@doc false
@deprecated "Manually validate the data instead"
def validate!(config) do
validate!(config, "runtime")
end
defp validate!(config, file) do
if is_list(config) do
Enum.all?(config, fn
{app, value} when is_atom(app) ->
if Keyword.keyword?(value) do
true
else
raise ArgumentError,
"expected #{Path.relative_to_cwd(file)} config for app #{inspect(app)} " <>
"to return keyword list, got: #{inspect(value)}"
end
_ ->
false
end)
else
raise ArgumentError,
"expected #{Path.relative_to_cwd(file)} config to return " <>
"keyword list, got: #{inspect(config)}"
end
config
end
end