2024-05-28 15:30:41 +12:00
|
|
|
defmodule Igniter.Config do
|
2024-06-13 10:22:08 +12:00
|
|
|
@moduledoc "Codemods and utilities for modifying Elixir config files."
|
|
|
|
|
|
|
|
require Igniter.Code.Function
|
|
|
|
alias Igniter.Code.Common
|
2024-05-28 15:30:41 +12:00
|
|
|
alias Sourceror.Zipper
|
|
|
|
|
2024-06-13 10:22:08 +12:00
|
|
|
@doc "Sets a config value in the given configuration file, if it is not already set."
|
|
|
|
@spec configure_new(Igniter.t(), Path.t(), atom(), list(atom), term()) :: Igniter.t()
|
2024-06-11 01:58:20 +12:00
|
|
|
def configure_new(igniter, file_path, app_name, config_path, value) do
|
|
|
|
configure(igniter, file_path, app_name, config_path, value, & &1)
|
|
|
|
end
|
|
|
|
|
2024-06-13 10:22:08 +12:00
|
|
|
@doc "Sets a config value in the given configuration file, updating it with `updater` if it is already set."
|
|
|
|
@spec configure(
|
|
|
|
Igniter.t(),
|
|
|
|
Path.t(),
|
|
|
|
atom(),
|
|
|
|
list(atom),
|
|
|
|
term(),
|
|
|
|
(Zipper.t() -> {:ok, Zipper.t()} | :error) | nil
|
|
|
|
) :: Igniter.t()
|
2024-06-12 09:33:05 +12:00
|
|
|
def configure(igniter, file_name, app_name, config_path, value, updater \\ nil) do
|
|
|
|
file_contents = "import Config\n"
|
2024-06-11 01:58:20 +12:00
|
|
|
|
2024-06-12 09:33:05 +12:00
|
|
|
file_path = Path.join("config", file_name)
|
2024-05-28 15:30:41 +12:00
|
|
|
config_path = List.wrap(config_path)
|
2024-06-11 01:58:20 +12:00
|
|
|
|
|
|
|
value =
|
|
|
|
case value do
|
|
|
|
{:code, value} -> value
|
|
|
|
value -> Macro.escape(value)
|
|
|
|
end
|
|
|
|
|
2024-06-13 10:22:08 +12:00
|
|
|
updater = updater || fn zipper -> {:ok, Zipper.replace(zipper, value)} end
|
2024-05-28 15:30:41 +12:00
|
|
|
|
|
|
|
igniter
|
2024-06-12 09:33:05 +12:00
|
|
|
|> ensure_default_configs_exist(file_name)
|
2024-06-11 01:58:20 +12:00
|
|
|
|> Igniter.include_or_create_elixir_file(file_path, file_contents)
|
2024-06-04 05:13:49 +12:00
|
|
|
|> Igniter.update_elixir_file(file_path, fn zipper ->
|
2024-06-11 01:58:20 +12:00
|
|
|
case Zipper.find(zipper, fn
|
|
|
|
{:import, _, [Config]} ->
|
|
|
|
true
|
2024-05-28 15:30:41 +12:00
|
|
|
|
2024-06-11 01:58:20 +12:00
|
|
|
{:import, _, [{:__aliases__, _, [:Config]}]} ->
|
|
|
|
true
|
2024-06-01 14:09:38 +12:00
|
|
|
|
2024-06-11 01:58:20 +12:00
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end) do
|
|
|
|
nil ->
|
|
|
|
{:error, "No call to `import Config` found in configuration file"}
|
2024-05-28 15:30:41 +12:00
|
|
|
|
2024-06-11 01:58:20 +12:00
|
|
|
zipper ->
|
|
|
|
modify_configuration_code(zipper, config_path, app_name, value, updater)
|
2024-05-28 15:30:41 +12:00
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2024-06-12 09:33:05 +12:00
|
|
|
defp ensure_default_configs_exist(igniter, "runtime.exs"), do: igniter
|
|
|
|
|
|
|
|
defp ensure_default_configs_exist(igniter, _file) do
|
|
|
|
igniter
|
|
|
|
|> Igniter.include_or_create_elixir_file("config/config.exs", """
|
|
|
|
import Config
|
|
|
|
|
|
|
|
# Import environment specific config. This must remain at the bottom
|
|
|
|
# of this file so it overrides the configuration defined above.
|
|
|
|
import_config "\#{config_env()}.exs"
|
|
|
|
""")
|
|
|
|
|> 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
|
|
|
|
|
2024-06-13 10:22:08 +12:00
|
|
|
@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
|
|
|
|
) :: Igniter.t()
|
2024-06-11 01:58:20 +12:00
|
|
|
def modify_configuration_code(zipper, config_path, app_name, value, updater \\ nil) do
|
2024-06-13 10:22:08 +12:00
|
|
|
updater = updater || fn zipper -> {:ok, Zipper.replace(zipper, value)} end
|
2024-06-11 01:58:20 +12:00
|
|
|
|
2024-06-12 09:33:05 +12:00
|
|
|
case try_update_three_arg(zipper, config_path, app_name, value, updater) do
|
2024-06-11 01:58:20 +12:00
|
|
|
{:ok, zipper} ->
|
|
|
|
zipper
|
|
|
|
|
|
|
|
:error ->
|
|
|
|
case try_update_two_arg(zipper, config_path, app_name, value, updater) do
|
|
|
|
{:ok, zipper} ->
|
|
|
|
zipper
|
|
|
|
|
|
|
|
:error ->
|
|
|
|
[first | rest] = config_path
|
|
|
|
|
2024-06-12 09:33:05 +12:00
|
|
|
# this indicates its a module / not a "pretty" atom
|
2024-06-11 01:58:20 +12:00
|
|
|
config =
|
2024-06-12 09:33:05 +12:00
|
|
|
if is_atom(first) && String.downcase(to_string(first)) != to_string(first) do
|
2024-06-13 10:22:08 +12:00
|
|
|
{:config, [], [app_name, first, Igniter.Code.Keyword.keywordify(rest, value)]}
|
2024-06-12 09:33:05 +12:00
|
|
|
else
|
2024-06-13 10:22:08 +12:00
|
|
|
{:config, [], [app_name, [{first, Igniter.Code.Keyword.keywordify(rest, value)}]]}
|
2024-06-12 09:33:05 +12:00
|
|
|
end
|
2024-06-11 01:58:20 +12:00
|
|
|
|
2024-06-13 10:22:08 +12:00
|
|
|
case Igniter.Code.Function.move_to_function_call_in_current_scope(
|
2024-06-11 01:58:20 +12:00
|
|
|
zipper,
|
|
|
|
:import,
|
|
|
|
1,
|
|
|
|
fn function_call ->
|
2024-06-13 10:22:08 +12:00
|
|
|
Igniter.Code.Function.argument_matches_predicate?(
|
2024-06-11 01:58:20 +12:00
|
|
|
function_call,
|
|
|
|
0,
|
2024-06-13 10:22:08 +12:00
|
|
|
&Common.nodes_equal?(&1, Config)
|
2024-06-11 01:58:20 +12:00
|
|
|
)
|
|
|
|
end
|
|
|
|
) do
|
|
|
|
{:ok, zipper} ->
|
|
|
|
zipper
|
|
|
|
|> Zipper.right()
|
|
|
|
|> case do
|
|
|
|
nil ->
|
2024-06-13 10:22:08 +12:00
|
|
|
Common.add_code(zipper, config)
|
2024-06-11 01:58:20 +12:00
|
|
|
|
|
|
|
zipper ->
|
2024-06-13 10:22:08 +12:00
|
|
|
Common.add_code(zipper, config, :before)
|
2024-06-11 01:58:20 +12:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-06-13 10:22:08 +12:00
|
|
|
@doc "Returns `true` if the given configuration path is set somewhere after the provided zipper."
|
|
|
|
@spec configures?(Zipper.t(), list(atom), atom()) :: boolean()
|
2024-06-11 01:58:20 +12:00
|
|
|
def configures?(zipper, config_path, app_name) do
|
|
|
|
if Enum.count(config_path) == 1 do
|
|
|
|
config_item = Enum.at(config_path, 0)
|
|
|
|
|
2024-06-13 10:22:08 +12:00
|
|
|
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
|
2024-06-11 01:58:20 +12:00
|
|
|
:error ->
|
|
|
|
false
|
|
|
|
|
|
|
|
{:ok, _zipper} ->
|
|
|
|
true
|
|
|
|
end
|
|
|
|
else
|
2024-06-13 10:22:08 +12:00
|
|
|
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
|
2024-06-11 01:58:20 +12:00
|
|
|
:error ->
|
|
|
|
:error
|
|
|
|
|
|
|
|
{:ok, zipper} ->
|
2024-06-13 10:22:08 +12:00
|
|
|
Igniter.Code.Function.argument_matches_predicate?(zipper, 1, fn zipper ->
|
|
|
|
Igniter.Code.Keyword.keyword_has_path?(zipper, config_path)
|
2024-06-11 01:58:20 +12:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-06-12 09:33:05 +12:00
|
|
|
defp try_update_three_arg(zipper, config_path, app_name, value, updater) do
|
2024-05-28 15:30:41 +12:00
|
|
|
if Enum.count(config_path) == 1 do
|
|
|
|
config_item = Enum.at(config_path, 0)
|
|
|
|
|
2024-06-13 10:22:08 +12:00
|
|
|
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
|
2024-06-01 14:09:38 +12:00
|
|
|
:error ->
|
2024-05-28 15:30:41 +12:00
|
|
|
:error
|
|
|
|
|
2024-06-01 14:09:38 +12:00
|
|
|
{:ok, zipper} ->
|
2024-06-13 10:22:08 +12:00
|
|
|
Igniter.Code.Function.update_nth_argument(zipper, 2, updater)
|
2024-05-28 15:30:41 +12:00
|
|
|
end
|
|
|
|
else
|
2024-06-12 09:33:05 +12:00
|
|
|
[config_item | path] = config_path
|
|
|
|
|
2024-06-13 10:22:08 +12:00
|
|
|
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
|
2024-06-12 09:33:05 +12:00
|
|
|
:error ->
|
|
|
|
:error
|
|
|
|
|
|
|
|
{:ok, zipper} ->
|
2024-06-13 10:22:08 +12:00
|
|
|
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
|
2024-06-12 09:33:05 +12:00
|
|
|
{:ok, zipper}
|
|
|
|
else
|
|
|
|
_ ->
|
|
|
|
:error
|
|
|
|
end
|
|
|
|
end
|
2024-05-28 15:30:41 +12:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp try_update_two_arg(zipper, config_path, app_name, value, updater) do
|
2024-06-13 10:22:08 +12:00
|
|
|
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
|
2024-06-01 14:09:38 +12:00
|
|
|
:error ->
|
2024-05-28 15:30:41 +12:00
|
|
|
:error
|
|
|
|
|
2024-06-01 14:09:38 +12:00
|
|
|
{:ok, zipper} ->
|
2024-06-13 10:22:08 +12:00
|
|
|
Igniter.Code.Function.update_nth_argument(
|
|
|
|
zipper,
|
|
|
|
1,
|
|
|
|
&Igniter.Code.Keyword.put_in_keyword(&1, config_path, value, updater)
|
|
|
|
)
|
2024-05-28 15:30:41 +12:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|