igniter/lib/application.ex

148 lines
4.2 KiB
Elixir
Raw Normal View History

defmodule Igniter.Application do
@moduledoc "Codemods and tools for working with Application modules."
2024-06-13 10:22:08 +12:00
require Igniter.Code.Common
require Igniter.Code.Function
2024-06-11 01:58:20 +12:00
2024-06-13 10:22:08 +12:00
alias Igniter.Code.Common
2024-06-11 01:58:20 +12:00
alias Sourceror.Zipper
2024-06-13 10:22:08 +12:00
@doc "Returns the name of the current application."
@spec app_name() :: atom()
def app_name do
Mix.Project.config()[:app]
end
2024-06-11 01:58:20 +12:00
2024-06-13 10:22:08 +12:00
@doc "Adds a new child to the `children` list in the application file"
@spec add_new_child(Igniter.t(), module() | {module, term()}) :: Igniter.t()
def add_new_child(igniter, to_supervise) do
2024-06-11 01:58:20 +12:00
project = Mix.Project.get!()
# TODO: Would be better to check the source and parse the app module out
# as something else may have set an app module
to_perform =
case project.application()[:mod] do
2024-06-13 10:22:08 +12:00
nil -> {:create_an_app, Igniter.Code.Module.module_name("Application")}
2024-06-11 01:58:20 +12:00
{mod, _} -> {:modify, mod}
mod -> {:modify, mod}
end
case to_perform do
{:create_an_app, mod} ->
igniter
|> create_app(mod)
|> do_add_child(mod, to_supervise)
{:modify, mod} ->
do_add_child(igniter, mod, to_supervise)
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) do
2024-06-13 10:22:08 +12:00
path = Igniter.Code.Module.proper_location(application)
2024-06-11 01:58:20 +12:00
diff_checker =
case to_supervise do
v when is_atom(v) ->
2024-06-13 10:22:08 +12:00
&Common.nodes_equal?/2
2024-06-11 01:58:20 +12:00
2024-06-12 09:33:05 +12:00
{v, _opts} when is_atom(v) ->
2024-06-11 01:58:20 +12:00
fn
{item, _}, {right, _} ->
2024-06-13 10:22:08 +12:00
Common.nodes_equal?(item, right)
2024-06-12 09:33:05 +12:00
2024-06-11 01:58:20 +12:00
item, {right, _} ->
2024-06-13 10:22:08 +12:00
Common.nodes_equal?(item, right)
2024-06-11 01:58:20 +12:00
_, _ ->
false
end
end
Igniter.update_elixir_file(igniter, path, fn zipper ->
2024-06-13 10:22:08 +12:00
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, Application),
{:ok, zipper} <- Igniter.Code.Module.move_to_def(zipper, :start, 2) do
2024-06-11 01:58:20 +12:00
zipper
2024-06-13 10:22:08 +12:00
|> Igniter.Code.Function.move_to_function_call_in_current_scope(:=, [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))
2024-06-11 01:58:20 +12:00
end)
|> case do
{:ok, zipper} ->
zipper
|> Zipper.down()
|> Zipper.rightmost()
2024-06-13 10:22:08 +12:00
|> Igniter.Code.List.append_new_to_list(to_supervise, diff_checker)
2024-06-11 01:58:20 +12:00
_ ->
{:error,
"Expected the `start/2` function in `#{path}` to have a `children = [...]` assignment."}
end
else
_ ->
{:error, "Expected `#{path}` to be an Application with a `start` function"}
end
end)
end
defp create_application_file(igniter, application) do
2024-06-13 10:22:08 +12:00
path = Igniter.Code.Module.proper_location(application)
2024-06-11 01:58:20 +12:00
contents = """
defmodule #{inspect(application)} do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = []
opts = [strategy: :one_for_one, name: Foo.Supervisor]
Supervisor.start_link(children, opts)
end
end
"""
Igniter.create_new_elixir_file(igniter, path, contents)
end
defp point_to_application_in_mix_exs(igniter, application) do
Igniter.update_elixir_file(igniter, "mix.exs", fn zipper ->
2024-06-13 10:22:08 +12:00
case Igniter.Code.Module.move_to_module_using(zipper, Mix.Project) do
2024-06-11 01:58:20 +12:00
{:ok, zipper} ->
2024-06-13 10:22:08 +12:00
case Igniter.Code.Module.move_to_def(zipper, :application, 0) do
2024-06-11 01:58:20 +12:00
{:ok, zipper} ->
zipper
|> Zipper.rightmost()
2024-06-13 10:22:08 +12:00
|> Igniter.Code.Keyword.set_keyword_key(:mod, {application, []}, fn z ->
{:ok, Zipper.replace(z, {application, []})}
2024-06-11 01:58:20 +12:00
end)
_ ->
Common.add_code(zipper, """
def application do
[
mod: {#{inspect(application)}, []}
]
end
""")
end
_ ->
{:error, "Required a module using `Mix.Project` to exist in `mix.exs`"}
end
end)
end
end