-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add mneme.install using igniter
- Loading branch information
1 parent
45333e7
commit 928e88a
Showing
4 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
example = "mix mneme.install" | ||
|
||
defmodule Mix.Tasks.Mneme.Install do | ||
@shortdoc "Sets up Mneme in your project" | ||
@moduledoc """ | ||
#{@shortdoc} | ||
## Example | ||
```bash | ||
#{example} | ||
``` | ||
""" | ||
|
||
use Igniter.Mix.Task | ||
|
||
alias Igniter.Code.Function | ||
|
||
@example example | ||
|
||
def info(_argv, _composing_task) do | ||
%Igniter.Mix.Task.Info{ | ||
# Groups allow for overlapping arguments for tasks by the same author | ||
# See the generators guide for more. | ||
group: :mneme, | ||
# dependencies to add | ||
adds_deps: [], | ||
# dependencies to add and call their associated installers, if they exist | ||
installs: [], | ||
# An example invocation | ||
example: @example, | ||
# A list of environments that this should be installed in. | ||
only: nil, | ||
# a list of positional arguments, i.e `[:file]` | ||
positional: [], | ||
# Other tasks your task composes using `Igniter.compose_task`, passing in the CLI argv | ||
# This ensures your option schema includes options from nested tasks | ||
composes: [], | ||
# `OptionParser` schema | ||
schema: [], | ||
# Default values for the options in the `schema`. | ||
defaults: [], | ||
# CLI aliases | ||
aliases: [], | ||
# A list of options in the schema that are required | ||
required: [] | ||
} | ||
end | ||
|
||
def igniter(igniter, _argv) do | ||
igniter | ||
|> update_preferred_cli_env() | ||
|> update_import_deps() | ||
|> update_test_helper() | ||
end | ||
|
||
defp update_preferred_cli_env(igniter) do | ||
Igniter.update_elixir_file(igniter, "mix.exs", fn zipper -> | ||
# First, try to update a keyword literal in the project | ||
with {:ok, zipper} <- Function.move_to_def(zipper, :project, 0), | ||
{:ok, zipper} <- | ||
Igniter.Code.Keyword.put_in_keyword( | ||
zipper, | ||
[:preferred_cli_env, :"mneme.watch"], | ||
:test | ||
) do | ||
{:ok, zipper} | ||
else | ||
_ -> | ||
# Second, try to update a local function containing a keyword literal. | ||
# This handles `[preferred_cli_env: preferred_cli_env()]`. | ||
with {:ok, zipper} <- Function.move_to_def(zipper, :project, 0), | ||
{:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :preferred_cli_env), | ||
true <- Function.function_call?(zipper), | ||
{call_name, _, call_args} <- zipper.node, | ||
{:ok, zipper} <- move_to_def_or_defp(zipper, call_name, length(call_args)), | ||
{:ok, zipper} <- | ||
Igniter.Code.Keyword.put_in_keyword(zipper, [:"mneme.watch"], :test) do | ||
{:ok, zipper} | ||
else | ||
_ -> | ||
{:error, | ||
"Unable to update `:preferred_cli_env` to include `[\"mneme.watch\": :test]"} | ||
end | ||
end | ||
end) | ||
end | ||
|
||
defp update_import_deps(igniter) do | ||
Igniter.Project.Formatter.import_dep(igniter, :mneme) | ||
end | ||
|
||
defp update_test_helper(igniter) do | ||
Igniter.update_elixir_file(igniter, "test/test_helper.exs", fn zipper -> | ||
with :error <- Function.move_to_function_call(zipper, {Mneme, :start}, :any), | ||
{:ok, zipper} <- Function.move_to_function_call(zipper, {ExUnit, :start}, :any) do | ||
{:ok, Igniter.Code.Common.add_code(zipper, "Mneme.start()")} | ||
else | ||
_ -> {:ok, zipper} | ||
end | ||
end) | ||
end | ||
|
||
defp move_to_def_or_defp(zipper, call_name, arity) do | ||
with :error <- Igniter.Code.Function.move_to_def(zipper, call_name, arity) do | ||
Igniter.Code.Function.move_to_defp(zipper, call_name, arity) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
defmodule Mix.Tasks.Mneme.InstallTest do | ||
use ExUnit.Case | ||
use Mneme, default_pattern: :last | ||
|
||
import Igniter.Test | ||
|
||
defp igniter_diff(igniter, opts \\ []) do | ||
sources = | ||
if opts[:only] do | ||
Enum.filter(igniter.rewrite.sources, fn {_, source} -> | ||
source.path in List.wrap(opts[:only]) | ||
end) | ||
else | ||
igniter.rewrite.sources | ||
end | ||
|
||
Igniter.diff(sources, color?: false) | ||
end | ||
|
||
test "mix mneme.install performs all setup when a project hasn't installed Mneme" do | ||
auto_assert """ | ||
Update: .formatter.exs | ||
1 1 |# Used by "mix format" | ||
2 2 |[ | ||
3 - | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||
3 + | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], | ||
4 + | import_deps: [:mneme] | ||
4 5 |] | ||
5 6 | | ||
Update: mix.exs | ||
...| | ||
8 8 | elixir: "~> 1.17", | ||
9 9 | start_permanent: Mix.env() == :prod, | ||
10 - | deps: deps() | ||
10 + | deps: deps(), | ||
11 + | preferred_cli_env: ["mneme.watch": :test] | ||
11 12 | ] | ||
12 13 | end | ||
...| | ||
Update: test/test_helper.exs | ||
1 1 |ExUnit.start() | ||
2 + |Mneme.start() | ||
""" <- test_project() |> Igniter.compose_task("mneme.install") |> igniter_diff() | ||
end | ||
|
||
describe "mix.exs" do | ||
test "when :preferred_cli_env already exists" do | ||
test_project = | ||
test_project( | ||
files: %{ | ||
"mix.exs" => """ | ||
defmodule Test.MixProject do | ||
use Mix.Project | ||
def project do | ||
[ | ||
app: :test, | ||
preferred_cli_env: [ | ||
"existing.task": :dev | ||
] | ||
] | ||
end | ||
end | ||
""" | ||
} | ||
) | ||
|
||
auto_assert """ | ||
Update: mix.exs | ||
...| | ||
6 6 | app: :test, | ||
7 7 | preferred_cli_env: [ | ||
8 - | "existing.task": :dev | ||
8 + | "existing.task": :dev, | ||
9 + | "mneme.watch": :test | ||
9 10 | ] | ||
10 11 | ] | ||
...| | ||
""" <- | ||
test_project | ||
|> Igniter.compose_task("mneme.install") | ||
|> igniter_diff(only: "mix.exs") | ||
end | ||
|
||
test "when :preferred_cli_env already contains mneme.watch" do | ||
test_project = | ||
test_project( | ||
files: %{ | ||
"mix.exs" => """ | ||
defmodule Test.MixProject do | ||
use Mix.Project | ||
def project do | ||
[ | ||
app: :test, | ||
preferred_cli_env: [ | ||
"mneme.watch": :test | ||
] | ||
] | ||
end | ||
end | ||
""" | ||
} | ||
) | ||
|
||
auto_assert "" <- | ||
test_project | ||
|> Igniter.compose_task("mneme.install") | ||
|> igniter_diff(only: "mix.exs") | ||
end | ||
|
||
test "when :preferred_cli_env is a call to a local function" do | ||
test_project = | ||
test_project( | ||
files: %{ | ||
"mix.exs" => """ | ||
defmodule Test.MixProject do | ||
use Mix.Project | ||
def project do | ||
[ | ||
app: :test, | ||
preferred_cli_env: preferred_cli_env() | ||
] | ||
end | ||
defp preferred_cli_env do | ||
[ | ||
"existing.task": :test | ||
] | ||
end | ||
end | ||
""" | ||
} | ||
) | ||
|
||
auto_assert """ | ||
Update: mix.exs | ||
...| | ||
11 11 | defp preferred_cli_env do | ||
12 12 | [ | ||
13 - | "existing.task": :test | ||
13 + | "existing.task": :test, | ||
14 + | "mneme.watch": :test | ||
14 15 | ] | ||
15 16 | end | ||
...| | ||
""" <- | ||
test_project | ||
|> Igniter.compose_task("mneme.install") | ||
|> igniter_diff(only: "mix.exs") | ||
end | ||
end | ||
|
||
describe "test/test_helper.exs" do | ||
test "when Mneme.start() is already present" do | ||
test_project = | ||
test_project( | ||
files: %{ | ||
"test/test_helper.exs" => """ | ||
ExUnit.start() | ||
Mneme.start() | ||
""" | ||
} | ||
) | ||
|
||
auto_assert "" <- | ||
test_project | ||
|> Igniter.compose_task("mneme.install") | ||
|> igniter_diff(only: "test/test_helper.exs") | ||
end | ||
|
||
test "when ExUnit.start/1 has options" do | ||
test_project = | ||
test_project( | ||
files: %{ | ||
"test/test_helper.exs" => """ | ||
ExUnit.start(exclude: :integration) | ||
""" | ||
} | ||
) | ||
|
||
auto_assert """ | ||
Update: test/test_helper.exs | ||
1 1 |ExUnit.start(exclude: :integration) | ||
2 + |Mneme.start() | ||
""" <- | ||
test_project | ||
|> Igniter.compose_task("mneme.install") | ||
|> igniter_diff(only: "test/test_helper.exs") | ||
end | ||
end | ||
end |