Skip to content

Commit

Permalink
feat: add mneme.install using igniter
Browse files Browse the repository at this point in the history
  • Loading branch information
zachallaun committed Oct 27, 2024
1 parent 45333e7 commit 928e88a
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 0 deletions.
109 changes: 109 additions & 0 deletions lib/mix/tasks/mneme.install.ex
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
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ defmodule Mneme.MixProject do
{:sourceror, "~> 1.0"},
{:rewrite, "~> 0.10.1"},
{:file_system, "~> 1.0"},
{:igniter, github: "zachallaun/igniter", ref: "fe0deca"},

# Development / Test
{:benchee, "~> 1.0", only: :dev},
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"excoveralls": {:hex, :excoveralls, "0.18.3", "bca47a24d69a3179951f51f1db6d3ed63bca9017f476fe520eb78602d45f7756", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "746f404fcd09d5029f1b211739afb8fb8575d775b21f6a3908e7ce3e640724c6"},
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"},
"igniter": {:git, "https://github.com/zachallaun/igniter.git", "fe0decafa9caf4d05a9e9097f652d00eab353adf", [ref: "fe0deca"]},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
Expand All @@ -23,6 +24,7 @@
"patch": {:hex, :patch, "0.13.1", "2da5b508e4d6558924a0959d95dc3aa8176b5ccf2539e4567481448d61853ccc", [:mix], [], "hexpm", "75f805827d9db0c335155fbb857e6eeb5c85034c9dc668d146bc0bfe48fac822"},
"rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"},
"sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"},
"spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"},
"styler": {:hex, :styler, "1.1.1", "ccb55763316915b5de532bf14c587c211ddc86bc749ac676e74dfacd3894cc0d", [:mix], [], "hexpm", "80ce12fb862e13d998589eea7c1932f4e6ce9d6ded2182cb322f8f9b2b8d3632"},
Expand Down
207 changes: 207 additions & 0 deletions test/mix/tasks/mneme.install_test.exs
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

0 comments on commit 928e88a

Please sign in to comment.