Skip to content

Commit 7630b67

Browse files
committed
improvement: support the opts being code when adding a new child to the app tree
improvement: prepend new children instead of appending them improvement: add an `after` option to `add_new_child/3` fix: handle some edge cases in application child adding
1 parent e47b32f commit 7630b67

File tree

2 files changed

+178
-27
lines changed

2 files changed

+178
-27
lines changed

lib/igniter/project/application.ex

Lines changed: 109 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -79,24 +79,60 @@ defmodule Igniter.Project.Application do
7979
end
8080
end
8181

82-
@doc "Adds a new child to the `children` list in the application file"
83-
@spec add_new_child(Igniter.t(), module() | {module, term()}) :: Igniter.t()
84-
def add_new_child(igniter, to_supervise) do
82+
@doc """
83+
Adds a new child to the `children` list in the application file
84+
85+
To pass quoted code as the options, use the following format:
86+
87+
{module, {:code, quoted_code}}
88+
89+
i.e
90+
91+
{MyApp.Supervisor, {:code, quote do
92+
Application.fetch_env!(:app, :config)
93+
end}}
94+
95+
## Options
96+
97+
- `after` - A list of other modules that this supervisor should appear after,
98+
or a function that takes a module and returns `true` if this module should be placed after it.
99+
100+
## Ordering
101+
102+
We will put the new child as the earliest item in the list that we can, skipping any modules
103+
in `after`.
104+
"""
105+
@spec add_new_child(
106+
Igniter.t(),
107+
module() | {module, {:code, term()}} | {module, term()},
108+
opts :: Keyword.t()
109+
) ::
110+
Igniter.t()
111+
def add_new_child(igniter, to_supervise, opts \\ []) do
85112
to_perform =
86113
case app_module(igniter) do
87114
nil -> {:create_an_app, Igniter.Code.Module.module_name(igniter, "Application")}
88115
{mod, _} -> {:modify, mod}
89116
mod -> {:modify, mod}
90117
end
91118

119+
opts =
120+
Keyword.update(opts, :after, fn _ -> false end, fn list ->
121+
if is_list(list) do
122+
fn item -> item in list end
123+
else
124+
list
125+
end
126+
end)
127+
92128
case to_perform do
93129
{:create_an_app, mod} ->
94130
igniter
95131
|> create_app(mod)
96-
|> do_add_child(mod, to_supervise)
132+
|> do_add_child(mod, to_supervise, opts)
97133

98134
{:modify, mod} ->
99-
do_add_child(igniter, mod, to_supervise)
135+
do_add_child(igniter, mod, to_supervise, opts)
100136
end
101137
end
102138

@@ -106,25 +142,20 @@ defmodule Igniter.Project.Application do
106142
|> create_application_file(application)
107143
end
108144

109-
def do_add_child(igniter, application, to_supervise) do
145+
def do_add_child(igniter, application, to_supervise, opts) do
110146
path = Igniter.Code.Module.proper_location(application)
111147

112-
diff_checker =
148+
to_supervise =
113149
case to_supervise do
114-
v when is_atom(v) ->
115-
&Common.nodes_equal?/2
116-
117-
{v, _opts} when is_atom(v) ->
118-
fn
119-
{item, _}, {right, _} ->
120-
Common.nodes_equal?(item, right)
121-
122-
item, {right, _} ->
123-
Common.nodes_equal?(item, right)
150+
module when is_atom(module) -> module
151+
{module, {:code, contents}} when is_atom(module) -> {module, contents}
152+
{module, contents} -> {module, Macro.escape(contents)}
153+
end
124154

125-
_, _ ->
126-
false
127-
end
155+
to_supervise_module =
156+
case to_supervise do
157+
{module, _} -> module
158+
module -> module
128159
end
129160

130161
Igniter.update_elixir_file(igniter, path, fn zipper ->
@@ -143,11 +174,35 @@ defmodule Igniter.Project.Application do
143174
) &&
144175
Igniter.Code.Function.argument_matches_pattern?(call, 1, v when is_list(v))
145176
end
146-
) do
147-
zipper
148-
|> Zipper.down()
149-
|> Zipper.rightmost()
150-
|> Igniter.Code.List.append_new_to_list(Macro.escape(to_supervise), diff_checker)
177+
),
178+
{:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 1) do
179+
if Igniter.Code.List.find_list_item_index(zipper, fn item ->
180+
if Igniter.Code.Tuple.tuple?(item) do
181+
with {:ok, zipper} <- Igniter.Code.Tuple.tuple_elem(zipper, 0),
182+
zipper <- Igniter.Code.Common.expand_alias(zipper),
183+
module when is_atom(module) <- zipper.node do
184+
module == to_supervise_module
185+
else
186+
_ -> false
187+
end
188+
else
189+
with zipper <- Igniter.Code.Common.expand_alias(zipper),
190+
module when is_atom(module) <- zipper.node do
191+
module == to_supervise_module
192+
else
193+
_ -> false
194+
end
195+
end
196+
end) do
197+
{:ok, zipper}
198+
else
199+
zipper
200+
|> Zipper.down()
201+
|> skip_after(opts)
202+
|> Zipper.insert_child(to_supervise)
203+
204+
# |> Igniter.Code.Common.insert_child(to_supervise)
205+
end
151206
else
152207
_ ->
153208
{:warning,
@@ -159,6 +214,28 @@ defmodule Igniter.Project.Application do
159214
end)
160215
end
161216

217+
def skip_after(zipper, opts) do
218+
Igniter.Code.Common.move_right(zipper, fn item ->
219+
with true <- Igniter.Code.Tuple.tuple?(item),
220+
{:ok, zipper} <- Igniter.Code.Tuple.tuple_elem(zipper, 0),
221+
zipper <- Igniter.Code.Common.expand_alias(zipper),
222+
module when is_atom(module) <- zipper.node,
223+
true <- opts[:after].(module) do
224+
true
225+
else
226+
_ ->
227+
false
228+
end
229+
end)
230+
|> case do
231+
{:ok, zipper} ->
232+
skip_after(zipper, opts)
233+
234+
:error ->
235+
zipper
236+
end
237+
end
238+
162239
def create_application_file(igniter, application) do
163240
path = Igniter.Code.Module.proper_location(application)
164241
supervisor = Igniter.Code.Module.module_name(igniter, "Supervisor")
@@ -189,9 +266,14 @@ defmodule Igniter.Project.Application do
189266
case Igniter.Code.Function.move_to_def(zipper, :application, 0) do
190267
{:ok, zipper} ->
191268
zipper
192-
|> Zipper.rightmost()
269+
|> Igniter.Code.Common.rightmost()
193270
|> Igniter.Code.Keyword.set_keyword_key(:mod, {application, []}, fn z ->
194-
{:ok, Common.replace_code(z, {application, []})}
271+
code =
272+
{application, []}
273+
|> Sourceror.to_string()
274+
|> Sourceror.parse_string!()
275+
276+
{:ok, Common.replace_code(z, code)}
195277
end)
196278

197279
_ ->

test/igniter/project/application_test.exs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,74 @@ defmodule Igniter.Project.ApplicationTest do
2727
18 + | mod: {Test.Application, []}
2828
""")
2929
end
30+
31+
test "supports taking options as the second argument" do
32+
test_project()
33+
|> Igniter.Project.Application.add_new_child({Foo, a: :b})
34+
|> assert_creates("lib/test/application.ex", """
35+
defmodule Test.Application do
36+
@moduledoc false
37+
38+
use Application
39+
40+
@impl true
41+
def start(_type, _args) do
42+
children = [{Foo, [a: :b]}]
43+
44+
opts = [strategy: :one_for_one, name: Test.Supervisor]
45+
Supervisor.start_link(children, opts)
46+
end
47+
end
48+
""")
49+
|> assert_has_patch("mix.exs", """
50+
17 - | extra_applications: [:logger]
51+
17 + | extra_applications: [:logger],
52+
18 + | mod: {Test.Application, []}
53+
""")
54+
end
55+
56+
test "supports taking code as the second argument" do
57+
test_project()
58+
|> Igniter.Project.Application.add_new_child(
59+
{Foo,
60+
{:code,
61+
quote do
62+
[1 + 2]
63+
end}}
64+
)
65+
|> assert_creates("lib/test/application.ex", """
66+
defmodule Test.Application do
67+
@moduledoc false
68+
69+
use Application
70+
71+
@impl true
72+
def start(_type, _args) do
73+
children = [{Foo, [1 + 2]}]
74+
75+
opts = [strategy: :one_for_one, name: Test.Supervisor]
76+
Supervisor.start_link(children, opts)
77+
end
78+
end
79+
""")
80+
|> assert_has_patch("mix.exs", """
81+
17 - | extra_applications: [:logger]
82+
17 + | extra_applications: [:logger],
83+
18 + | mod: {Test.Application, []}
84+
""")
85+
end
86+
87+
test "supports expressing " do
88+
:erlang.system_flag(:backtrace_depth, 1000)
89+
90+
test_project()
91+
|> Igniter.Project.Application.add_new_child(Foo)
92+
|> apply_igniter!()
93+
|> Igniter.Project.Application.add_new_child(Bar)
94+
|> assert_has_patch("lib/test/application.ex", """
95+
8 - | children = [Foo]
96+
8 + | children = [Bar, Foo]
97+
""")
98+
end
3099
end
31100
end

0 commit comments

Comments
 (0)