Packages
igniter
0.3.76
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/application.ex
defmodule Igniter.Project.Application do
@moduledoc "Codemods and tools for working with Application modules."
require Igniter.Code.Common
require Igniter.Code.Function
alias Igniter.Code.Common
alias Sourceror.Zipper
@doc "Returns the name of the current application."
@spec app_name() :: atom()
@deprecated "Use `app_name/1` instead."
def app_name do
Mix.Project.config()[:app]
end
@doc "Returns the name of the application."
@spec app_name(Igniter.t()) :: atom()
def app_name(igniter) do
zipper =
igniter
|> Igniter.include_existing_file("mix.exs")
|> Map.get(:rewrite)
|> Rewrite.source!("mix.exs")
|> Rewrite.Source.get(:quoted)
|> Sourceror.Zipper.zip()
with {:ok, zipper} <- Igniter.Code.Function.move_to_def(zipper, :project, 0),
zipper <- Igniter.Code.Common.rightmost(zipper),
true <- Igniter.Code.List.list?(zipper),
{:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :app),
{:ok, app_name} when is_atom(app_name) <- expand_app_name(zipper) do
app_name
else
_ ->
raise """
Failed to parse the application name from mix.exs.
Please ensure that the `project` function in your mix.exs
file returns a keyword list with an `app` key.
"""
end
end
defp expand_app_name(zipper) do
with :error <- Common.expand_literal(zipper),
:error <- expand_attribute(zipper) do
:error
end
end
defp expand_attribute(zipper) do
with {:@, _, [{attr, _, nil}]} <- zipper.node,
{:ok, zipper} <- find_prev(zipper, &match?({:@, _, [{^attr, _, [_]}]}, &1.node)) do
zipper
|> Zipper.down()
|> Zipper.down()
|> Common.expand_literal()
else
_ ->
:error
end
end
defp find_prev(zipper, pred) do
case Common.move_left(zipper, pred) do
{:ok, zipper} ->
{:ok, zipper}
:error ->
case Common.move_upwards(zipper, 1) do
{:ok, zipper} -> find_prev(zipper, pred)
:error -> :error
end
end
end
@doc "Returns the name of the application module."
def app_module(igniter) do
zipper =
igniter
|> Igniter.include_existing_file("mix.exs")
|> Map.get(:rewrite)
|> Rewrite.source!("mix.exs")
|> Rewrite.Source.get(:quoted)
|> Sourceror.Zipper.zip()
with {:ok, zipper} <- Igniter.Code.Function.move_to_def(zipper, :application, 0),
zipper <- Igniter.Code.Common.rightmost(zipper),
true <- Igniter.Code.List.list?(zipper),
{:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :mod) do
case Igniter.Code.Common.expand_literal(zipper) do
{:ok, app_module} ->
{:ok, app_module}
:error ->
try do
zipper.node
|> Code.eval_quoted()
|> elem(0)
rescue
_ ->
reraise """
An application module was configured, but we could not determine its name, cannot continue.
""",
__STACKTRACE__
end
end
else
_ ->
nil
end
end
@doc """
Adds a new child to the `children` list in the application file
To pass quoted code as the options, use the following format:
{module, {:code, quoted_code}}
i.e
{MyApp.Supervisor, {:code, quote do
Application.fetch_env!(:app, :config)
end}}
## Options
- `after` - A list of other modules that this supervisor should appear after,
or a function that takes a module and returns `true` if this module should be placed after it.
- `opts_updater` - A function that takes the current options (second element of the child tuple),
and returns a new value. If the existing value of the module is not a tuple, the value passed into
your function will be `[]`. Your function *must* return `{:ok, zipper}` or
`{:error | :warning, "error_or_warning"}`.
## Ordering
We will put the new child as the earliest item in the list that we can, skipping any modules
in the `after` option.
"""
@spec add_new_child(
Igniter.t(),
module() | {module, {:code, term()}} | {module, term()},
opts :: Keyword.t()
) ::
Igniter.t()
def add_new_child(igniter, to_supervise, opts \\ []) do
to_perform =
case app_module(igniter) do
nil -> {:create_an_app, Igniter.Project.Module.module_name(igniter, "Application")}
{mod, _} -> {:modify, mod}
mod -> {:modify, mod}
end
opts =
Keyword.update(opts, :after, fn _ -> false end, fn list ->
if is_list(list) do
fn item -> item in list end
else
list
end
end)
case to_perform do
{:create_an_app, mod} ->
igniter
|> create_app(mod)
|> do_add_child(mod, to_supervise, opts)
{:modify, mod} ->
do_add_child(igniter, mod, to_supervise, opts)
end
end
def create_app(igniter, application) do
igniter
|> point_to_application_in_mix_exs(application)
|> create_application_file(application)
end
def do_add_child(igniter, application, to_supervise, opts) do
path = Igniter.Project.Module.proper_location(igniter, application, :source_folder)
to_supervise =
case to_supervise do
module when is_atom(module) -> module
{module, {:code, contents}} when is_atom(module) -> {module, contents}
{module, contents} -> {module, Macro.escape(contents)}
end
to_supervise_module =
case to_supervise do
{module, _} -> module
module -> module
end
Igniter.update_elixir_file(igniter, path, fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Application),
{:ok, zipper} <- Igniter.Code.Function.move_to_def(zipper, :start, 2),
{:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:=,
[2],
fn call ->
Igniter.Code.Function.argument_matches_pattern?(
call,
0,
{:children, _, context} when is_atom(context)
) &&
Igniter.Code.Function.argument_matches_pattern?(call, 1, v when is_list(v))
end
),
{:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 1) do
case Igniter.Code.List.move_to_list_item(zipper, fn item ->
if Igniter.Code.Tuple.tuple?(item) do
with {:ok, item} <- Igniter.Code.Tuple.tuple_elem(item, 0),
item <- Igniter.Code.Common.expand_alias(item) do
Igniter.Code.Common.nodes_equal?(item, to_supervise_module)
else
_ -> false
end
else
item
|> Igniter.Code.Common.expand_alias()
|> Igniter.Code.Common.nodes_equal?(to_supervise_module)
end
end) do
{:ok, zipper} ->
if updater = opts[:opts_updater] do
zipper =
if Igniter.Code.Tuple.tuple?(zipper) do
zipper
else
Zipper.replace(zipper, {zipper.node, []})
end
{:ok, zipper} = Igniter.Code.Tuple.tuple_elem(zipper, 1)
updater.(zipper)
else
{:ok, zipper}
end
:error ->
zipper
|> Zipper.down()
|> then(fn zipper ->
case Zipper.down(zipper) do
nil ->
Zipper.insert_child(zipper, to_supervise)
zipper ->
zipper
|> skip_after(opts)
|> case do
{:after, zipper} ->
Zipper.insert_right(zipper, to_supervise)
{:before, zipper} ->
Zipper.insert_left(zipper, to_supervise)
end
end
end)
end
else
_ ->
{:warning,
"""
Could not find a `children = [...]` assignment in the `start` function of the `#{application}` module.
Please ensure that #{inspect(to_supervise)} is added started by the application `#{application}` manually.
"""}
end
end)
end
def skip_after(zipper, opts) do
Igniter.Code.List.do_move_to_list_item(zipper, fn item ->
with {:is_tuple, true} <- {:is_tuple, Igniter.Code.Tuple.tuple?(item)},
{:ok, item} <- Igniter.Code.Tuple.tuple_elem(item, 0),
item <- Igniter.Code.Common.expand_alias(item),
module when is_atom(module) <- alias_to_mod(item.node),
true <- opts[:after].(module) do
true
else
{:is_tuple, false} ->
with item <- Igniter.Code.Common.expand_alias(item),
module when is_atom(module) <- alias_to_mod(item.node),
true <- opts[:after].(module) do
true
else
_ ->
false
end
_ ->
false
end
end)
|> case do
{:ok, zipper} ->
case Zipper.right(zipper) do
nil ->
{:after, zipper}
zipper ->
skip_after(zipper, Keyword.put(opts, :nested?, true))
end
:error ->
if Keyword.get(opts, :nested?) do
{:after, zipper}
else
{:before, zipper}
end
end
end
def create_application_file(igniter, application) do
path = Igniter.Project.Module.proper_location(igniter, application)
supervisor = Igniter.Project.Module.module_name(igniter, "Supervisor")
contents = """
defmodule #{inspect(application)} do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = []
opts = [strategy: :one_for_one, name: #{inspect(supervisor)}]
Supervisor.start_link(children, opts)
end
end
"""
Igniter.create_new_file(igniter, path, contents)
end
defp point_to_application_in_mix_exs(igniter, application) do
Igniter.update_elixir_file(igniter, "mix.exs", fn zipper ->
case Igniter.Code.Module.move_to_module_using(zipper, Mix.Project) do
{:ok, zipper} ->
case Igniter.Code.Function.move_to_def(zipper, :application, 0) do
{:ok, zipper} ->
zipper
|> Igniter.Code.Common.rightmost()
|> Igniter.Code.Keyword.set_keyword_key(:mod, {application, []}, fn z ->
code =
{application, []}
|> Sourceror.to_string()
|> Sourceror.parse_string!()
{:ok, Common.replace_code(z, code)}
end)
_ ->
Common.add_code(zipper, """
def application do
[
mod: {#{inspect(application)}, []}
]
end
""")
end
_ ->
{:warning,
"""
No module in `mix.exs` using `Mix.Project` was found. Please update your `mix.exs`
to point to the application module `#{inspect(application)}`.
For example:
def application do
[
mod: {#{inspect(application)}, []}
]
end
"""}
end
end)
end
defp alias_to_mod({:__aliases__, _, parts}), do: Module.concat(parts)
defp alias_to_mod(v) when is_atom(v), do: v
defp alias_to_mod(other), do: other
end