Skip to content

Commit

Permalink
improvement: move some files around and update config names
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Jul 2, 2024
1 parent b878903 commit 2c2a2e3
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .igniter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# For option documentation, see hexdocs.pm/igniter/Igniter.Project.IgniterConfig.html
# To keep it up to date, use `mix igniter.setup`

[leaf_module_location: :outside_folder]
[module_location: :outside_matching_folder]
55 changes: 55 additions & 0 deletions documentation/configuring-igniter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Configuring Igniter

This guide is for those who are _end-users_ of igniter, for example, using the generators provided by a library that are backed by igniter.

## Setting up igniter

Use `mix igniter.setup` to create a `.igniter.exs` file in your project root. This file configures igniter for your project. You can run this command repeatedly to keep the file up to date over time.

See the documentation in `Igniter.Project.IgniterConfig` for available configuration.

## Moving modules

One available configuration is `module_location`. This configuration dictates where modules are placed when there is a folder that exactly matches their module name. There are two available strategies for this, and with igniter not only can you change your mind, but you can actually _move back and forth_ between each strategy. To move any modules to their rightful place, use `mix igniter.move_modules`.

> ### Only for matching modules {: .tip}
>
> The following rules are _only applied_ when a top-level moduel is defined in the file. If it is not, then the file will always be left exactly where it is. It is generally considered best-practice to define one top-level module per file.
## `:outside_matching_folder`

The "standard" way to place a module is to place it in a folder path that exactly matches its module name, inside of `lib/`. For example, a module named `MyApp.MyModule` would be placed in `lib/my_app/my_module.ex`.

Use the default `:outside_matching_folder` to follow this convention in all cases.

## `:inside_matching_folder`

What some people don't like about the previously described strategy is that it can split up related modules. For example:

```
lib/
└── my_app/
├── accounts/
│ ├── user.ex
│ ├── organization.ex
├── social/
│ ├── post.ex
│ ├── comment.ex
├── accounts.ex # <- This feels to some like it should be in `/accounts`
└── social.ex
```

They would prefer to put that leaf-node module in its matching folder _if it exists_, and otherwise follow the original convention if not.

```
lib/
└── my_app/
├── accounts/
│ ├── user.ex
│ ├── organization.ex
│ ├── accounts.ex
├── social/
│ ├── post.ex
│ ├── comment.ex
│ └── social.ex
```
12 changes: 8 additions & 4 deletions lib/igniter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ defmodule Igniter do
:dry_run_with_changes
else
unless opts[:quiet_on_no_changes?] || "--yes" in argv do
Mix.shell().info("\n#{title}: No proposed changes!\n")
Mix.shell().info("\n#{title}: No proposed content changes!\n")
end

:dry_run_with_no_changes
Expand All @@ -586,7 +586,7 @@ defmodule Igniter do
end

unless Enum.empty?(igniter.moves) do
Mix.shell().info("The following fils will be moved:")
Mix.shell().info("The following files will be moved:")

Enum.each(igniter.moves, fn {from, to} ->
Mix.shell().info(
Expand All @@ -610,15 +610,19 @@ defmodule Igniter do
""")
end

if "--dry-run" in argv || result_of_dry_run == :dry_run_with_no_changes do
if "--dry-run" in argv ||
(result_of_dry_run == :dry_run_with_no_changes && Enum.empty?(igniter.tasks) &&
Enum.empty?(igniter.moves)) do
result_of_dry_run
else
if "--yes" in argv ||
Mix.shell().yes?(opts[:confirmation_message] || "Proceed with changes?") do
sources
|> Enum.any?(fn source ->
Rewrite.Source.updated?(source)
Rewrite.Source.from?(source, :string) || Rewrite.Source.updated?(source)
end)
|> Kernel.||(!Enum.empty?(igniter.tasks))
|> Kernel.||(!Enum.empty?(igniter.tasks))
|> if do
igniter.rewrite
|> Rewrite.write_all()
Expand Down
2 changes: 2 additions & 0 deletions lib/igniter/code/list.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ defmodule Igniter.Code.List do
if list?(zipper) do
Common.within(zipper, fn zipper ->
zipper
|> Igniter.Code.Common.maybe_move_to_single_child_block()
|> Zipper.down()
|> Igniter.Util.Debug.puts_code_at_node()
|> Common.nth_right(index)
|> case do
:error ->
Expand Down
124 changes: 60 additions & 64 deletions lib/igniter/code/module.ex
Original file line number Diff line number Diff line change
Expand Up @@ -124,40 +124,36 @@ defmodule Igniter.Code.Module do
end

@doc false
def move_modules(igniter) do
if Igniter.Project.IgniterConfig.get(igniter, :leaf_module_location) == :inside_folder do
igniter = Igniter.include_glob(igniter, "lib/**/*.ex")

igniter.rewrite
|> Enum.filter(&(Path.extname(&1.path) == ".ex"))
|> Enum.reduce(igniter, fn source, igniter ->
zipper =
source
|> Rewrite.Source.get(:quoted)
|> Zipper.zip()

with {:ok, zipper} <- Igniter.Code.Module.move_to_defmodule(zipper),
{:defmodule, _, [module | _]} <-
zipper
|> Igniter.Code.Common.expand_aliases()
|> Zipper.subtree()
|> Zipper.node(),
module when not is_nil(module) <- to_module_name(module),
true <-
module_matches_path?(module, source.path),
new_path when not is_nil(new_path) <- should_move_file_to(igniter.rewrite, source) do
Igniter.move_file(igniter, source.path, new_path, error_if_exists?: false)
else
_ ->
igniter
end
end)
else
igniter
end
def move_modules(igniter, opts \\ []) do
module_location_config = Igniter.Project.IgniterConfig.get(igniter, :module_location)
igniter = Igniter.include_glob(igniter, "lib/**/*.ex")

igniter.rewrite
|> Enum.filter(&(Path.extname(&1.path) == ".ex"))
|> Enum.reduce(igniter, fn source, igniter ->
zipper =
source
|> Rewrite.Source.get(:quoted)
|> Zipper.zip()

with {:ok, zipper} <- Igniter.Code.Module.move_to_defmodule(zipper),
{:defmodule, _, [module | _]} <-
zipper
|> Igniter.Code.Common.expand_aliases()
|> Zipper.subtree()
|> Zipper.node(),
module when not is_nil(module) <- to_module_name(module),
new_path when not is_nil(new_path) <-
should_move_file_to(igniter.rewrite, source, module, module_location_config, opts) do
Igniter.move_file(igniter, source.path, new_path, error_if_exists?: false)
else
_ ->
igniter
end
end)
end

defp should_move_file_to(rewrite, source) do
defp should_move_file_to(rewrite, source, module, module_location_config, opts) do
all_paths = Rewrite.paths(rewrite)

paths_created =
Expand All @@ -170,25 +166,41 @@ defmodule Igniter.Code.Module do
last = source.path |> Path.split() |> List.last() |> Path.rootname()

path_it_might_live_in =
source.path
|> Path.split()
|> Enum.reverse()
|> Enum.drop(1)
|> Enum.reverse()
|> Enum.concat([last])
case module_location_config do
:inside_matching_folder ->
source.path
|> Path.split()
|> Enum.reverse()
|> Enum.drop(1)
|> Enum.reverse()
|> Enum.concat([last])

:outside_matching_folder ->
module
|> proper_location()
|> Path.split()
|> Enum.reverse()
|> Enum.drop(1)
|> Enum.reverse()
end

if Rewrite.Source.from?(source, :string) do
Enum.any?(all_paths, fn path ->
List.starts_with?(Path.split(path), path_it_might_live_in)
end)
if opts[:move_all?] ||
(module_location_config == :inside_matching_folder &&
Enum.any?(all_paths, fn path ->
List.starts_with?(Path.split(path), path_it_might_live_in)
end)) do
path_it_might_live_in
end
else
# only move a file if we just created its new home
if File.dir?(Path.join(path_it_might_live_in)) do
false
else
Enum.any?(paths_created, fn path ->
List.starts_with?(Path.split(path), path_it_might_live_in)
end)
# only move a file if we just created its new home, or if `move_all?` is set
if opts[:move_all?] ||
(!File.dir?(Path.join(path_it_might_live_in)) &&
module_location_config == :inside_matching_folder &&
Enum.any?(paths_created, fn path ->
List.starts_with?(Path.split(path), path_it_might_live_in)
end)) do
path_it_might_live_in
end
end
|> if do
Expand All @@ -202,22 +214,6 @@ defmodule Igniter.Code.Module do
defp to_module_name(value) when is_atom(value) and not is_nil(value), do: value
defp to_module_name(_), do: nil

defp module_matches_path?(module, path) do
split = Module.split(module)

rootname = Path.split(path) |> List.last() |> Path.rootname()

split_from_path =
path
|> Path.dirname()
|> Path.join(rootname)
|> Path.split()
|> Enum.drop(1)
|> Enum.map(&Macro.camelize/1)

split_from_path == split
end

defp do_proper_location(module_name, kind) do
path =
module_name
Expand Down
5 changes: 3 additions & 2 deletions lib/igniter/project/deps.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ defmodule Igniter.Project.Deps do
false
end
end
end) do
Igniter.Code.List.remove_index(zipper, current_declaration_index)
end),
{:ok, zipper} <- Igniter.Code.List.remove_index(zipper, current_declaration_index) do
{:ok, zipper}
else
_ ->
{:warning,
Expand Down
10 changes: 5 additions & 5 deletions lib/igniter/project/igniter_config.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
defmodule Igniter.Project.IgniterConfig do
@configs [
leaf_module_location: [
type: {:in, [:outside_folder, :inside_folder]},
default: :outside_folder,
module_location: [
type: {:in, [:outside_matching_folder, :inside_matching_folder]},
default: :outside_matching_folder,
doc: """
- `:outside_folder`, modules will be placed in a folder exactly matching their path.
- `:inside_folder`, modules who's name matches an existing folder will be placed inside that folder,
- `:outside_matching_folder`, modules will be placed in a folder exactly matching their path.
- `:inside_matching_folder`, modules who's name matches an existing folder will be placed inside that folder,
or moved there if the folder is created.
"""
]
Expand Down
5 changes: 5 additions & 0 deletions lib/igniter/util/install.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ defmodule Igniter.Util.Install do
Enum.map(List.wrap(install), &to_string/1)
end

if Enum.any?(install_list, &(&1 == "igniter")) do
raise ArgumentError,
"cannot install the igniter package with `mix igniter.install`. Please use `mix igniter.setup` instead."
end

Application.ensure_all_started(:req)

task_installs =
Expand Down
File renamed without changes.
File renamed without changes.
9 changes: 9 additions & 0 deletions lib/mix/tasks/igniter.move_modules.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Mix.Tasks.Igniter.MoveModules do
@moduledoc "Moves all modules to their 'correct' location."
@shortdoc @moduledoc
use Igniter.Mix.Task

def igniter(igniter, _argv) do
Igniter.Code.Module.move_modules(igniter, move_all?: true)
end
end
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
defmodule Mix.Tasks.Igniter.Setup do
@moduledoc "Creates or updates a .igniter.exs file, used to configure Igniter for end user's preferences."

@shortdoc @moduledoc
use Igniter.Mix.Task

def igniter(igniter, _argv) do
Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ defmodule Igniter.MixProject do
extras: [
{"README.md", title: "Home"},
"documentation/writing-generators.md",
"documentation/configuring-igniter.md",
"CHANGELOG.md"
],
before_closing_head_tag: fn type ->
Expand Down
4 changes: 2 additions & 2 deletions test/code/module_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Igniter.Code.ModuleTest do
%{rewrite: rewrite} =
Igniter.new()
|> Igniter.assign(:igniter_exs,
leaf_module_location: :inside_folder
module_location: :inside_matching_folder
)
|> Igniter.include_or_create_elixir_file("lib/foo/bar.ex", "defmodule Foo.Bar do\nend")
|> Igniter.include_or_create_elixir_file(
Expand Down Expand Up @@ -89,7 +89,7 @@ defmodule Igniter.Code.ModuleTest do
%{rewrite: rewrite} =
Igniter.new()
|> Igniter.assign(:igniter_exs,
leaf_module_location: :inside_folder
module_location: :inside_matching_folder
)
|> Igniter.create_new_elixir_file("lib/foo/bar/something.ex", """
defmodule Foo.Bar.Something do
Expand Down

0 comments on commit 2c2a2e3

Please sign in to comment.