Skip to content

Commit 580d1bf

Browse files
committed
improvement: add Igniter.Code.Common.add_comment/2
Comments are important and now there is a higher level function for adding them. improvement: add `Igniter.Project.Config.configure_group/6` `Igniter.Project.Config.configure_group/6` is an elegant way to configure many things at once, with support for adding an explanatory comment above the entire group. So on a fresh installation when there is nothing for a given app/shared prefix configured, you get a config like ```elixir config :my_app, SomeModule, foo: :foo, bar: :bar ``` but if the shared prefix is configured, we simply merge into existing config without adding a comment.
1 parent 8d1d7cb commit 580d1bf

8 files changed

Lines changed: 244 additions & 25 deletions

File tree

lib/igniter/code/common.ex

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,51 @@ defmodule Igniter.Code.Common do
247247
end
248248
end
249249

250+
def add_comment(zipper, comment, opts \\ []) do
251+
zipper = maybe_move_to_single_child_block(zipper)
252+
253+
comments =
254+
comment
255+
|> String.trim_leading("\n")
256+
|> String.trim_trailing("\n")
257+
|> String.split("\n", trim: true)
258+
|> Enum.map(fn
259+
"" ->
260+
"#"
261+
262+
string ->
263+
if String.starts_with?(String.trim_leading(string), "#") do
264+
string
265+
else
266+
if String.starts_with?(string, " ") do
267+
"#" <> string
268+
else
269+
"# " <> string
270+
end
271+
end
272+
end)
273+
|> Enum.map(fn comment ->
274+
%{
275+
line: 0,
276+
text: comment,
277+
column: 0,
278+
next_eol_count: 1,
279+
previous_eol_count: 1
280+
}
281+
end)
282+
283+
node =
284+
case opts[:placement] || :before do
285+
:before ->
286+
Sourceror.prepend_comments(zipper.node, comments)
287+
288+
:after ->
289+
Sourceror.append_comments(zipper.node, comments)
290+
end
291+
292+
Zipper.replace(zipper, node)
293+
end
294+
250295
@doc """
251296
Adds the provided code to the zipper.
252297
@@ -304,6 +349,8 @@ defmodule Igniter.Code.Common do
304349
expand_env? = Keyword.get(opts, :expand_env?, true)
305350
placement = Keyword.get(opts, :placement, :after)
306351

352+
new_code = clean_lines(new_code)
353+
307354
new_code =
308355
if expand_env? do
309356
use_aliases(new_code, zipper)
@@ -323,12 +370,12 @@ defmodule Igniter.Code.Common do
323370
{:__block__, upwards_meta, upwards_code} = upwards.node
324371
index = Enum.count(zipper.path.left || [])
325372

326-
{to_insert, new_meta} =
327-
if extendable_block?(new_code) do
328-
{:__block__, new_meta, new_code} = new_code
329-
{new_code, new_meta}
373+
to_insert =
374+
if extendable_block?(new_code) and !has_comments?(new_code) do
375+
{:__block__, _new_meta, new_code} = new_code
376+
new_code
330377
else
331-
{[new_code], []}
378+
[new_code]
332379
end
333380

334381
{head, tail} =
@@ -340,16 +387,15 @@ defmodule Igniter.Code.Common do
340387

341388
Zipper.replace(
342389
upwards,
343-
{:__block__, combine_comments(upwards_meta, new_meta, placement),
344-
head ++ to_insert ++ tail}
390+
{:__block__, upwards_meta, head ++ to_insert ++ tail}
345391
)
346392

347393
super_upwards && extendable_block?(super_upwards.node) ->
348394
{:__block__, upwards_meta, upwards_code} = super_upwards.node
349395
index = Enum.count(zipper.supertree.path.left || [])
350396

351397
{to_insert, new_meta} =
352-
if extendable_block?(new_code) do
398+
if extendable_block?(new_code) and !has_comments?(new_code) do
353399
{:__block__, new_meta, new_code} = new_code
354400
{new_code, new_meta}
355401
else
@@ -427,7 +473,7 @@ defmodule Igniter.Code.Common do
427473
Zipper.replace(zipper, {:__block__, meta, new_stuff})
428474
else
429475
{code, meta} =
430-
if extendable_block?(new_code) do
476+
if extendable_block?(new_code) and !has_comments?(new_code) do
431477
{:__block__, meta, new_stuff} = new_code
432478

433479
if placement == :after do
@@ -449,6 +495,22 @@ defmodule Igniter.Code.Common do
449495
end
450496
end
451497

498+
defp clean_lines(ast) do
499+
Macro.prewalk(ast, fn
500+
{call, meta, args} ->
501+
{call, Keyword.put(meta, :line, 0), args}
502+
503+
other ->
504+
other
505+
end)
506+
end
507+
508+
defp has_comments?({_, meta, _}) do
509+
List.wrap(meta[:leading_comments]) != [] || List.wrap(meta[:trailing_comments]) != []
510+
end
511+
512+
defp has_comments?(_), do: false
513+
452514
@doc """
453515
Updates all nodes matching the given predicate with the given function.
454516
@@ -566,7 +628,10 @@ defmodule Igniter.Code.Common do
566628
end
567629

568630
def replace_code(%Zipper{} = zipper, new_code) do
569-
new_code = use_aliases(new_code, zipper)
631+
new_code =
632+
new_code
633+
|> clean_lines()
634+
|> use_aliases(zipper)
570635

571636
if extendable_block?(new_code) and in_extendable_block?(zipper) do
572637
at_supertree_root? = Zipper.up(zipper) == nil and !!zipper.supertree
@@ -629,7 +694,6 @@ defmodule Igniter.Code.Common do
629694
new_meta[:trailing_comments] || [],
630695
&((new_meta[:trailing_comments] || []) ++ &1)
631696
)
632-
|> ensure_unique_comments()
633697
else
634698
meta
635699
|> Keyword.update(
@@ -642,16 +706,9 @@ defmodule Igniter.Code.Common do
642706
new_meta[:trailing_comments] || [],
643707
&(&1 ++ (new_meta[:trailing_comments] || []))
644708
)
645-
|> ensure_unique_comments()
646709
end
647710
end
648711

649-
defp ensure_unique_comments(meta) do
650-
meta
651-
|> Keyword.update!(:leading_comments, &Enum.uniq/1)
652-
|> Keyword.update!(:trailing_comments, &Enum.uniq/1)
653-
end
654-
655712
defp multi_element_pipe?(%{node: {:|>, _, _}} = zipper) do
656713
up = Zipper.up(zipper)
657714
down = Zipper.down(zipper)

lib/igniter/project/config.ex

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ defmodule Igniter.Project.Config do
88
@type updater :: (Sourceror.Zipper.t() -> {:ok, Sourceror.Zipper.t()}) | :error | nil
99
@type after_predicate :: (Sourceror.Zipper.t() -> boolean())
1010

11+
@type config_group_item ::
12+
{list(term) | term(), term()} | {list(term) | term(), term(), Keyword.t()}
13+
1114
@doc """
1215
Sets a config value in the given configuration file, if it is not already set.
1316
@@ -31,6 +34,93 @@ defmodule Igniter.Project.Config do
3134
)
3235
end
3336

37+
@doc """
38+
Configures a "group" of configurations, which is multiple configurations set at one time.
39+
If the app + the shared prefix is already configured, then each configuration is added individually,
40+
and the `comment` for the group is ignored. The sub configurations use `configure`, so if you want to
41+
not change the value if its already set, use `updater: &{:ok, &1}` in the item opts.
42+
43+
## Options
44+
45+
- `comment` - A comment string to add above the group when its added.
46+
"""
47+
@spec configure_group(
48+
Igniter.t(),
49+
Path.t(),
50+
atom(),
51+
shared_prefix :: list(atom),
52+
list(config_group_item()),
53+
opts :: Keyword.t()
54+
) :: Igniter.t()
55+
def configure_group(igniter, file_path, app_name, shared_prefix, items, opts \\ []) do
56+
if Enum.empty?(items) do
57+
raise ArgumentError, "Must provide at least one item in configure_group/6"
58+
end
59+
60+
items =
61+
Enum.map(items, fn
62+
{sub_path, value} ->
63+
{List.wrap(sub_path), value, opts}
64+
65+
{sub_path, value, opts} ->
66+
{List.wrap(sub_path), value, opts}
67+
end)
68+
69+
if configures_key?(igniter, file_path, app_name, shared_prefix) do
70+
Enum.reduce(items, igniter, fn {path, value, opts}, igniter ->
71+
configure(igniter, file_path, app_name, shared_prefix ++ path, value, opts)
72+
end)
73+
else
74+
zipper =
75+
{:__block__, [], []}
76+
|> Zipper.zip()
77+
78+
code_with_configuration_added =
79+
Enum.reduce(items, zipper, fn {path, value, opts}, zipper ->
80+
modify_configuration_code(zipper, shared_prefix ++ path, app_name, value, opts)
81+
end)
82+
|> Zipper.topmost()
83+
|> then(fn zipper ->
84+
if opts[:comment] do
85+
Igniter.Code.Common.add_comment(zipper, opts[:comment])
86+
else
87+
zipper
88+
end
89+
end)
90+
|> Zipper.topmost_root()
91+
92+
config_file_path = config_file_path(igniter, file_path)
93+
94+
igniter
95+
|> ensure_default_configs_exist()
96+
|> Igniter.update_elixir_file(config_file_path, fn zipper ->
97+
case Zipper.find(zipper, fn
98+
{:import, _, [Config]} ->
99+
true
100+
101+
{:import, _, [{:__aliases__, _, [:Config]}]} ->
102+
true
103+
104+
_ ->
105+
false
106+
end) do
107+
nil ->
108+
{:warning,
109+
bad_config_message(
110+
app_name,
111+
file_path,
112+
shared_prefix,
113+
Sourceror.to_string(zipper),
114+
opts
115+
)}
116+
117+
zipper ->
118+
Igniter.Code.Common.add_code(zipper, code_with_configuration_added)
119+
end
120+
end)
121+
end
122+
end
123+
34124
@spec configure_runtime_env(
35125
Igniter.t(),
36126
atom(),

lib/igniter/util/debug.ex

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ defmodule Igniter.Util.Debug do
1919
:error
2020
end
2121

22-
def puts_code_at_node(zipper) do
22+
def puts_code_at_node(%Zipper{} = zipper) do
2323
zipper
2424
|> Zipper.node()
2525
|> Sourceror.to_string()
@@ -29,6 +29,15 @@ defmodule Igniter.Util.Debug do
2929
zipper
3030
end
3131

32+
def puts_code_at_node(node) do
33+
node
34+
|> Sourceror.to_string()
35+
|> then(&"==code==\n#{&1}\n==!code==\n")
36+
|> IO.puts()
37+
38+
node
39+
end
40+
3241
@doc "Returns the formatted code at the node of the zipper to the console"
3342
def code_at_node(zipper) do
3443
zipper
@@ -37,7 +46,7 @@ defmodule Igniter.Util.Debug do
3746
end
3847

3948
@doc "Puts the ast at the node of the zipper to the console"
40-
def puts_ast_at_node(zipper) do
49+
def puts_ast_at_node(%Zipper{} = zipper) do
4150
zipper
4251
|> Zipper.node()
4352
|> then(&"==ast==\n#{inspect(&1, pretty: true)}\n==!ast==\n")

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ defmodule Igniter.MixProject do
104104
{:rewrite, "~> 1.1 and >= 1.1.1"},
105105
{:glob_ex, "~> 0.1.7"},
106106
{:spitfire, "~> 0.1 and >= 0.1.3"},
107-
{:sourceror, "~> 1.4"},
107+
{:sourceror, path: "../../oss/sourceror", override: true},
108108
{:jason, "~> 1.4"},
109109
{:req, "~> 0.5"},
110110
{:phx_new, "~> 1.7", optional: true},

test/igniter/project/config_test.exs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,4 +673,51 @@ defmodule Igniter.Project.ConfigTest do
673673
assert String.contains?(config, "config :fake, foo: [bar: true]")
674674
end
675675
end
676+
677+
describe "configure_group" do
678+
test "adds configuration with a comment above it" do
679+
test_project()
680+
|> Igniter.Project.Config.configure_group(
681+
"config.exs",
682+
:foo,
683+
[:bar],
684+
[
685+
{:baz, :buz}
686+
],
687+
comment: """
688+
Configures the foobar to
689+
accomplish the barbaz
690+
"""
691+
)
692+
|> assert_creates("config/config.exs", """
693+
import Config
694+
# Configures the foobar to
695+
# accomplish the barbaz
696+
config :foo, bar: [baz: :buz]
697+
import_config "\#{config_env()}.exs"
698+
""")
699+
end
700+
701+
test "alters configuration without adding a comment if the group is already configured" do
702+
test_project()
703+
|> Igniter.Project.Config.configure("config.exs", :foo, [:bar], [])
704+
|> apply_igniter!()
705+
|> Igniter.Project.Config.configure_group(
706+
"config.exs",
707+
:foo,
708+
[:bar],
709+
[
710+
{:baz, :buz}
711+
],
712+
comment: """
713+
Configures the foobar to
714+
accomplish the barbaz
715+
"""
716+
)
717+
|> assert_has_patch("config/config.exs", """
718+
- |config :foo, bar: []
719+
+ |config :foo, bar: [baz: :buz]
720+
""")
721+
end
722+
end
676723
end

test/igniter/project/formatter_test.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ defmodule Igniter.Project.FormatterTest do
4040
| end
4141
+ |
4242
+ | test "2" do
43-
+ | expect Foo.subtract(x, y), do: x - y
43+
+ | expect Foo.subtract(x, y),
44+
+ | do: x - y
4445
+ | end
4546
|end
4647
""")

test/igniter/project/test_test.exs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ defmodule Igniter.Project.TestTest do
3838
assert String.contains?(
3939
contents,
4040
"""
41-
defp elixirc_paths(:test), do: elixirc_paths(:dev) ++ [\"test/support\"]\n defp elixirc_paths(_), do: [\"lib\"]
41+
defp elixirc_paths(:test),
42+
do: elixirc_paths(:dev) ++ ["test/support"]
43+
44+
defp elixirc_paths(_),
45+
do: ["lib"]
4246
"""
4347
|> String.trim()
4448
)

0 commit comments

Comments
 (0)