diff --git a/documentation/writing-installers.md b/documentation/writing-installers.md new file mode 100644 index 0000000..63c2e6f --- /dev/null +++ b/documentation/writing-installers.md @@ -0,0 +1,100 @@ +# Writing Installers + +Generate the install task: +``` +mix igniter.gen.task my_dep.install +``` +All of the work will be done in the igniter call: + +```elixir +@impl Igniter.Mix.Task +def igniter(igniter) do + # Do your work here and return an updated igniter + igniter + |> Igniter.add_warning("mix routex.install is not yet implemented") +end +``` + +## Fetching Dependency + +```elixir +if Igniter.Project.Deps.has_dep?(igniter, :my_dep) do + igniter +else + igniter + |> Igniter.Project.Deps.add_dep({:my_dep, "~> 1.0"}) + |> Igniter.apply_and_fetch_dependencies(error_on_abort?: true, yes_to_deps: true) +end +``` + +Check does the dependency already exist and if not add it and apply it. + + +## Creating a module + +```elixir +Igniter.Project.Module.create_module(igniter, module, """ +use MyDep.Module +""") +``` + +Create a new module that uses some module from your application. + +## Modifying a module + +For example: + +```elixir +defmodule SomeModule do + alias Some.Example + + def some_function(a, b) do + Example.function(a, b) + end +end +``` + +Find that module and update it: + +```elixir +Igniter.Project.Module.find_and_update_module!(igniter, SomeModule, fn zipper -> + {:ok, igniter} +end) +``` + +In the function block use [`within/2`](https://hexdocs.pm/igniter/Igniter.Code.Common.html#within/2) to do multiple modifications. Find the part you want to change and modify it. + +For example: + - replace alias with an import: +```elixir +Igniter.Code.Common.within(fn zipper -> + pred = &match?(%Zipper{node: {:alias, _, _}}, &1) + zipper = Common.remove(zipper, pred) + line = "import Some.Example, only: [:function]" + {:ok, Common.add_code(zipper, line, placement: :before)} +end) +``` + + - replace function block: + ```elixir +Igniter.Code.Common.within(fn zipper -> + {:ok, zipper} = move_to_function(zipper, :some_function) + {:ok, zipper} = Common.move_to_do_block(zipper) + line = "my_private_function!(function(a, b))" + {:ok, Igniter.Code.Common.replace_code(zipper, line)} +end) +``` + + - add a block: + ```elixir +Igniter.Code.Common.within(fn zipper -> + block = """ + defp private_function!({:ok, result}), do: result + defp private_function!(_), do: raise "Something went wrong!" + """ + + {:ok, Common.add_code(zipper, block, placement: :after)} +end) +``` + +You can chain within blocks using `with`. \ No newline at end of file diff --git a/mix.exs b/mix.exs index 4a0a50c..7c783ac 100644 --- a/mix.exs +++ b/mix.exs @@ -53,6 +53,7 @@ defmodule Igniter.MixProject do extras: [ {"README.md", title: "Home"}, "documentation/writing-generators.md", + "documentation/writing-installers.md", "documentation/configuring-igniter.md", "documentation/upgrades.md", "CHANGELOG.md" diff --git a/test/igniter/code/within_test.exs b/test/igniter/code/within_test.exs new file mode 100644 index 0000000..99bf4a6 --- /dev/null +++ b/test/igniter/code/within_test.exs @@ -0,0 +1,82 @@ +defmodule Igniter.Code.WithinTest do + alias Igniter.Code.Common + alias Sourceror.Zipper + use ExUnit.Case + import Igniter.Test + + describe "with within" do + test "performs multiple changes to some module" do + test_project() + |> Igniter.create_new_file("lib/some_module.ex", """ + defmodule SomeModule do + alias Some.Example + + def some_function(a, b) do + Example.function(a, b) + end + end + """) + |> apply_igniter!() + |> Igniter.Project.Module.find_and_update_module!(SomeModule, fn zipper -> + zipper + |> within!(fn zipper -> + pred = &match?(%Zipper{node: {:alias, _, _}}, &1) + zipper = Common.remove(zipper, pred) + line = "import Some.Example, only: [:function]" + {:ok, Common.add_code(zipper, line, placement: :before)} + end) + |> within!(fn zipper -> + {:ok, zipper} = move_to_function(zipper, :some_function) + {:ok, zipper} = Common.move_to_do_block(zipper) + line = "my_private_function!(function(a, b))" + {:ok, Igniter.Code.Common.replace_code(zipper, line)} + end) + |> within!(fn zipper -> + block = """ + defp private_function!({:ok, result}), do: result + defp private_function!(_), do: raise "Something went wrong!" + """ + + {:ok, Common.add_code(zipper, block, placement: :after)} + end) + |> then(fn zipper -> {:ok, zipper} end) + end) + |> Igniter.Refactors.Rename.rename_function( + {SomeModule, :some_function}, + {SomeModule, :some_function!}, + arity: 2 + ) + |> assert_has_patch("lib/some_module.ex", """ + |defmodule SomeModule do + - | alias Some.Example + + | import Some.Example, only: [:function] + | + - | def some_function(a, b) do + - | Example.function(a, b) + + | def some_function!(a, b) do + + | my_private_function!(function(a, b)) + | end + + | + + | defp private_function!({:ok, result}), + + | do: result + + | + + | defp private_function!(_), + + | do: raise("Something went wrong!") + |end + """) + end + end + + defp within!(zipper, function) do + case Common.within(zipper, function) do + {:ok, zipper} -> zipper + :error -> raise "Error calling within" + end + end + + defp move_to_function(zipper, function) do + Igniter.Code.Common.move_to(zipper, fn zipper -> + Igniter.Code.Function.function_call?(zipper, function) + end) + end +end