Packages
igniter
0.3.7
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.30
0.6.29
0.6.28
0.6.27
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.52
0.5.51
0.5.50
0.5.49
0.5.48
0.5.47
0.5.46
0.5.45
0.5.44
0.5.43
0.5.42
0.5.41
0.5.40
0.5.39
0.5.38
0.5.37
0.5.36
0.5.35
0.5.34
0.5.33
0.5.32
0.5.31
0.5.30
0.5.29
0.5.28
0.5.27
0.5.26
0.5.25
0.5.24
0.5.23
0.5.22
0.5.21
0.5.20
0.5.19
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.78
0.3.77
0.3.76
0.3.75
0.3.74
0.3.73
0.3.72
0.3.71
0.3.70
0.3.69
0.3.68
0.3.67
0.3.66
0.3.65
0.3.64
0.3.63
0.3.62
0.3.61
0.3.60
0.3.59
0.3.58
0.3.57
0.3.56
0.3.55
0.3.54
0.3.53
0.3.52
0.3.51
0.3.50
0.3.49
0.3.48
0.3.47
0.3.46
0.3.45
0.3.44
0.3.43
0.3.42
0.3.41
0.3.40
0.3.39
0.3.38
0.3.37
0.3.36
0.3.35
0.3.34
0.3.33
0.3.32
0.3.31
0.3.30
0.3.29
0.3.28
0.3.27
0.3.26
0.3.25
0.3.24
0.3.23
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A code generation and project patching framework
Current section
Files
Jump to
Current section
Files
lib/igniter/project/config.ex
defmodule Igniter.Project.Config do
@moduledoc "Codemods and utilities for modifying Elixir config files."
require Igniter.Code.Function
alias Igniter.Code.Common
alias Sourceror.Zipper
@doc """
Sets a config value in the given configuration file, if it is not already set.
## Opts
* `failure_message` - A message to display to the user if the configuration change is unsuccessful.
"""
@spec configure_new(Igniter.t(), Path.t(), atom(), list(atom), term(), opts :: Keyword.t()) ::
Igniter.t()
def configure_new(igniter, file_path, app_name, config_path, value, opts \\ []) do
configure(
igniter,
file_path,
app_name,
config_path,
value,
Keyword.put(opts, :updater, &{:ok, &1})
)
end
@doc """
Sets a config value in the given configuration file, updating it with `updater` if it is already set.
## Opts
* `:updater` - A function that takes a zipper at a currently configured value and returns a new zipper with the value updated.
* `failure_message` - A message to display to the user if the configuration change is unsuccessful.
"""
@spec configure(
Igniter.t(),
Path.t(),
atom(),
list(atom),
term(),
opts :: Keyword.t()
) :: Igniter.t()
def configure(igniter, file_name, app_name, config_path, value, opts \\ []) do
file_contents = "import Config\n"
file_path = Path.join("config", file_name)
config_path = List.wrap(config_path)
value =
case value do
{:code, value} -> value
value -> Macro.escape(value)
end
updater = opts[:updater] || fn zipper -> {:ok, Common.replace_code(zipper, value)} end
igniter
|> ensure_default_configs_exist(file_path)
|> Igniter.include_or_create_elixir_file(file_path, file_contents)
|> Igniter.update_elixir_file(file_path, fn zipper ->
case Zipper.find(zipper, fn
{:import, _, [Config]} ->
true
{:import, _, [{:__aliases__, _, [:Config]}]} ->
true
_ ->
false
end) do
nil ->
{:warning, bad_config_message(app_name, file_path, config_path, value, opts)}
_ ->
modify_configuration_code(zipper, config_path, app_name, value, updater)
end
end)
end
defp ensure_default_configs_exist(igniter, file)
when file in ["config/dev.exs", "config/test.exs", "config/prod.exs"] do
igniter
|> Igniter.include_or_create_elixir_file("config/config.exs", """
import Config
""")
|> ensure_config_evaluates_env()
|> Igniter.include_or_create_elixir_file("config/dev.exs", """
import Config
""")
|> Igniter.include_or_create_elixir_file("config/test.exs", """
import Config
""")
|> Igniter.include_or_create_elixir_file("config/prod.exs", """
import Config
""")
end
defp ensure_default_configs_exist(igniter, _), do: igniter
defp ensure_config_evaluates_env(igniter) do
Igniter.update_elixir_file(igniter, "config/config.exs", fn zipper ->
case Igniter.Code.Function.move_to_function_call_in_current_scope(zipper, :import_config, 1) do
{:ok, _} ->
{:ok, zipper}
_ ->
{:ok,
Igniter.Code.Common.add_code(zipper, """
import_config "\#{config_env()}.exs"
""")}
end
end)
end
defp bad_config_message(app_name, file_path, config_path, value, opts) do
path =
config_path
|> keywordify(value)
code =
quote do
config unquote(app_name), unquote(path)
end
message =
if opts[:failure_message] do
"""
#{opts[:failure_message]}
"""
else
""
end
or_update =
if opts[:updater] do
" or update"
else
""
end
"""
Please set#{or_update} the following config in #{file_path}:
#{Macro.to_string(code)}#{String.trim_trailing(message)}
"""
end
defp keywordify([], value), do: value
defp keywordify([key | rest], value), do: [{key, keywordify(rest, value)}]
@doc """
Modifies elixir configuration code starting at the configured zipper.
If you want to set configuration, use `configure/6` or `configure_new/5` instead. This is a lower-level
tool for modifying configuration files when you need to adjust some specific part of them.
"""
@spec modify_configuration_code(
Zipper.t(),
list(atom),
atom(),
term(),
(Zipper.t() -> {:ok, Zipper.t()} | :error) | nil
) :: Zipper.t()
def modify_configuration_code(zipper, config_path, app_name, value, updater \\ nil) do
updater = updater || fn zipper -> {:ok, Common.replace_code(zipper, value)} end
Igniter.Code.Common.within(zipper, fn zipper ->
case try_update_three_arg(zipper, config_path, app_name, value, updater) do
{:ok, zipper} ->
{:ok, zipper}
:error ->
case try_update_two_arg(zipper, config_path, app_name, value, updater) do
{:ok, zipper} ->
{:ok, zipper}
:error ->
[first | rest] = config_path
# this indicates its a module / not a "pretty" atom
config =
if is_atom(first) && String.downcase(to_string(first)) != to_string(first) do
{:config, [], [app_name, first, Igniter.Code.Keyword.keywordify(rest, value)]}
else
{:config, [],
[app_name, [{first, Igniter.Code.Keyword.keywordify(rest, value)}]]}
end
case Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:import,
1,
fn function_call ->
Igniter.Code.Function.argument_matches_predicate?(
function_call,
0,
&Common.nodes_equal?(&1, Config)
)
end
) do
{:ok, zipper} ->
zipper
|> Zipper.right()
|> case do
nil ->
{:ok, Common.add_code(zipper, config)}
zipper ->
{:ok, Common.add_code(zipper, config, :before)}
end
:error ->
zipper
|> Zipper.right()
|> case do
nil ->
{:ok, Common.add_code(zipper, config)}
zipper ->
{:ok, Common.add_code(zipper, config, :before)}
end
end
end
end
end)
|> case do
{:ok, zipper} -> zipper
:error -> zipper
end
end
@doc """
Checks if either `config :root_key, _` or `config :root_key, _, _` is present
in the provided config file.
Note: The config file name should _not_ include the `config/` prefix.
"""
@spec configures_root_key?(Igniter.t(), String.t(), atom()) :: boolean()
def configures_root_key?(igniter, config_file_name, root_key) do
case load_config_zipper(igniter, config_file_name) do
nil -> false
zipper -> configures_root_key?(zipper, root_key)
end
end
@doc """
Same as `configures_root_key?/3` but accepts a Zipper instead.
"""
@spec configures_root_key?(Zipper.t(), atom()) :: boolean()
def configures_root_key?(%Zipper{} = zipper, root_key) do
with :error <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:config,
2,
&Igniter.Code.Function.argument_equals?(&1, 0, root_key)
),
:error <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:config,
3,
&Igniter.Code.Function.argument_equals?(&1, 0, root_key)
) do
false
else
{:ok, _zipper} -> true
end
end
@doc """
If the last argument is key, checks if either `config :root_key, :key, ...`
or `config :root_key, key: ...` is set.
If the last argument is a keyword path, checks if
`config :root_key, path_head, [...]` defines `path_rest` or if
`config :root_key, [...]` defines `path`, where `path_head` is the first
element of `path` and `path_rest` are the remaining elements.
Note: `config_file_name` should _not_ include the `config/` prefix.
"""
@spec configures_key?(
Igniter.t(),
String.t(),
atom(),
atom() | list(atom())
) :: boolean()
def configures_key?(igniter, config_file_name, root_key, key_or_path) do
case load_config_zipper(igniter, config_file_name) do
nil -> false
zipper -> configures_key?(zipper, root_key, key_or_path)
end
end
@doc """
Same as `configures_key?/4` but accepts a Zipper.
"""
@spec configures_key?(Zipper.t(), atom(), atom() | list(atom())) :: boolean()
def configures_key?(zipper, root_key, key_or_path)
def configures_key?(zipper = %Zipper{}, root_key, key) when is_atom(key) do
configures_key?(zipper, root_key, List.wrap(key))
end
def configures_key?(zipper = %Zipper{}, root_key, path) when is_list(path) do
with :error <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:config,
2,
fn function_call ->
Igniter.Code.Function.argument_equals?(function_call, 0, root_key) and
Igniter.Code.Function.argument_matches_predicate?(
function_call,
1,
fn argument_zipper ->
Igniter.Code.Keyword.keyword_has_path?(argument_zipper, path)
end
)
end
),
:error <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:config,
3,
fn function_call ->
case path do
[key] ->
Igniter.Code.Function.argument_equals?(function_call, 0, root_key) and
Igniter.Code.Function.argument_equals?(function_call, 1, key)
[path_head | path_rest] ->
Igniter.Code.Function.argument_equals?(function_call, 0, root_key) and
Igniter.Code.Function.argument_equals?(function_call, 1, path_head) and
Igniter.Code.Function.argument_matches_predicate?(
function_call,
2,
fn argument_zipper ->
Igniter.Code.Keyword.keyword_has_path?(argument_zipper, path_rest)
end
)
end
end
) do
false
else
{:ok, _zipper} -> true
end
end
@doc "Returns `true` if the given configuration path is set somewhere after the provided zipper, or in the given configuration file."
@deprecated "Use configures_root_key?/3 or configures_key?/4 instead."
@spec configures?(Igniter.t(), String.t(), list(atom), atom()) :: boolean()
def configures?(igniter, config_file_name, path, app_name) do
case load_config_zipper(igniter, config_file_name) do
nil -> false
zipper -> configures?(zipper, path, app_name)
end
end
@spec configures?(Zipper.t(), list(atom), atom()) :: boolean()
@deprecated "Use configures_root_key?/2 or configures_key?/3 instead."
def configures?(zipper, path, app_name) do
case path do
[] ->
configures_root_key?(zipper, app_name)
path ->
configures_key?(zipper, app_name, path)
end
end
defp load_config_zipper(igniter, config_file_name) do
config_file_path = Path.join("config", config_file_name)
igniter =
Igniter.include_existing_elixir_file(igniter, config_file_path, required?: false)
case Rewrite.source(igniter.rewrite, config_file_path) do
{:ok, source} ->
source
|> Rewrite.Source.get(:quoted)
|> Zipper.zip()
_ ->
nil
end
end
defp try_update_three_arg(zipper, config_path, app_name, value, updater) do
if Enum.count(config_path) == 1 do
config_item = Enum.at(config_path, 0)
case Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:config,
3,
fn function_call ->
Igniter.Code.Function.argument_matches_pattern?(function_call, 0, ^app_name) &&
Igniter.Code.Function.argument_matches_pattern?(function_call, 1, ^config_item)
end
) do
:error ->
:error
{:ok, zipper} ->
Igniter.Code.Function.update_nth_argument(zipper, 2, updater)
end
else
[config_item | path] = config_path
case Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:config,
3,
fn function_call ->
Igniter.Code.Function.argument_matches_pattern?(function_call, 0, ^app_name) &&
(Igniter.Code.Function.argument_matches_pattern?(function_call, 1, ^config_item) ||
Igniter.Code.Function.argument_matches_predicate?(
function_call,
1,
&Common.nodes_equal?(&1, config_item)
))
end
) do
:error ->
:error
{:ok, zipper} ->
with {:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 2),
{:ok, zipper} <- Igniter.Code.Keyword.put_in_keyword(zipper, path, value, updater) do
{:ok, zipper}
else
_ ->
:error
end
end
end
end
defp try_update_two_arg(zipper, config_path, app_name, value, updater) do
case Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:config,
2,
fn function_call ->
Igniter.Code.Function.argument_matches_pattern?(function_call, 0, ^app_name)
end
) do
:error ->
:error
{:ok, zipper} ->
Igniter.Code.Function.update_nth_argument(
zipper,
1,
&Igniter.Code.Keyword.put_in_keyword(&1, config_path, value, updater)
)
end
end
end