mirror of
https://github.com/ash-project/igniter.git
synced 2024-09-19 13:02:51 +12:00
init
This commit is contained in:
commit
7ae0261a28
19 changed files with 1415 additions and 0 deletions
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
igniter-*.tar
|
||||
|
||||
# Temporary files, for example, from tests.
|
||||
/tmp/
|
24
README.md
Normal file
24
README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Igniter
|
||||
|
||||
**TODO: Add description**
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `igniter` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:igniter, "~> 0.1.0"}
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||
be found at <https://hexdocs.pm/igniter>.
|
||||
|
||||
# TODO list:
|
||||
|
||||
- [ ] properly parse args, not `"--dry-run" in argv`. Do we want to have some kind of "maybe parsed" args structure so we can call tasks with `argv` or one of those? Maybe.
|
18
lib/args.ex
Normal file
18
lib/args.ex
Normal file
|
@ -0,0 +1,18 @@
|
|||
defmodule Igniter.Args do
|
||||
def validate_present_and_underscored(igniter, opts, option, message) do
|
||||
cond do
|
||||
!opts[option] ->
|
||||
{:error, Igniter.add_issue(igniter, message)}
|
||||
|
||||
not (Macro.underscore(opts[option]) == opts[option]) ->
|
||||
{:error,
|
||||
Igniter.add_issue(
|
||||
igniter,
|
||||
"Must provide the #{option} in snake_case. Did you mean `#{Macro.underscore(opts[:option])}`"
|
||||
)}
|
||||
|
||||
true ->
|
||||
{:ok, opts[option]}
|
||||
end
|
||||
end
|
||||
end
|
580
lib/common.ex
Normal file
580
lib/common.ex
Normal file
|
@ -0,0 +1,580 @@
|
|||
defmodule Igniter.Common do
|
||||
alias Sourceror.Zipper
|
||||
|
||||
def find(zipper, direction \\ :next, pred) do
|
||||
Zipper.find(zipper, direction, fn thing ->
|
||||
try do
|
||||
pred.(thing)
|
||||
rescue
|
||||
FunctionClauseError ->
|
||||
false
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defmacro node_matches_pattern?(zipper, pattern) do
|
||||
quote do
|
||||
ast =
|
||||
unquote(zipper)
|
||||
|> Igniter.Common.maybe_enter_block()
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|
||||
match?(unquote(pattern), ast)
|
||||
end
|
||||
end
|
||||
|
||||
defmacro find_pattern(zipper, direction \\ :next, pattern) do
|
||||
quote do
|
||||
Sourceror.Zipper.find(unquote(zipper), unquote(direction), fn
|
||||
unquote(pattern) ->
|
||||
true
|
||||
|
||||
_ ->
|
||||
false
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defmacro argument_matches_pattern?(zipper, index, pattern) do
|
||||
quote do
|
||||
Igniter.Common.argument_matches_predicate?(
|
||||
unquote(zipper),
|
||||
unquote(index),
|
||||
&match?(unquote(pattern), &1)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def puts_code_at_node(zipper) do
|
||||
zipper
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|> Sourceror.to_string()
|
||||
|> IO.puts()
|
||||
|
||||
zipper
|
||||
end
|
||||
|
||||
def add_code(zipper, new_code) do
|
||||
current_code =
|
||||
zipper
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|> IO.inspect()
|
||||
|
||||
case current_code do
|
||||
{:__block__, block_meta, stuff} ->
|
||||
Zipper.replace(zipper, {:__block__, block_meta, stuff ++ [new_code]})
|
||||
|
||||
code ->
|
||||
Zipper.replace(zipper, {:__block__, [], [code, new_code]})
|
||||
end
|
||||
end
|
||||
|
||||
def put_in_keyword(zipper, path, value, updater \\ nil) do
|
||||
updater = updater || fn _ -> value end
|
||||
|
||||
do_put_in_keyword(zipper, path, value, updater)
|
||||
end
|
||||
|
||||
defp do_put_in_keyword(zipper, [key], value, updater) do
|
||||
set_keyword_key(zipper, key, value, updater)
|
||||
end
|
||||
|
||||
defp do_put_in_keyword(zipper, [key | rest], value, updater) do
|
||||
if node_matches_pattern?(zipper, value when is_list(value)) do
|
||||
case find_list_item(zipper, fn item ->
|
||||
if is_tuple?(item) do
|
||||
first_elem = tuple_elem(item, 0)
|
||||
first_elem && node_matches_pattern?(first_elem, ^key)
|
||||
end
|
||||
end) do
|
||||
nil ->
|
||||
value = keywordify(rest, value)
|
||||
|
||||
prepend_to_list(
|
||||
zipper,
|
||||
{{:__block__, [format: :keyword], [key]}, {:__block__, [], [value]}}
|
||||
)
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> tuple_elem(1)
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
do_put_in_keyword(zipper, rest, value, updater)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_keyword_key(zipper, key, value, updater) do
|
||||
if node_matches_pattern?(zipper, value when is_list(value)) do
|
||||
case find_list_item(zipper, fn item ->
|
||||
if is_tuple?(item) do
|
||||
first_elem = tuple_elem(item, 0)
|
||||
first_elem && node_matches_pattern?(first_elem, ^key)
|
||||
end
|
||||
end) do
|
||||
nil ->
|
||||
prepend_to_list(
|
||||
zipper,
|
||||
{{:__block__, [format: :keyword], [key]}, {:__block__, [], [value]}}
|
||||
)
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> tuple_elem(1)
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
updater.(zipper)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_function_call_in_current_scope(zipper, name, arity, predicate \\ fn _ -> true end) do
|
||||
case Zipper.down(zipper) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
find_right(zipper, fn zipper ->
|
||||
is_function_call(zipper, name, arity) && predicate.(zipper)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def is_function_call(zipper, name, arity) do
|
||||
zipper
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|> case do
|
||||
{^name, _, args} ->
|
||||
Enum.count(args) == arity
|
||||
|
||||
{{^name, _, context}, _, args} when is_atom(context) ->
|
||||
Enum.count(args) == arity
|
||||
|
||||
{:|>, _, [{^name, _, context} | rest]} when is_atom(context) ->
|
||||
Enum.count(rest) == arity - 1
|
||||
|
||||
{:|>, _, [^name | rest]} ->
|
||||
Enum.count(rest) == arity - 1
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def update_nth_argument(zipper, index, func) do
|
||||
if is_pipeline?(zipper) do
|
||||
if index == 0 do
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
func.(zipper)
|
||||
end
|
||||
else
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> Zipper.rightmost()
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> nth_right(index)
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
nth ->
|
||||
func.(nth)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> nth_right(index)
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
nth ->
|
||||
func.(nth)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def argument_matches_predicate?(zipper, index, func) do
|
||||
if is_pipeline?(zipper) do
|
||||
if index == 0 do
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil -> nil
|
||||
zipper -> func.(zipper)
|
||||
end
|
||||
else
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> Zipper.rightmost()
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> nth_right(index - 1)
|
||||
|> maybe_enter_block()
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|> func.()
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
false
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> nth_right(index)
|
||||
|> maybe_enter_block()
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|> func.()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def is_pipeline?(zipper) do
|
||||
zipper
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|> case do
|
||||
{:|>, _, _} -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def move_to_module_using(zipper, module) do
|
||||
split_module =
|
||||
module
|
||||
|> Module.split()
|
||||
|> Enum.map(&String.to_atom/1)
|
||||
|
||||
with zipper when not is_nil(zipper) <- find_pattern(zipper, {:defmodule, _, [_, _]}),
|
||||
subtree <- Zipper.subtree(zipper),
|
||||
subtree <- subtree |> Zipper.down() |> Zipper.rightmost(),
|
||||
subtree <- remove_module_definitions(subtree),
|
||||
found when not is_nil(found) <-
|
||||
find(subtree, fn
|
||||
{:use, _, [^module]} ->
|
||||
true
|
||||
|
||||
{:use, _, [{:__aliases__, _, ^split_module}]} ->
|
||||
true
|
||||
end),
|
||||
{:ok, zipper} <- move_to_do_block(zipper) do
|
||||
{:ok, zipper}
|
||||
else
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
# aliases will confuse this, but that is a later problem :)
|
||||
def equal_modules?(zipper, module) do
|
||||
root =
|
||||
zipper
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|
||||
do_equal_modules?(root, module)
|
||||
end
|
||||
|
||||
defp do_equal_modules?(left, left), do: true
|
||||
defp do_equal_modules?({:__aliases__, _, mod}, {:__aliases__, _, mod}), do: true
|
||||
|
||||
defp do_equal_modules?({:__aliases__, _, mod}, right) when is_atom(right) do
|
||||
Module.concat(mod) == right
|
||||
end
|
||||
|
||||
defp do_equal_modules?(left, {:__aliases__, _, mod}) when is_atom(left) do
|
||||
Module.concat(mod) == left
|
||||
end
|
||||
|
||||
defp do_equal_modules?(_, _), do: false
|
||||
|
||||
def move_to_defp(zipper, fun, arity) do
|
||||
case find_pattern(zipper, {:defp, _, [{^fun, _, args}, _]} when length(args) == arity) do
|
||||
nil ->
|
||||
if arity == 0 do
|
||||
case find_pattern(zipper, {:defp, _, [{^fun, _, context}, _]} when is_atom(context)) do
|
||||
nil ->
|
||||
:error
|
||||
|
||||
zipper ->
|
||||
move_to_do_block(zipper)
|
||||
end
|
||||
else
|
||||
:error
|
||||
end
|
||||
|
||||
zipper ->
|
||||
move_to_do_block(zipper)
|
||||
end
|
||||
end
|
||||
|
||||
def move_to_do_block(zipper) do
|
||||
case find_pattern(zipper, {{:__block__, _, [:do]}, _}) do
|
||||
nil ->
|
||||
:error
|
||||
|
||||
zipper ->
|
||||
{:ok,
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|> Zipper.rightmost()
|
||||
|> maybe_enter_block()}
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_enter_block(nil), do: nil
|
||||
|
||||
def maybe_enter_block(zipper) do
|
||||
zipper
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|> case do
|
||||
{:__block__, _, [_]} ->
|
||||
Zipper.down(zipper)
|
||||
|
||||
_ ->
|
||||
zipper
|
||||
end
|
||||
end
|
||||
|
||||
def remove_module_definitions(zipper) do
|
||||
Sourceror.Zipper.traverse(zipper, fn
|
||||
{:defmodule, _, _} ->
|
||||
nil
|
||||
|
||||
other ->
|
||||
other
|
||||
end)
|
||||
end
|
||||
|
||||
def prepend_new_to_list(zipper, quoted, equality_pred \\ &default_equality_pred/2) do
|
||||
zipper
|
||||
|> find_list_item_index(fn value ->
|
||||
equality_pred.(value, quoted)
|
||||
end)
|
||||
|> case do
|
||||
nil ->
|
||||
zipper
|
||||
|> maybe_enter_block()
|
||||
|> Zipper.insert_child(quoted)
|
||||
|
||||
_ ->
|
||||
zipper
|
||||
end
|
||||
end
|
||||
|
||||
defp default_equality_pred(zipper, quoted) do
|
||||
zipper
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|> Kernel.==(quoted)
|
||||
end
|
||||
|
||||
def prepend_to_list(zipper, quoted) do
|
||||
zipper
|
||||
|> maybe_enter_block()
|
||||
|> Zipper.insert_child(quoted)
|
||||
end
|
||||
|
||||
def remove_index(zipper, index) do
|
||||
zipper
|
||||
|> maybe_enter_block()
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
zipper
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> do_remove_index(index)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_remove_index(zipper, 0) do
|
||||
Zipper.remove(zipper)
|
||||
end
|
||||
|
||||
defp do_remove_index(zipper, i) do
|
||||
zipper
|
||||
|> Zipper.right()
|
||||
|> case do
|
||||
nil ->
|
||||
zipper
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> do_remove_index(i - 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp nth_right(zipper, 0) do
|
||||
zipper
|
||||
end
|
||||
|
||||
defp nth_right(zipper, n) do
|
||||
zipper
|
||||
|> Zipper.right()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
nth_right(zipper, n - 1)
|
||||
end
|
||||
end
|
||||
|
||||
def find_list_item_index(zipper, pred) do
|
||||
# go into first list item
|
||||
zipper
|
||||
|> maybe_enter_block()
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
find_index_right(zipper, pred, 0)
|
||||
end
|
||||
end
|
||||
|
||||
def find_list_item(zipper, pred) do
|
||||
# go into first list item
|
||||
zipper
|
||||
|> maybe_enter_block()
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
find_right(zipper, pred)
|
||||
end
|
||||
end
|
||||
|
||||
def is_tuple?(item) do
|
||||
item
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|> case do
|
||||
{:{}, _, _} -> true
|
||||
{_, _} -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def tuple_elem(item, elem) do
|
||||
item
|
||||
|> maybe_enter_block()
|
||||
|> Zipper.down()
|
||||
|> go_right_n_times(elem)
|
||||
|> maybe_enter_block()
|
||||
end
|
||||
|
||||
defp go_right_n_times(zipper, 0), do: maybe_enter_block(zipper)
|
||||
|
||||
defp go_right_n_times(zipper, n) do
|
||||
zipper
|
||||
|> Zipper.right()
|
||||
|> case do
|
||||
nil -> nil
|
||||
zipper -> go_right_n_times(zipper, n - 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp find_index_right(zipper, pred, index) do
|
||||
if pred.(maybe_enter_block(zipper)) do
|
||||
index
|
||||
else
|
||||
case Zipper.right(zipper) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> find_index_right(pred, index + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp find_right(zipper, pred) do
|
||||
if pred.(maybe_enter_block(zipper)) do
|
||||
zipper
|
||||
else
|
||||
case Zipper.right(zipper) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> find_right(pred)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def keywordify([], value) do
|
||||
value
|
||||
end
|
||||
|
||||
def keywordify([key | rest], value) do
|
||||
[{key, keywordify(rest, value)}]
|
||||
end
|
||||
end
|
99
lib/config.ex
Normal file
99
lib/config.ex
Normal file
|
@ -0,0 +1,99 @@
|
|||
defmodule Igniter.Config do
|
||||
require Igniter.Common
|
||||
alias Igniter.Common
|
||||
alias Sourceror.Zipper
|
||||
|
||||
def configure(igniter, file_path, app_name, config_path, value, updater \\ nil) do
|
||||
file_path = Path.join("config", file_path)
|
||||
config_path = List.wrap(config_path)
|
||||
value = Macro.escape(value)
|
||||
updater = updater || fn zipper -> Zipper.replace(zipper, value) end
|
||||
|
||||
igniter
|
||||
|> Igniter.include_or_create_elixir_file(file_path, "import Config\n")
|
||||
|> Igniter.update_file(file_path, fn source ->
|
||||
quoted = Rewrite.Source.get(source, :quoted)
|
||||
zipper = Zipper.zip(quoted)
|
||||
|
||||
case try_update_three_arg(zipper, config_path, app_name, updater) do
|
||||
{:ok, zipper} ->
|
||||
Rewrite.Source.update(source, :configure, :quoted, Zipper.root(zipper))
|
||||
|
||||
:error ->
|
||||
case try_update_two_arg(zipper, config_path, app_name, value, updater) do
|
||||
{:ok, zipper} ->
|
||||
Rewrite.Source.update(source, :configure, :quoted, Zipper.root(zipper))
|
||||
|
||||
:error ->
|
||||
# add new code here
|
||||
config =
|
||||
if Enum.count(config_path) == 1 do
|
||||
quote do
|
||||
config unquote(app_name), unquote(Enum.at(config_path, 0)), unquote(value)
|
||||
end
|
||||
else
|
||||
{:config, [], [app_name, Igniter.Common.keywordify(config_path, value)]}
|
||||
end
|
||||
|
||||
code =
|
||||
zipper
|
||||
|> Igniter.Common.add_code(config)
|
||||
|> Zipper.root()
|
||||
|
||||
Rewrite.Source.update(
|
||||
source,
|
||||
:configure,
|
||||
:quoted,
|
||||
code
|
||||
)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp try_update_three_arg(zipper, config_path, app_name, updater) do
|
||||
if Enum.count(config_path) == 1 do
|
||||
config_item = Enum.at(config_path, 0)
|
||||
|
||||
case Common.find_function_call_in_current_scope(zipper, :config, 3, fn function_call ->
|
||||
Common.argument_matches_pattern?(function_call, 0, ^app_name)
|
||||
Common.argument_matches_pattern?(function_call, 1, ^config_item)
|
||||
end) do
|
||||
nil ->
|
||||
:error
|
||||
|
||||
zipper ->
|
||||
case Common.update_nth_argument(zipper, 2, updater) do
|
||||
nil ->
|
||||
:error
|
||||
|
||||
zipper ->
|
||||
{:ok, zipper}
|
||||
end
|
||||
end
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp try_update_two_arg(zipper, config_path, app_name, value, updater) do
|
||||
case Common.find_function_call_in_current_scope(zipper, :config, 2, fn function_call ->
|
||||
Common.argument_matches_pattern?(function_call, 0, ^app_name)
|
||||
end) do
|
||||
nil ->
|
||||
:error
|
||||
|
||||
zipper ->
|
||||
Common.update_nth_argument(zipper, 1, fn zipper ->
|
||||
Igniter.Common.put_in_keyword(zipper, config_path, value, updater)
|
||||
end)
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zipper ->
|
||||
{:ok, zipper}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
133
lib/deps.ex
Normal file
133
lib/deps.ex
Normal file
|
@ -0,0 +1,133 @@
|
|||
defmodule Igniter.Deps do
|
||||
require Igniter.Common
|
||||
alias Sourceror.Zipper
|
||||
alias Igniter.Common
|
||||
|
||||
def get_dependency_declaration(igniter, name) do
|
||||
zipper =
|
||||
igniter
|
||||
|> Igniter.include_existing_elixir_file("mix.exs")
|
||||
|> Map.get(:rewrite)
|
||||
|> Rewrite.source!("mix.exs")
|
||||
|> Rewrite.Source.get(:quoted)
|
||||
|> Zipper.zip()
|
||||
|
||||
with {:ok, zipper} <- Common.move_to_module_using(zipper, Mix.Project),
|
||||
{:ok, zipper} <- Common.move_to_defp(zipper, :deps, 0),
|
||||
true <- Common.node_matches_pattern?(zipper, value when is_list(value)),
|
||||
current_declaration when not is_nil(current_declaration) <-
|
||||
Common.find_list_item(zipper, fn item ->
|
||||
if Common.is_tuple?(item) do
|
||||
first_elem = Common.tuple_elem(item, 0)
|
||||
first_elem && Common.node_matches_pattern?(first_elem, ^name)
|
||||
end
|
||||
end) do
|
||||
current_declaration
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.node()
|
||||
|> Macro.to_string()
|
||||
else
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def add_dependency(igniter, name, version) do
|
||||
case get_dependency_declaration(igniter, name) do
|
||||
nil ->
|
||||
do_add_dependency(igniter, name, version)
|
||||
|
||||
current ->
|
||||
desired = "`{#{inspect(name)}, #{inspect(version)}}`"
|
||||
current = "`#{current}`"
|
||||
|
||||
if desired == current do
|
||||
igniter
|
||||
else
|
||||
if Mix.shell().yes?("""
|
||||
Dependency #{name} is already in mix.exs. Should we replace it?
|
||||
|
||||
Desired: #{desired}
|
||||
Found: #{current}
|
||||
""") do
|
||||
igniter
|
||||
|> remove_dependency(name)
|
||||
|> do_add_dependency(name, version)
|
||||
else
|
||||
igniter
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_dependency(igniter, name) do
|
||||
igniter
|
||||
|> Igniter.update_file("mix.exs", fn source ->
|
||||
quoted = Rewrite.Source.get(source, :quoted)
|
||||
|
||||
new_quoted =
|
||||
with zipper <- Zipper.zip(quoted),
|
||||
{:ok, zipper} <- Common.move_to_module_using(zipper, Mix.Project),
|
||||
{:ok, zipper} <- Common.move_to_defp(zipper, :deps, 0),
|
||||
true <- Common.node_matches_pattern?(zipper, value when is_list(value)),
|
||||
current_declaration_index when not is_nil(current_declaration_index) <-
|
||||
Common.find_list_item_index(zipper, fn item ->
|
||||
if Common.is_tuple?(item) do
|
||||
first_elem = Common.tuple_elem(item, 0)
|
||||
first_elem && Common.node_matches_pattern?(first_elem, ^name)
|
||||
end
|
||||
end) do
|
||||
zipper
|
||||
|> Common.remove_index(current_declaration_index)
|
||||
|> Zipper.root()
|
||||
else
|
||||
_ ->
|
||||
quoted
|
||||
end
|
||||
|
||||
if new_quoted == quoted do
|
||||
Rewrite.Source.add_issue(
|
||||
source,
|
||||
"Failed to remove dependency #{inspect(name)}"
|
||||
)
|
||||
else
|
||||
Rewrite.Source.update(source, :add_dependency, :quoted, new_quoted)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp do_add_dependency(igniter, name, version) do
|
||||
igniter
|
||||
|> Igniter.Formatter.import_dep(name)
|
||||
|> Igniter.update_file("mix.exs", fn source ->
|
||||
quoted = Rewrite.Source.get(source, :quoted)
|
||||
|
||||
new_quoted =
|
||||
with zipper <- Zipper.zip(quoted),
|
||||
{:ok, zipper} <- Common.move_to_module_using(zipper, Mix.Project),
|
||||
{:ok, zipper} <- Common.move_to_defp(zipper, :deps, 0),
|
||||
true <- Common.node_matches_pattern?(zipper, value when is_list(value)) do
|
||||
quoted =
|
||||
quote do
|
||||
{unquote(name), unquote(version)}
|
||||
end
|
||||
|
||||
zipper
|
||||
|> Common.prepend_to_list(quoted)
|
||||
|> Zipper.root()
|
||||
else
|
||||
_ ->
|
||||
quoted
|
||||
end
|
||||
|
||||
if new_quoted == quoted do
|
||||
Rewrite.Source.add_issue(
|
||||
source,
|
||||
"Failed to add dependency #{inspect({inspect(name), inspect(version)})}"
|
||||
)
|
||||
else
|
||||
Rewrite.Source.update(source, :add_dependency, :quoted, new_quoted)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
84
lib/formatter.ex
Normal file
84
lib/formatter.ex
Normal file
|
@ -0,0 +1,84 @@
|
|||
defmodule Igniter.Formatter do
|
||||
alias Igniter.Common
|
||||
alias Sourceror.Zipper
|
||||
|
||||
@default_formatter """
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
"""
|
||||
|
||||
def import_dep(igniter, dep) do
|
||||
igniter
|
||||
|> Igniter.include_or_create_elixir_file(".formatter.exs", @default_formatter)
|
||||
|> Igniter.update_file(".formatter.exs", fn source ->
|
||||
quoted = Rewrite.Source.get(source, :quoted)
|
||||
zipper = Zipper.zip(quoted)
|
||||
|
||||
new_code =
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
code =
|
||||
quote do
|
||||
[import_deps: [unquote(dep)]]
|
||||
end
|
||||
|
||||
zipper
|
||||
|> Igniter.Common.add_code(code)
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> Zipper.rightmost()
|
||||
|> Common.put_in_keyword([:import_deps], [dep], fn nested_zipper ->
|
||||
Igniter.Common.prepend_new_to_list(
|
||||
nested_zipper,
|
||||
dep
|
||||
)
|
||||
end)
|
||||
end
|
||||
|> Zipper.root()
|
||||
|
||||
Rewrite.Source.update(source, :import_formatter_dep, :quoted, new_code)
|
||||
end)
|
||||
end
|
||||
|
||||
def add_formatter_plugin(igniter, plugin) do
|
||||
igniter
|
||||
|> Igniter.include_or_create_elixir_file(".formatter.exs", @default_formatter)
|
||||
|> Igniter.update_file(".formatter.exs", fn source ->
|
||||
quoted = Rewrite.Source.get(source, :quoted)
|
||||
zipper = Zipper.zip(quoted)
|
||||
|
||||
new_code =
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|> case do
|
||||
nil ->
|
||||
code =
|
||||
quote do
|
||||
[plugins: [unquote(plugin)]]
|
||||
end
|
||||
|
||||
zipper
|
||||
|> Igniter.Common.add_code(code)
|
||||
|
||||
zipper ->
|
||||
zipper
|
||||
|> Zipper.rightmost()
|
||||
|> Common.put_in_keyword([:plugins], [Spark.Formatter], fn nested_zipper ->
|
||||
Igniter.Common.prepend_new_to_list(
|
||||
nested_zipper,
|
||||
Spark.Formatter,
|
||||
&Igniter.Common.equal_modules?/2
|
||||
)
|
||||
end)
|
||||
end
|
||||
|> Zipper.root()
|
||||
|
||||
Rewrite.Source.update(source, :add_formatter_plugin, :quoted, new_code)
|
||||
end)
|
||||
end
|
||||
end
|
99
lib/igniter.ex
Normal file
99
lib/igniter.ex
Normal file
|
@ -0,0 +1,99 @@
|
|||
defmodule Igniter do
|
||||
@moduledoc """
|
||||
Igniter is a library for installing packages and generating code.
|
||||
"""
|
||||
|
||||
defstruct [:rewrite, issues: []]
|
||||
|
||||
def new() do
|
||||
%__MODULE__{rewrite: Rewrite.new()}
|
||||
end
|
||||
|
||||
def add_issue(igniter, issue) do
|
||||
%{igniter | issues: [issue | igniter.issues]}
|
||||
end
|
||||
|
||||
def compose_task(igniter, task_name, argv) do
|
||||
if igniter.issues == [] do
|
||||
task_name
|
||||
|> Mix.Task.get()
|
||||
|> case do
|
||||
nil ->
|
||||
igniter
|
||||
|
||||
task ->
|
||||
Code.ensure_compiled!(task)
|
||||
|
||||
if function_exported?(task, :igniter, 2) do
|
||||
if !task.supports_umbrella?() && Mix.Project.umbrella?() do
|
||||
raise """
|
||||
Cannot run #{inspect(task)} in an umbrella project.
|
||||
"""
|
||||
end
|
||||
|
||||
task.igniter(igniter, argv)
|
||||
else
|
||||
add_issue(igniter, "#{inspect(task)} does not implement `Igniter.igniter/2`")
|
||||
end
|
||||
end
|
||||
else
|
||||
igniter
|
||||
end
|
||||
end
|
||||
|
||||
def update_file(igniter, path, func) do
|
||||
if Rewrite.has_source?(igniter.rewrite, path) do
|
||||
%{igniter | rewrite: Rewrite.update!(igniter.rewrite, path, func)}
|
||||
else
|
||||
igniter
|
||||
|> include_existing_elixir_file(path)
|
||||
|> update_file(path, func)
|
||||
end
|
||||
end
|
||||
|
||||
def include_existing_elixir_file(igniter, path) do
|
||||
if Rewrite.has_source?(igniter.rewrite, path) do
|
||||
igniter
|
||||
else
|
||||
if File.exists?(path) do
|
||||
%{igniter | rewrite: Rewrite.put!(igniter.rewrite, Rewrite.Source.Ex.read!(path))}
|
||||
else
|
||||
add_issue(igniter, "Required #{path} but it did not exist")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def include_or_create_elixir_file(igniter, path, contents \\ "") do
|
||||
if Rewrite.has_source?(igniter.rewrite, path) do
|
||||
igniter
|
||||
else
|
||||
source =
|
||||
try do
|
||||
Rewrite.Source.Ex.read!(path)
|
||||
rescue
|
||||
_ ->
|
||||
""
|
||||
|> Rewrite.Source.Ex.from_string(path)
|
||||
|> Rewrite.Source.update(:file_creator, :content, contents)
|
||||
end
|
||||
|
||||
%{igniter | rewrite: Rewrite.put!(igniter.rewrite, source)}
|
||||
end
|
||||
end
|
||||
|
||||
def create_new_elixir_file(igniter, path, contents \\ "") do
|
||||
source =
|
||||
try do
|
||||
path
|
||||
|> Rewrite.Source.Ex.read!()
|
||||
|> Rewrite.Source.add_issue("File already exists")
|
||||
rescue
|
||||
_ ->
|
||||
""
|
||||
|> Rewrite.Source.Ex.from_string(path)
|
||||
|> Rewrite.Source.update(:file_creator, :content, contents)
|
||||
end
|
||||
|
||||
%{igniter | rewrite: Rewrite.put!(igniter.rewrite, source)}
|
||||
end
|
||||
end
|
29
lib/mix/task.ex
Normal file
29
lib/mix/task.ex
Normal file
|
@ -0,0 +1,29 @@
|
|||
defmodule Igniter.Mix.Task do
|
||||
@callback supports_umbrella?() :: boolean()
|
||||
@callback igniter(igniter :: Igniter.t(), argv :: list(String.t())) :: Igniter.t()
|
||||
|
||||
defmacro __using__(_opts) do
|
||||
quote do
|
||||
use Mix.Task
|
||||
@behaviour Igniter.Mix.Task
|
||||
|
||||
def run(argv) do
|
||||
if !supports_umbrella?() && Mix.Project.umbrella?() do
|
||||
raise """
|
||||
Cannot run #{inspect(__MODULE__)} in an umbrella project.
|
||||
"""
|
||||
end
|
||||
|
||||
Application.ensure_all_started([:rewrite])
|
||||
|
||||
Igniter.new()
|
||||
|> igniter(argv)
|
||||
|> Igniter.Tasks.do_or_dry_run(argv)
|
||||
end
|
||||
|
||||
def supports_umbrella?, do: false
|
||||
|
||||
defoverridable supports_umbrella?: 0
|
||||
end
|
||||
end
|
||||
end
|
18
lib/mix/tasks/igniter.install.ex
Normal file
18
lib/mix/tasks/igniter.install.ex
Normal file
|
@ -0,0 +1,18 @@
|
|||
defmodule Mix.Tasks.Igniter.Install do
|
||||
use Mix.Task
|
||||
|
||||
@impl true
|
||||
def run([install | argv]) do
|
||||
Application.ensure_all_started([:rewrite])
|
||||
|
||||
if String.contains?(install, "/") do
|
||||
raise "installation from github not supported yet"
|
||||
else
|
||||
Mix.Task.run("igniter.install_from_hex", [install | argv])
|
||||
end
|
||||
end
|
||||
|
||||
def run([]) do
|
||||
raise "must provide a package to install!"
|
||||
end
|
||||
end
|
9
lib/mix/tasks/igniter.install.spark.ex
Normal file
9
lib/mix/tasks/igniter.install.spark.ex
Normal file
|
@ -0,0 +1,9 @@
|
|||
defmodule Mix.Tasks.Igniter.Install.Spark do
|
||||
use Igniter.Mix.Task
|
||||
|
||||
def igniter(igniter, _argv) do
|
||||
igniter
|
||||
|> Igniter.Formatter.add_formatter_plugin(Spark.Formatter)
|
||||
|> Igniter.Config.configure("config.exs", :spark, [:formatter, :remove_parens?], true, & &1)
|
||||
end
|
||||
end
|
98
lib/mix/tasks/igniter.install_from_hex.ex
Normal file
98
lib/mix/tasks/igniter.install_from_hex.ex
Normal file
|
@ -0,0 +1,98 @@
|
|||
defmodule Mix.Tasks.Igniter.InstallFromHex do
|
||||
use Mix.Task
|
||||
|
||||
@impl true
|
||||
def run([install | argv]) do
|
||||
install = String.to_atom(install)
|
||||
Application.ensure_all_started(:req)
|
||||
|
||||
case Req.get!("https://hex.pm/api/packages/#{install}").body do
|
||||
%{
|
||||
"releases" => [
|
||||
%{"version" => version}
|
||||
| _
|
||||
]
|
||||
} ->
|
||||
requirement =
|
||||
version
|
||||
|> Version.parse!()
|
||||
|> case do
|
||||
%Version{major: 0, minor: minor} ->
|
||||
"~> 0.#{minor}"
|
||||
|
||||
%Version{major: major} ->
|
||||
"~> #{major}.0"
|
||||
end
|
||||
|
||||
dependency_add_result =
|
||||
Igniter.new()
|
||||
|> Igniter.Deps.add_dependency(install, requirement)
|
||||
|> Igniter.Tasks.do_or_dry_run(argv,
|
||||
title: "Fetching Dependency",
|
||||
quiet_on_no_changes?: true
|
||||
)
|
||||
|
||||
if dependency_add_result == :issues do
|
||||
raise "Exiting due to issues found while fetching dependency"
|
||||
end
|
||||
|
||||
if dependency_add_result == :dry_run_with_changes do
|
||||
install_dep_now? =
|
||||
Mix.shell().yes?("""
|
||||
Cannot display any further installation changes without installing the `#{install}` dependency.
|
||||
|
||||
Would you like to install the dependency now?
|
||||
|
||||
This will be the only change made, and then any remaining steps will be displayed as a dry-run.
|
||||
""")
|
||||
|
||||
if install_dep_now? do
|
||||
Igniter.new()
|
||||
|> Igniter.Deps.add_dependency(install, requirement)
|
||||
|> Igniter.Tasks.do_or_dry_run(argv -- ["--dry-run"],
|
||||
title: "Fetching Dependency",
|
||||
quiet_on_no_changes?: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
case System.cmd("mix", ["deps.get"]) do
|
||||
{_, 0} ->
|
||||
:ok
|
||||
|
||||
{output, exit} ->
|
||||
Mix.shell().info("""
|
||||
mix deps.get returned exited with code: `#{exit}`
|
||||
|
||||
#{output}
|
||||
""")
|
||||
end
|
||||
|
||||
Mix.Task.load_all()
|
||||
|> Enum.find(fn module ->
|
||||
Mix.Task.task_name(module) == "igniter.install.#{install}"
|
||||
end)
|
||||
|> case do
|
||||
nil ->
|
||||
if dependency_add_result in [:dry_run_with_no_changes, :no_changes] do
|
||||
Mix.shell().info("Igniter: #{install} already installed")
|
||||
else
|
||||
if dependency_add_result == :changes_aborted do
|
||||
Mix.shell().info("Igniter: #{install} installation aborted")
|
||||
else
|
||||
Mix.shell().info("Igniter: #{install} installation complete")
|
||||
end
|
||||
end
|
||||
|
||||
_task ->
|
||||
Mix.shell().info("Igniter: Installing #{install}...")
|
||||
Mix.Task.run("igniter.install.#{install}", argv)
|
||||
end
|
||||
|
||||
_ ->
|
||||
raise "No published versions of #{install}"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
12
lib/module.ex
Normal file
12
lib/module.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule Igniter.Module do
|
||||
def module_name(suffix) do
|
||||
Module.concat(module_name_prefix(), suffix)
|
||||
end
|
||||
|
||||
def module_name_prefix() do
|
||||
Mix.Project.get!()
|
||||
|> Module.split()
|
||||
|> :lists.droplast()
|
||||
|> Module.concat()
|
||||
end
|
||||
end
|
117
lib/tasks.ex
Normal file
117
lib/tasks.ex
Normal file
|
@ -0,0 +1,117 @@
|
|||
defmodule Igniter.Tasks do
|
||||
def app_name do
|
||||
Mix.Project.config()[:app]
|
||||
end
|
||||
|
||||
def do_or_dry_run(igniter, argv, opts \\ []) do
|
||||
title = opts[:title] || "Igniter"
|
||||
|
||||
sources =
|
||||
igniter.rewrite
|
||||
|> Rewrite.sources()
|
||||
|
||||
issues =
|
||||
Enum.flat_map(sources, fn source ->
|
||||
changed_issues =
|
||||
if Rewrite.Source.file_changed?(source) do
|
||||
["File has been changed since it was originally read."]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
issues = changed_issues ++ Rewrite.Source.issues(source)
|
||||
|
||||
case issues do
|
||||
[] -> []
|
||||
issues -> [{source, issues}]
|
||||
end
|
||||
end)
|
||||
|
||||
case issues do
|
||||
[_ | _] ->
|
||||
explain_issues(issues)
|
||||
:issues
|
||||
|
||||
[] ->
|
||||
if igniter.issues == [] do
|
||||
result_of_dry_run =
|
||||
sources
|
||||
|> Enum.filter(fn source ->
|
||||
Rewrite.Source.updated?(source)
|
||||
end)
|
||||
|> case do
|
||||
[] ->
|
||||
unless opts[:quiet_on_no_changes?] do
|
||||
IO.puts("\n#{title}: No proposed changes!\n")
|
||||
end
|
||||
|
||||
:dry_run_with_no_changes
|
||||
|
||||
sources ->
|
||||
IO.puts("\n#{title}: Proposed changes:\n")
|
||||
|
||||
Enum.each(sources, fn source ->
|
||||
IO.puts("""
|
||||
#{Rewrite.Source.get(source, :path)}
|
||||
|
||||
#{Rewrite.Source.diff(source)}
|
||||
""")
|
||||
end)
|
||||
|
||||
:dry_run_with_changes
|
||||
end
|
||||
|
||||
if "--dry-run" in argv || result_of_dry_run == :dry_run_with_no_changes do
|
||||
result_of_dry_run
|
||||
else
|
||||
if "--yes" in argv || Mix.shell().yes?("Proceed with changes?") do
|
||||
sources
|
||||
|> Enum.any?(fn source ->
|
||||
Rewrite.Source.updated?(source)
|
||||
end)
|
||||
|> if do
|
||||
igniter.rewrite
|
||||
|> Rewrite.write_all()
|
||||
|
||||
:changes_made
|
||||
else
|
||||
:no_changes
|
||||
end
|
||||
else
|
||||
:changes_aborted
|
||||
end
|
||||
end
|
||||
else
|
||||
IO.puts("Issues during code generation")
|
||||
|
||||
igniter.issues
|
||||
|> Enum.map_join("\n", fn error ->
|
||||
if is_binary(error) do
|
||||
"* #{error}"
|
||||
else
|
||||
"* #{Exception.format(:error, error)}"
|
||||
end
|
||||
end)
|
||||
|> IO.puts()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp explain_issues(issues) do
|
||||
IO.puts("Igniter: Issues found in proposed changes:\n")
|
||||
|
||||
Enum.each(issues, fn {source, issues} ->
|
||||
IO.puts("Issues with #{Rewrite.Source.get(source, :path)}")
|
||||
|
||||
issues
|
||||
|> Enum.map_join("\n", fn error ->
|
||||
if is_binary(error) do
|
||||
"* #{error}"
|
||||
else
|
||||
"* #{Exception.format(:error, error)}"
|
||||
end
|
||||
end)
|
||||
|> IO.puts()
|
||||
end)
|
||||
end
|
||||
end
|
29
mix.exs
Normal file
29
mix.exs
Normal file
|
@ -0,0 +1,29 @@
|
|||
defmodule Igniter.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :igniter,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.16",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:spark, "~> 2.0"},
|
||||
{:rewrite, "~> 0.9"},
|
||||
{:req, "~> 0.4"}
|
||||
]
|
||||
end
|
||||
end
|
27
mix.lock
Normal file
27
mix.lock
Normal file
|
@ -0,0 +1,27 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "3.0.7", "6c37e092f53b1b21eb89596f600a652b2a601f84378f44fd5dd1cdec72eb1cc2", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9288ddb50fe727096c6f63fd82c631de2505dcd29bdfa50b5dc13c865f0bf434"},
|
||||
"castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
|
||||
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
"ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
|
||||
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
|
||||
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
|
||||
"glob_ex": {:hex, :glob_ex, "0.1.7", "eae6b6377147fb712ac45b360e6dbba00346689a87f996672fe07e97d70597b1", [:mix], [], "hexpm", "decc1c21c0c73df3c9c994412716345c1692477b9470e337f628a7e08da0da6a"},
|
||||
"hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"},
|
||||
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
||||
"libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"},
|
||||
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
||||
"mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||
"nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||
"reactor": {:hex, :reactor, "0.8.4", "344d02ba4a0010763851f4e4aa0ff190ebe7e392e3c27c6cd143dde077b986e7", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49c1fd3c786603cec8140ce941c41c7ea72cc4411860ccdee9876c4ca2204f81"},
|
||||
"req": {:hex, :req, "0.4.14", "103de133a076a31044e5458e0f850d5681eef23dfabf3ea34af63212e3b902e2", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0 or ~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2ddd3d33f9ab714ced8d3c15fd03db40c14dbf129003c4a3eb80fac2cc0b1b08"},
|
||||
"rewrite": {:hex, :rewrite, "0.10.1", "238073297d122dad6b5501d761cb3bc0ce5bb4ab86e34c826c395f5f44b2f562", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "91f8d6fe363033e8ff60097bb5e0b76867667df0b4d67e79c2850444c02d8b19"},
|
||||
"sourceror": {:hex, :sourceror, "1.2.1", "b415255ad8bd05f0e859bb3d7ea617f6c2a4a405f2a534a231f229bd99b89f8b", [:mix], [], "hexpm", "e4d97087e67584a7585b5fe3d5a71bf8e7332f795dd1a44983d750003d5e750c"},
|
||||
"spark": {:hex, :spark, "2.1.22", "a36400eede64c51af578de5fdb5a5aaa3e0811da44bcbe7545fce059bd2a990b", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f764611d0b15ac132e72b2326539acc11fc4e63baa3e429f541bca292b5f7064"},
|
||||
"splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"},
|
||||
"stream_data": {:hex, :stream_data, "1.0.0", "c1380747a4650902732696861d5cb66ad3cb1cc93f31c2c8498bf87cddbabe2d", [:mix], [], "hexpm", "acd53e27c66c617d466f42ec77a7f59e5751f6051583c621ccdb055b9690435d"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||
}
|
8
test/igniter_test.exs
Normal file
8
test/igniter_test.exs
Normal file
|
@ -0,0 +1,8 @@
|
|||
defmodule IgniterTest do
|
||||
use ExUnit.Case
|
||||
doctest Igniter
|
||||
|
||||
test "greets the world" do
|
||||
assert Igniter.hello() == :world
|
||||
end
|
||||
end
|
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
|
@ -0,0 +1 @@
|
|||
ExUnit.start()
|
Loading…
Reference in a new issue