great questions!
So there are two levels of granularity that are relevant here. There is a “task”, which is an abstraction over a mix task with one minor change, which is that you only define an igniter/2
function, which takes an igniter (a small wrapper around a Rewrite
) and returns an igniter. This enables chaining. For example, here is a very early version of mix igniter.install.ash.resource
defmodule Mix.Tasks.Igniter.Gen.Ash.Resource do
use Igniter.Mix.Task
@impl Igniter.Mix.Task
def igniter(igniter, argv) do
with {:ok, domain_underscore} <-
Igniter.Args.validate_nth_present_and_underscored(
igniter,
argv,
0,
:domain,
"Required first argument (snake_case domain) is missing"
),
{:ok, resource_underscore} <-
Igniter.Args.validate_nth_present_and_underscored(
igniter,
argv,
1,
:resource,
"Required second argument (snake_case domain) is missing"
) do
app_name = Igniter.Tasks.app_name()
domain_module_name = Igniter.Module.module_name(Macro.camelize(domain_underscore))
resource_module_name =
Module.concat([domain_module_name, Macro.camelize(resource_underscore)])
igniter
|> Igniter.compose_task("igniter.gen.ash.resource_reference", argv)
|> Igniter.create_new_elixir_file(
"lib/#{app_name}/#{domain_underscore}/#{resource_underscore}.ex",
"""
defmodule #{inspect(resource_module_name)} do
use Ash.Resource,
domain: #{inspect(domain_module_name)}
actions do
defaults [:read, :destroy, create: [], update: []]
end
attributes do
uuid_primary_key :id
end
end
"""
)
else
{:error, igniter} ->
igniter
end
end
end
That level of granularity would be called a task
, and tasks can be composed with Igniter.compose_task/2
. This means any task can be called via mix
or composed, which is a very useful property.
At a lower level of granularity, you have individual functions that will modify a rewrite
to make some code modification. We don’t currently have a name for these aside from “functions”, but you could perhaps call them “codemods”. Here is an example snippet from mix igniter.install.ash
igniter
|> Igniter.Deps.add_dependency(:picosat_elixir, "~> 0.2")
|> Igniter.compose_task("igniter.install.spark", argv)
|> Igniter.Formatter.import_dep(:ash)
|> Igniter.Config.configure("config.exs", :spark, [:formatter, :"Ash.Resource"], [], fn x ->
x
end)
|> Igniter.Config.configure("config.exs", :spark, [:formatter, :"Ash.Domain"], [], fn x ->
x
end)
add_dependency
, import_dep
and configure
are examples of the building blocks Igniter will provide.
As you can see above, it is functional composition. A playbook/DSL version may come some day, but is not necessary to start.
When you run an igniter task, it displays a diff of all changes. Here is example output from running mix igniter.install ash
in a new package (ignore the bit about the local dependency. You have to use a local dependency to test/work on your igniter ![:slight_smile: :slight_smile:](https://elixirforum.com/images/emoji/apple/slight_smile.png?v=12)
Not looking up dependency, because a local dependency is detected
Igniter: Installing ash...
Igniter: Proposed changes:
.formatter.exs
1 + |# Used by "mix format"
2 + |[
3 + | import_deps: [:ash],
4 + | plugins: [Spark.Formatter],
5 + | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
6 + |]
1 7 |
2 - |
config/config.exs
1 + |import Config
2 + |config :spark, formatter: ["Ash.Domain": [], "Ash.Resource": [], remove_parens?: true]
3 + |config :igniter_example, :ash_domains, [IgniterExample.ExampleDomain]
1 4 |
2 - |
lib/igniter_example/example_domain.ex
1 + |defmodule IgniterExample.ExampleDomain do
2 + | use Ash.Domain
1 3 |
4 + | resources do
5 + | resource(IgniterExample.ExampleDomain.ExampleResource)
6 + | end
7 + |end
lib/igniter_example/example_domain/example_resource.ex
1 + |defmodule IgniterExample.ExampleDomain.ExampleResource do
2 + | use Ash.Resource,
3 + | domain: IgniterExample.ExampleDomain
1 4 |
5 + | actions do
6 + | defaults([:read, :destroy, create: [], update: []])
7 + | end
2 8 |
9 + | attributes do
10 + | uuid_primary_key(:id)
11 + | end
12 + |end
13 + |
Proceed with changes? [Yn]
Since those are all new files, I need to figure out a way to hide the left side of those line numbers representing the original files, just haven’t done that yet ![:slight_smile: :slight_smile:](https://elixirforum.com/images/emoji/apple/slight_smile.png?v=12)
The codebase is not quite ready, but I will share it before too long.