mirror of
https://github.com/ash-project/igniter.git
synced 2024-09-17 03:52:51 +12:00
improvement: add CI/build and get it passing locally
This commit is contained in:
parent
281ec0f656
commit
260d56532b
30 changed files with 755 additions and 261 deletions
21
.check.exs
Normal file
21
.check.exs
Normal file
|
@ -0,0 +1,21 @@
|
|||
[
|
||||
## all available options with default values (see `mix check` docs for description)
|
||||
# parallel: true,
|
||||
# skipped: true,
|
||||
|
||||
## list of tools (see `mix check` docs for defaults)
|
||||
tools: [
|
||||
## curated tools may be disabled (e.g. the check for compilation warnings)
|
||||
# {:compiler, false},
|
||||
|
||||
## ...or adjusted (e.g. use one-line formatter for more compact credo output)
|
||||
# {:credo, "mix credo --format oneline"},
|
||||
|
||||
{:doctor, false}
|
||||
|
||||
## custom new tools may be added (mix tasks or arbitrary commands)
|
||||
# {:my_mix_task, command: "mix release", env: %{"MIX_ENV" => "prod"}},
|
||||
# {:my_arbitrary_tool, command: "npm test", cd: "assets"},
|
||||
# {:my_arbitrary_script, command: ["my_script", "argument with spaces"], cd: "scripts"}
|
||||
]
|
||||
]
|
184
.credo.exs
Normal file
184
.credo.exs
Normal file
|
@ -0,0 +1,184 @@
|
|||
# This file contains the configuration for Credo and you are probably reading
|
||||
# this after creating it with `mix credo.gen.config`.
|
||||
#
|
||||
# If you find anything wrong or unclear in this file, please report an
|
||||
# issue on GitHub: https://github.com/rrrene/credo/issues
|
||||
#
|
||||
%{
|
||||
#
|
||||
# You can have as many configs as you like in the `configs:` field.
|
||||
configs: [
|
||||
%{
|
||||
#
|
||||
# Run any config using `mix credo -C <name>`. If no config name is given
|
||||
# "default" is used.
|
||||
#
|
||||
name: "default",
|
||||
#
|
||||
# These are the files included in the analysis:
|
||||
files: %{
|
||||
#
|
||||
# You can give explicit globs or simply directories.
|
||||
# In the latter case `**/*.{ex,exs}` will be used.
|
||||
#
|
||||
included: [
|
||||
"lib/",
|
||||
"src/",
|
||||
"test/",
|
||||
"web/",
|
||||
"apps/*/lib/",
|
||||
"apps/*/src/",
|
||||
"apps/*/test/",
|
||||
"apps/*/web/"
|
||||
],
|
||||
excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"]
|
||||
},
|
||||
#
|
||||
# Load and configure plugins here:
|
||||
#
|
||||
plugins: [],
|
||||
#
|
||||
# If you create your own checks, you must specify the source files for
|
||||
# them here, so they can be loaded by Credo before running the analysis.
|
||||
#
|
||||
requires: [],
|
||||
#
|
||||
# If you want to enforce a style guide and need a more traditional linting
|
||||
# experience, you can change `strict` to `true` below:
|
||||
#
|
||||
strict: false,
|
||||
#
|
||||
# To modify the timeout for parsing files, change this value:
|
||||
#
|
||||
parse_timeout: 5000,
|
||||
#
|
||||
# If you want to use uncolored output by default, you can change `color`
|
||||
# to `false` below:
|
||||
#
|
||||
color: true,
|
||||
#
|
||||
# You can customize the parameters of any check by adding a second element
|
||||
# to the tuple.
|
||||
#
|
||||
# To disable a check put `false` as second element:
|
||||
#
|
||||
# {Credo.Check.Design.DuplicatedCode, false}
|
||||
#
|
||||
checks: [
|
||||
#
|
||||
## Consistency Checks
|
||||
#
|
||||
{Credo.Check.Consistency.ExceptionNames, []},
|
||||
{Credo.Check.Consistency.LineEndings, []},
|
||||
{Credo.Check.Consistency.ParameterPatternMatching, []},
|
||||
{Credo.Check.Consistency.SpaceAroundOperators, []},
|
||||
{Credo.Check.Consistency.SpaceInParentheses, []},
|
||||
{Credo.Check.Consistency.TabsOrSpaces, []},
|
||||
|
||||
#
|
||||
## Design Checks
|
||||
#
|
||||
# You can customize the priority of any check
|
||||
# Priority values are: `low, normal, high, higher`
|
||||
#
|
||||
{Credo.Check.Design.AliasUsage, false},
|
||||
# You can also customize the exit_status of each check.
|
||||
# If you don't want TODO comments to cause `mix credo` to fail, just
|
||||
# set this value to 0 (zero).
|
||||
#
|
||||
{Credo.Check.Design.TagTODO, [exit_status: 2]},
|
||||
{Credo.Check.Design.TagFIXME, []},
|
||||
|
||||
#
|
||||
## Readability Checks
|
||||
#
|
||||
{Credo.Check.Readability.AliasOrder, []},
|
||||
{Credo.Check.Readability.FunctionNames, []},
|
||||
{Credo.Check.Readability.LargeNumbers, []},
|
||||
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
|
||||
{Credo.Check.Readability.ModuleAttributeNames, []},
|
||||
{Credo.Check.Readability.ModuleDoc, []},
|
||||
{Credo.Check.Readability.ModuleNames, []},
|
||||
{Credo.Check.Readability.ParenthesesInCondition, []},
|
||||
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
|
||||
{Credo.Check.Readability.PredicateFunctionNames, []},
|
||||
{Credo.Check.Readability.PreferImplicitTry, []},
|
||||
{Credo.Check.Readability.RedundantBlankLines, []},
|
||||
{Credo.Check.Readability.Semicolons, []},
|
||||
{Credo.Check.Readability.SpaceAfterCommas, []},
|
||||
{Credo.Check.Readability.StringSigils, []},
|
||||
{Credo.Check.Readability.TrailingBlankLine, []},
|
||||
{Credo.Check.Readability.TrailingWhiteSpace, []},
|
||||
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
|
||||
{Credo.Check.Readability.VariableNames, []},
|
||||
|
||||
#
|
||||
## Refactoring Opportunities
|
||||
#
|
||||
{Credo.Check.Refactor.CondStatements, []},
|
||||
{Credo.Check.Refactor.CyclomaticComplexity, false},
|
||||
{Credo.Check.Refactor.FunctionArity, [max_arity: 13]},
|
||||
{Credo.Check.Refactor.LongQuoteBlocks, false},
|
||||
{Credo.Check.Refactor.MapInto, false},
|
||||
{Credo.Check.Refactor.MatchInCondition, []},
|
||||
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
|
||||
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
|
||||
{Credo.Check.Refactor.Nesting, [max_nesting: 10]},
|
||||
{Credo.Check.Refactor.UnlessWithElse, []},
|
||||
{Credo.Check.Refactor.WithClauses, []},
|
||||
|
||||
#
|
||||
## Warnings
|
||||
#
|
||||
{Credo.Check.Warning.BoolOperationOnSameValues, []},
|
||||
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
|
||||
{Credo.Check.Warning.IExPry, []},
|
||||
{Credo.Check.Warning.IoInspect, []},
|
||||
{Credo.Check.Warning.LazyLogging, false},
|
||||
{Credo.Check.Warning.MixEnv, false},
|
||||
{Credo.Check.Warning.OperationOnSameValues, []},
|
||||
{Credo.Check.Warning.OperationWithConstantResult, []},
|
||||
{Credo.Check.Warning.RaiseInsideRescue, []},
|
||||
{Credo.Check.Warning.UnusedEnumOperation, []},
|
||||
{Credo.Check.Warning.UnusedFileOperation, []},
|
||||
{Credo.Check.Warning.UnusedKeywordOperation, []},
|
||||
{Credo.Check.Warning.UnusedListOperation, []},
|
||||
{Credo.Check.Warning.UnusedPathOperation, []},
|
||||
{Credo.Check.Warning.UnusedRegexOperation, []},
|
||||
{Credo.Check.Warning.UnusedStringOperation, []},
|
||||
{Credo.Check.Warning.UnusedTupleOperation, []},
|
||||
{Credo.Check.Warning.UnsafeExec, []},
|
||||
|
||||
#
|
||||
# Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`)
|
||||
|
||||
#
|
||||
# Controversial and experimental checks (opt-in, just replace `false` with `[]`)
|
||||
#
|
||||
{Credo.Check.Readability.StrictModuleLayout, false},
|
||||
{Credo.Check.Consistency.MultiAliasImportRequireUse, false},
|
||||
{Credo.Check.Consistency.UnusedVariableNames, false},
|
||||
{Credo.Check.Design.DuplicatedCode, false},
|
||||
{Credo.Check.Readability.AliasAs, false},
|
||||
{Credo.Check.Readability.MultiAlias, false},
|
||||
{Credo.Check.Readability.Specs, false},
|
||||
{Credo.Check.Readability.SinglePipe, false},
|
||||
{Credo.Check.Readability.WithCustomTaggedTuple, false},
|
||||
{Credo.Check.Refactor.ABCSize, false},
|
||||
{Credo.Check.Refactor.AppendSingleItem, false},
|
||||
{Credo.Check.Refactor.DoubleBooleanNegation, false},
|
||||
{Credo.Check.Refactor.ModuleDependencies, false},
|
||||
{Credo.Check.Refactor.NegatedIsNil, false},
|
||||
{Credo.Check.Refactor.PipeChainStart, false},
|
||||
{Credo.Check.Refactor.VariableRebinding, false},
|
||||
{Credo.Check.Warning.LeakyEnvironment, false},
|
||||
{Credo.Check.Warning.MapGetUnsafePass, false},
|
||||
{Credo.Check.Warning.UnsafeToAtom, false}
|
||||
|
||||
#
|
||||
# Custom checks can be created using `mix credo.gen.check`.
|
||||
#
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,4 +1,9 @@
|
|||
# Used by "mix format"
|
||||
spark_locals_without_parens = [attributes: 1, decrypt_by_default: 1, on_decrypt: 1, vault: 1]
|
||||
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
|
||||
locals_without_parens: spark_locals_without_parens,
|
||||
export: [
|
||||
locals_without_parens: spark_locals_without_parens
|
||||
]
|
||||
]
|
||||
|
|
76
.github/CODE_OF_CONDUCT.md
vendored
Normal file
76
.github/CODE_OF_CONDUCT.md
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at zach@zachdaniel.dev. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
10
.github/CONTRIBUTING.md
vendored
Normal file
10
.github/CONTRIBUTING.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Contributing to Igniter
|
||||
|
||||
- We have a zero tolerance policy for failure to abide by our code of conduct. It is very standard, but please make sure
|
||||
you have read it.
|
||||
- Issues may be opened to propose new ideas, to ask questions, or to file bugs.
|
||||
- Before working on a feature, please talk to the core team/the rest of the community via a proposal. We are
|
||||
building something that needs to be cohesive and well thought out across all use cases. Our top priority is
|
||||
supporting real life use cases like yours, but we have to make sure that we do that in a sustainable way. The
|
||||
best compromise there is to make sure that discussions are centered around the _use case_ for a feature, rather
|
||||
than the propsed feature itself.
|
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ""
|
||||
labels: bug, needs review
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
A minimal set of resource definitions and calls that can reproduce the bug.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
\*\* Runtime
|
||||
|
||||
- Elixir version
|
||||
- Erlang version
|
||||
- OS
|
||||
- Igniter version
|
||||
- any related extension versions
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
36
.github/ISSUE_TEMPLATE/proposal.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/proposal.md
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
name: Proposal
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement, needs review
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Express the feature either with a change to resource syntax, or with a change to the resource interface**
|
||||
|
||||
For example
|
||||
|
||||
```elixir
|
||||
attributes do
|
||||
attribute :foo, :integer, bar: 10 # <- Adding `bar` here would cause <x>
|
||||
end
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```elixir
|
||||
Api.read(:resource, bar: 10) # <- Adding `bar` here would cause <x>
|
||||
```
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
### Contributor checklist
|
||||
|
||||
- [ ] Bug fixes include regression tests
|
||||
- [ ] Features include unit/acceptance tests
|
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: mix
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
14
.github/workflows/elixir.yml
vendored
Normal file
14
.github/workflows/elixir.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: CI
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_call:
|
||||
jobs:
|
||||
ash-ci:
|
||||
uses: ash-project/ash/.github/workflows/ash-ci.yml@main
|
||||
secrets:
|
||||
HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
|
2
.tool-versions
Normal file
2
.tool-versions
Normal file
|
@ -0,0 +1,2 @@
|
|||
erlang 26.0.2
|
||||
elixir 1.16.2
|
1
FUNDING.yml
Normal file
1
FUNDING.yml
Normal file
|
@ -0,0 +1 @@
|
|||
github: zachdaniel
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Zachary Scott Daniel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
17
config/config.exs
Normal file
17
config/config.exs
Normal file
|
@ -0,0 +1,17 @@
|
|||
import Config
|
||||
|
||||
if Mix.env() == :dev do
|
||||
config :git_ops,
|
||||
mix_project: Igniter.MixProject,
|
||||
changelog_file: "CHANGELOG.md",
|
||||
repository_url: "https://github.com/ash-project/igniter",
|
||||
# Instructs the tool to manage your mix version in your `mix.exs` file
|
||||
# See below for more information
|
||||
manage_mix_version?: true,
|
||||
# Instructs the tool to manage the version in your README.md
|
||||
# Pass in `true` to use `"README.md"` or a string to customize
|
||||
manage_readme_version: [
|
||||
"README.md"
|
||||
],
|
||||
version_tag_prefix: "v"
|
||||
end
|
7
lib/application.ex
Normal file
7
lib/application.ex
Normal file
|
@ -0,0 +1,7 @@
|
|||
defmodule Igniter.Application do
|
||||
@moduledoc "Codemods and tools for working with Application modules."
|
||||
|
||||
def app_name do
|
||||
Mix.Project.config()[:app]
|
||||
end
|
||||
end
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Igniter.Args do
|
||||
@moduledoc "Tools for validating and parsing command line arguments to tasks."
|
||||
def validate_nth_present_and_underscored(igniter, argv, n, option, message) do
|
||||
value = Enum.at(argv, n)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Igniter.Common do
|
||||
@doc """
|
||||
Common utilities for working with igniter, primarily with zippers.
|
||||
@moduledoc """
|
||||
Common utilities for working with igniter, primarily with `Sourceror.Zipper`.
|
||||
"""
|
||||
alias Sourceror.Zipper
|
||||
|
||||
|
@ -96,7 +96,7 @@ defmodule Igniter.Common do
|
|||
defp do_put_in_keyword(zipper, [key | rest], value, updater) do
|
||||
if node_matches_pattern?(zipper, value when is_list(value)) do
|
||||
case move_to_list_item(zipper, fn item ->
|
||||
if is_tuple?(item) do
|
||||
if tuple?(item) do
|
||||
first_elem = tuple_elem(item, 0)
|
||||
first_elem && node_matches_pattern?(first_elem, ^key)
|
||||
end
|
||||
|
@ -127,7 +127,7 @@ defmodule Igniter.Common do
|
|||
def set_keyword_key(zipper, key, value, updater) do
|
||||
if node_matches_pattern?(zipper, value when is_list(value)) do
|
||||
case move_to_list_item(zipper, fn item ->
|
||||
if is_tuple?(item) do
|
||||
if tuple?(item) do
|
||||
first_elem = tuple_elem(item, 0)
|
||||
first_elem && node_matches_pattern?(first_elem, ^key)
|
||||
end
|
||||
|
@ -176,7 +176,7 @@ defmodule Igniter.Common do
|
|||
zipper
|
||||
|> Zipper.down()
|
||||
|> move_to_list_item(fn item ->
|
||||
if is_tuple?(item) do
|
||||
if tuple?(item) do
|
||||
first_elem = tuple_elem(item, 0)
|
||||
first_elem && node_matches_pattern?(first_elem, ^key)
|
||||
end
|
||||
|
@ -222,7 +222,7 @@ defmodule Igniter.Common do
|
|||
zipper
|
||||
|> Zipper.down()
|
||||
|> move_to_list_item(fn item ->
|
||||
if is_tuple?(item) do
|
||||
if tuple?(item) do
|
||||
first_elem = tuple_elem(item, 0)
|
||||
first_elem && node_matches_pattern?(first_elem, ^key)
|
||||
end
|
||||
|
@ -284,11 +284,11 @@ defmodule Igniter.Common do
|
|||
zipper
|
||||
|> maybe_move_to_block()
|
||||
|> move_right(fn zipper ->
|
||||
is_function_call(zipper, name, arity) && predicate.(zipper)
|
||||
function_call?(zipper, name, arity) && predicate.(zipper)
|
||||
end)
|
||||
end
|
||||
|
||||
def is_function_call(zipper, name, arity) do
|
||||
def function_call?(zipper, name, arity) do
|
||||
zipper
|
||||
|> maybe_move_to_block()
|
||||
|> Zipper.subtree()
|
||||
|
@ -312,7 +312,7 @@ defmodule Igniter.Common do
|
|||
end
|
||||
|
||||
def update_nth_argument(zipper, index, func) do
|
||||
if is_pipeline?(zipper) do
|
||||
if pipeline?(zipper) do
|
||||
if index == 0 do
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|
@ -373,7 +373,7 @@ defmodule Igniter.Common do
|
|||
end
|
||||
|
||||
def argument_matches_predicate?(zipper, index, func) do
|
||||
if is_pipeline?(zipper) do
|
||||
if pipeline?(zipper) do
|
||||
if index == 0 do
|
||||
zipper
|
||||
|> Zipper.down()
|
||||
|
@ -438,7 +438,7 @@ defmodule Igniter.Common do
|
|||
end
|
||||
end
|
||||
|
||||
def is_pipeline?(zipper) do
|
||||
def pipeline?(zipper) do
|
||||
zipper
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|
@ -448,6 +448,7 @@ defmodule Igniter.Common do
|
|||
end
|
||||
end
|
||||
|
||||
# sobelow_skip ["DOS.StringToAtom"]
|
||||
def move_to_module_using(zipper, module) do
|
||||
split_module =
|
||||
module
|
||||
|
@ -660,7 +661,7 @@ defmodule Igniter.Common do
|
|||
end
|
||||
end
|
||||
|
||||
def is_tuple?(item) do
|
||||
def tuple?(item) do
|
||||
item
|
||||
|> Zipper.subtree()
|
||||
|> Zipper.root()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Igniter.Config do
|
||||
@moduledoc "Codemods and utilities for configuring Elixir applications."
|
||||
require Igniter.Common
|
||||
alias Igniter.Common
|
||||
alias Sourceror.Zipper
|
||||
|
|
63
lib/deps.ex
63
lib/deps.ex
|
@ -1,36 +1,8 @@
|
|||
defmodule Igniter.Deps do
|
||||
@moduledoc "Codemods and utilities for managing dependencies declared in mix.exs"
|
||||
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)),
|
||||
{:ok, current_declaration} <-
|
||||
Common.move_to_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()
|
||||
|> Sourceror.to_string()
|
||||
else
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
alias Sourceror.Zipper
|
||||
|
||||
def add_dependency(igniter, name, version) do
|
||||
case get_dependency_declaration(igniter, name) do
|
||||
|
@ -60,6 +32,35 @@ defmodule Igniter.Deps do
|
|||
end
|
||||
end
|
||||
|
||||
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)),
|
||||
{:ok, current_declaration} <-
|
||||
Common.move_to_list_item(zipper, fn item ->
|
||||
if Common.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()
|
||||
|> Sourceror.to_string()
|
||||
else
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp remove_dependency(igniter, name) do
|
||||
igniter
|
||||
|> Igniter.update_file("mix.exs", fn source ->
|
||||
|
@ -72,7 +73,7 @@ defmodule Igniter.Deps do
|
|||
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
|
||||
if Common.tuple?(item) do
|
||||
first_elem = Common.tuple_elem(item, 0)
|
||||
first_elem && Common.node_matches_pattern?(first_elem, ^name)
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Igniter.Formatter do
|
||||
@moduledoc "Codemods and utilities for interacting with `.formatter.exs` files"
|
||||
alias Igniter.Common
|
||||
alias Sourceror.Zipper
|
||||
|
||||
|
|
185
lib/igniter.ex
185
lib/igniter.ex
|
@ -8,10 +8,10 @@ defmodule Igniter do
|
|||
@type t :: %__MODULE__{
|
||||
rewrite: Rewrite.t(),
|
||||
issues: [String.t()],
|
||||
tasks: [{String.t() | list(STring.t())}]
|
||||
tasks: [{String.t() | list(String.t())}]
|
||||
}
|
||||
|
||||
def new() do
|
||||
def new do
|
||||
%__MODULE__{rewrite: Rewrite.new()}
|
||||
end
|
||||
|
||||
|
@ -121,6 +121,185 @@ defmodule Igniter do
|
|||
|> format(path)
|
||||
end
|
||||
|
||||
def do_or_dry_run(igniter, argv, opts \\ []) do
|
||||
igniter = %{igniter | issues: Enum.uniq(igniter.issues)}
|
||||
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 = Enum.uniq(changed_issues ++ Rewrite.Source.issues(source))
|
||||
|
||||
case issues do
|
||||
[] -> []
|
||||
issues -> [{source, issues}]
|
||||
end
|
||||
end)
|
||||
|
||||
case issues do
|
||||
[_ | _] ->
|
||||
explain_issues(issues)
|
||||
:issues
|
||||
|
||||
[] ->
|
||||
case igniter do
|
||||
%{issues: []} ->
|
||||
result_of_dry_run =
|
||||
sources
|
||||
|> Enum.filter(fn source ->
|
||||
Rewrite.Source.updated?(source)
|
||||
end)
|
||||
|> case do
|
||||
[] ->
|
||||
unless opts[:quiet_on_no_changes?] do
|
||||
Mix.shell().info("\n#{title}: No proposed changes!\n")
|
||||
end
|
||||
|
||||
:dry_run_with_no_changes
|
||||
|
||||
sources ->
|
||||
Mix.shell().info("\n#{title}: Proposed changes:\n")
|
||||
|
||||
Enum.each(sources, fn source ->
|
||||
if Rewrite.Source.from?(source, :string) do
|
||||
content_lines =
|
||||
source
|
||||
|> Rewrite.Source.get(:content)
|
||||
|> String.split("\n")
|
||||
|> Enum.with_index()
|
||||
|
||||
space_padding =
|
||||
content_lines
|
||||
|> Enum.map(&elem(&1, 1))
|
||||
|> Enum.max()
|
||||
|> to_string()
|
||||
|> String.length()
|
||||
|
||||
diffish_looking_text =
|
||||
Enum.map_join(content_lines, "\n", fn {line, line_number_minus_one} ->
|
||||
line_number = line_number_minus_one + 1
|
||||
|
||||
"#{String.pad_trailing(to_string(line_number), space_padding)} #{IO.ANSI.yellow()}| #{IO.ANSI.green()}#{line}#{IO.ANSI.reset()}"
|
||||
end)
|
||||
|
||||
Mix.shell().info("""
|
||||
Create: #{Rewrite.Source.get(source, :path)}
|
||||
|
||||
#{diffish_looking_text}
|
||||
""")
|
||||
else
|
||||
Mix.shell().info("""
|
||||
Update: #{Rewrite.Source.get(source, :path)}
|
||||
|
||||
#{Rewrite.Source.diff(source)}
|
||||
""")
|
||||
end
|
||||
end)
|
||||
|
||||
:dry_run_with_changes
|
||||
end
|
||||
|
||||
if igniter.tasks != [] do
|
||||
message =
|
||||
if result_of_dry_run == :dry_run_with_no_changes do
|
||||
"The following tasks will be run"
|
||||
else
|
||||
"The following tasks will be run after the above changes:"
|
||||
end
|
||||
|
||||
Mix.shell().info("""
|
||||
#{message}
|
||||
|
||||
#{Enum.map_join(igniter.tasks, "\n", fn {task, args} -> "* #{IO.ANSI.red()}#{task}#{IO.ANSI.yellow()} #{Enum.join(args, " ")}#{IO.ANSI.reset()}" end)}
|
||||
""")
|
||||
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?(opts[:confirmation_message] || "Proceed with changes?") do
|
||||
sources
|
||||
|> Enum.any?(fn source ->
|
||||
Rewrite.Source.updated?(source)
|
||||
end)
|
||||
|> if do
|
||||
igniter.rewrite
|
||||
|> Rewrite.write_all()
|
||||
|> case do
|
||||
{:ok, _result} ->
|
||||
igniter.tasks
|
||||
|> Enum.each(fn {task, args} ->
|
||||
Mix.Task.run(task, args)
|
||||
end)
|
||||
|
||||
:changes_made
|
||||
|
||||
{:error, error, rewrite} ->
|
||||
igniter
|
||||
|> Map.put(:rewrite, rewrite)
|
||||
|> Igniter.add_issue(error)
|
||||
|> igniter_issues()
|
||||
|
||||
:issues
|
||||
end
|
||||
else
|
||||
:no_changes
|
||||
end
|
||||
else
|
||||
:changes_aborted
|
||||
end
|
||||
end
|
||||
|
||||
igniter ->
|
||||
igniter_issues(igniter)
|
||||
:issues
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp igniter_issues(igniter) do
|
||||
Mix.shell().info("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)
|
||||
|> Mix.shell().info()
|
||||
end
|
||||
|
||||
defp explain_issues(issues) do
|
||||
Mix.shell().info("Igniter: Issues found in proposed changes:\n")
|
||||
|
||||
Enum.each(issues, fn {source, issues} ->
|
||||
Mix.shell().info("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)
|
||||
|> Mix.shell().info()
|
||||
end)
|
||||
end
|
||||
|
||||
defp format(igniter, adding_path \\ nil) do
|
||||
if adding_path && Path.basename(adding_path) == ".formatter.exs" do
|
||||
format(igniter)
|
||||
|
@ -179,6 +358,7 @@ defmodule Igniter do
|
|||
end
|
||||
end
|
||||
|
||||
# sobelow_skip ["RCE.CodeModule"]
|
||||
defp find_formatter_exs_file_options(path, formatter_exs_files) do
|
||||
case Map.fetch(formatter_exs_files, path) do
|
||||
{:ok, source} ->
|
||||
|
@ -249,6 +429,7 @@ defmodule Igniter do
|
|||
nil
|
||||
end
|
||||
|
||||
# sobelow_skip ["RCE.CodeModule"]
|
||||
defp eval_file_with_keyword_list(path) do
|
||||
{opts, _} = Code.eval_file(path)
|
||||
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
defmodule Igniter.Install do
|
||||
@moduledoc false
|
||||
@option_schema [
|
||||
switches: [
|
||||
no_network: :boolean,
|
||||
example: :boolean,
|
||||
dry_run: :boolean
|
||||
dry_run: :boolean,
|
||||
yes: :boolean
|
||||
],
|
||||
aliases: [
|
||||
d: :dry_run,
|
||||
n: :no_network,
|
||||
e: :example
|
||||
e: :example,
|
||||
y: :yes
|
||||
]
|
||||
]
|
||||
|
||||
# only supports hex installation at the moment
|
||||
def install(install, argv) do
|
||||
install_list =
|
||||
install
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.to_atom/1)
|
||||
install_list = install_list(install)
|
||||
|
||||
Application.ensure_all_started(:req)
|
||||
|
||||
|
@ -69,7 +67,7 @@ defmodule Igniter.Install do
|
|||
end
|
||||
|
||||
dependency_add_result =
|
||||
Igniter.Tasks.do_or_dry_run(igniter, argv,
|
||||
Igniter.do_or_dry_run(igniter, argv,
|
||||
title: "Fetching Dependency",
|
||||
quiet_on_no_changes?: true,
|
||||
confirmation_message: confirmation_message
|
||||
|
@ -89,7 +87,7 @@ defmodule Igniter.Install do
|
|||
""")
|
||||
|
||||
if install_dep_now? do
|
||||
Igniter.Tasks.do_or_dry_run(igniter, (argv ++ ["--yes"]) -- ["--dry-run"],
|
||||
Igniter.do_or_dry_run(igniter, (argv ++ ["--yes"]) -- ["--dry-run"],
|
||||
title: "Fetching Dependency",
|
||||
quiet_on_no_changes?: true
|
||||
)
|
||||
|
@ -110,7 +108,7 @@ defmodule Igniter.Install do
|
|||
end
|
||||
|
||||
all_tasks =
|
||||
Enum.filter(Mix.Task.load_all(), &Spark.implements_behaviour?(&1, Igniter.Mix.Task))
|
||||
Enum.filter(Mix.Task.load_all(), &implements_behaviour?(&1, Igniter.Mix.Task))
|
||||
|
||||
install_list
|
||||
|> Enum.flat_map(fn install ->
|
||||
|
@ -123,8 +121,44 @@ defmodule Igniter.Install do
|
|||
|> Enum.reduce(Igniter.new(), fn task, igniter ->
|
||||
Igniter.compose_task(igniter, task, argv)
|
||||
end)
|
||||
|> Igniter.Tasks.do_or_dry_run(argv)
|
||||
|> Igniter.do_or_dry_run(argv)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp implements_behaviour?(module, behaviour) do
|
||||
:attributes
|
||||
|> module.module_info()
|
||||
|> Enum.any?(fn
|
||||
{:behaviour, ^behaviour} ->
|
||||
true
|
||||
|
||||
# optimizations, probably extremely minor but this is in a tight loop in some places
|
||||
{:behaviour, [^behaviour | _]} ->
|
||||
true
|
||||
|
||||
{:behaviour, [_, ^behaviour | _]} ->
|
||||
true
|
||||
|
||||
{:behaviour, [_, _, ^behaviour | _]} ->
|
||||
true
|
||||
|
||||
# never seen a module with three behaviours in real life, let alone four.
|
||||
{:behaviour, behaviours} when is_list(behaviours) ->
|
||||
module in behaviours
|
||||
|
||||
_ ->
|
||||
false
|
||||
end)
|
||||
rescue
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
|
||||
# sobelow_skip ["DOS.StringToAtom"]
|
||||
defp install_list(install) do
|
||||
install
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.to_atom/1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Igniter.Mix.Task do
|
||||
@moduledoc "A behaviour for implementing a Mix task that is enriched to be composable with other Igniter tasks."
|
||||
@callback supports_umbrella?() :: boolean()
|
||||
@callback igniter(igniter :: Igniter.t(), argv :: list(String.t())) :: Igniter.t()
|
||||
|
||||
|
@ -18,7 +19,7 @@ defmodule Igniter.Mix.Task do
|
|||
|
||||
Igniter.new()
|
||||
|> igniter(argv)
|
||||
|> Igniter.Tasks.do_or_dry_run(argv)
|
||||
|> Igniter.do_or_dry_run(argv)
|
||||
end
|
||||
|
||||
def supports_umbrella?, do: false
|
||||
|
|
|
@ -1,7 +1,21 @@
|
|||
defmodule Mix.Tasks.Igniter.Install do
|
||||
@moduledoc """
|
||||
Install a package or packages, and run any associated installers.
|
||||
|
||||
## Args
|
||||
|
||||
mix igniter.install package1,package2,package3
|
||||
|
||||
## Switches
|
||||
|
||||
* `--dry-run` - `d` - Run the task without making any changes.
|
||||
* `--yes` - `y` - Automatically answer yes to any prompts.
|
||||
* `--example` - `e` - Request that installed packages include initial example code.
|
||||
"""
|
||||
use Mix.Task
|
||||
|
||||
@impl true
|
||||
@shortdoc "Install a package or packages, and run any associated installers."
|
||||
def run([install | argv]) do
|
||||
Application.ensure_all_started([:rewrite])
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
defmodule Mix.Tasks.Spark.Install 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
|
|
@ -1,9 +1,10 @@
|
|||
defmodule Igniter.Module do
|
||||
@moduledoc "Codemods and tools for generating and working with Elixir modules"
|
||||
def module_name(suffix) do
|
||||
Module.concat(module_name_prefix(), suffix)
|
||||
end
|
||||
|
||||
def module_name_prefix() do
|
||||
def module_name_prefix do
|
||||
Mix.Project.get!()
|
||||
|> Module.split()
|
||||
|> :lists.droplast()
|
||||
|
|
180
lib/tasks.ex
180
lib/tasks.ex
|
@ -1,180 +0,0 @@
|
|||
defmodule Igniter.Tasks do
|
||||
def app_name do
|
||||
Mix.Project.config()[:app]
|
||||
end
|
||||
|
||||
def do_or_dry_run(igniter, argv, opts \\ []) do
|
||||
igniter = %{igniter | issues: Enum.uniq(igniter.issues)}
|
||||
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 = Enum.uniq(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
|
||||
Mix.shell().info("\n#{title}: No proposed changes!\n")
|
||||
end
|
||||
|
||||
:dry_run_with_no_changes
|
||||
|
||||
sources ->
|
||||
Mix.shell().info("\n#{title}: Proposed changes:\n")
|
||||
|
||||
Enum.each(sources, fn source ->
|
||||
if Rewrite.Source.from?(source, :string) do
|
||||
content_lines =
|
||||
source
|
||||
|> Rewrite.Source.get(:content)
|
||||
|> String.split("\n")
|
||||
|> Enum.with_index()
|
||||
|
||||
space_padding =
|
||||
content_lines
|
||||
|> Enum.map(&elem(&1, 1))
|
||||
|> Enum.max()
|
||||
|> to_string()
|
||||
|> String.length()
|
||||
|
||||
diffish_looking_text =
|
||||
Enum.map_join(content_lines, "\n", fn {line, line_number_minus_one} ->
|
||||
line_number = line_number_minus_one + 1
|
||||
|
||||
"#{String.pad_trailing(to_string(line_number), space_padding)} #{IO.ANSI.yellow()}| #{IO.ANSI.green()}#{line}#{IO.ANSI.reset()}"
|
||||
end)
|
||||
|
||||
Mix.shell().info("""
|
||||
Create: #{Rewrite.Source.get(source, :path)}
|
||||
|
||||
#{diffish_looking_text}
|
||||
""")
|
||||
else
|
||||
Mix.shell().info("""
|
||||
Update: #{Rewrite.Source.get(source, :path)}
|
||||
|
||||
#{Rewrite.Source.diff(source)}
|
||||
""")
|
||||
end
|
||||
end)
|
||||
|
||||
:dry_run_with_changes
|
||||
end
|
||||
|
||||
if igniter.tasks != [] do
|
||||
message =
|
||||
if result_of_dry_run in [:dry_run_with_no_changes, :no_changes] do
|
||||
"The following tasks will be run"
|
||||
else
|
||||
"The following tasks will be run after the above changes:"
|
||||
end
|
||||
|
||||
Mix.shell().info("""
|
||||
#{message}
|
||||
|
||||
#{Enum.map_join(igniter.tasks, "\n", fn {task, args} -> "* #{IO.ANSI.red()}#{task}#{IO.ANSI.yellow()} #{Enum.join(args, " ")}#{IO.ANSI.reset()}" end)}
|
||||
""")
|
||||
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?(opts[:confirmation_message] || "Proceed with changes?") do
|
||||
sources
|
||||
|> Enum.any?(fn source ->
|
||||
Rewrite.Source.updated?(source)
|
||||
end)
|
||||
|> if do
|
||||
igniter.rewrite
|
||||
|> Rewrite.write_all()
|
||||
|> case do
|
||||
{:ok, _result} ->
|
||||
igniter.tasks
|
||||
|> Enum.each(fn {task, args} ->
|
||||
Mix.Task.run(task, args)
|
||||
end)
|
||||
|
||||
:changes_made
|
||||
|
||||
{:error, error} ->
|
||||
igniter
|
||||
|> Igniter.add_issue(error)
|
||||
|> igniter_issues()
|
||||
|
||||
{:error, error}
|
||||
end
|
||||
else
|
||||
:no_changes
|
||||
end
|
||||
else
|
||||
:changes_aborted
|
||||
end
|
||||
end
|
||||
else
|
||||
igniter_issues(igniter)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp igniter_issues(igniter) do
|
||||
Mix.shell().info("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)
|
||||
|> Mix.shell().info()
|
||||
end
|
||||
|
||||
defp explain_issues(issues) do
|
||||
Mix.shell().info("Igniter: Issues found in proposed changes:\n")
|
||||
|
||||
Enum.each(issues, fn {source, issues} ->
|
||||
Mix.shell().info("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)
|
||||
|> Mix.shell().info()
|
||||
end)
|
||||
end
|
||||
end
|
9
mix.exs
9
mix.exs
|
@ -9,6 +9,7 @@ defmodule Igniter.MixProject do
|
|||
version: @version,
|
||||
elixir: "~> 1.16",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
aliases: aliases(),
|
||||
docs: docs(),
|
||||
deps: deps()
|
||||
]
|
||||
|
@ -52,7 +53,6 @@ defmodule Igniter.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:spark, "~> 2.0"},
|
||||
{:rewrite, "~> 0.9"},
|
||||
{:req, "~> 0.4"},
|
||||
# Dev/Test dependencies
|
||||
|
@ -70,4 +70,11 @@ defmodule Igniter.MixProject do
|
|||
{:doctor, "~> 0.21", only: [:dev, :test]}
|
||||
]
|
||||
end
|
||||
|
||||
defp aliases do
|
||||
[
|
||||
sobelow: "sobelow --skip",
|
||||
credo: "credo --strict"
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
10
mix.lock
10
mix.lock
|
@ -1,19 +1,15 @@
|
|||
%{
|
||||
"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"},
|
||||
"benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"},
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"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"},
|
||||
"credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"},
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
|
||||
"doctor": {:hex, :doctor, "0.21.0", "20ef89355c67778e206225fe74913e96141c4d001cb04efdeba1a2a9704f1ab5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "a227831daa79784eb24cdeedfa403c46a4cb7d0eab0e31232ec654314447e4e0"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
|
||||
"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"},
|
||||
"eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"},
|
||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
|
||||
"ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.33.0", "690562b153153c7e4d455dc21dab86e445f66ceba718defe64b0ef6f0bd83ba0", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "3f69adc28274cb51be37d09b03e4565232862a4b10288a3894587b0131412124"},
|
||||
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
|
||||
|
@ -23,7 +19,6 @@
|
|||
"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"},
|
||||
"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"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
|
||||
|
@ -36,17 +31,12 @@
|
|||
"nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
||||
"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.3", "1c998cceac960c3025a1701158d846dee94bc426d95abefd2b4a2e981835ea1c", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "d3ea3179de167ebda56bf81b7e5c2697256a0719fdcc2c0df65ea8173efe3563"},
|
||||
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
|
||||
"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"},
|
||||
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
|
||||
"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"},
|
||||
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
|
||||
"yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"},
|
||||
}
|
||||
|
|
|
@ -123,3 +123,22 @@ defmodule Igniter.ConfigTest do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Mix.Tasks.LiveViewNative.Install do
|
||||
use Igniter.Mix.Task
|
||||
|
||||
def igniter(igniter, argv) do
|
||||
Igniter.new()
|
||||
|> Igniter.Config.configure("fake.exs", :fake, [:foo], %{"b" => ["c", "d"]}, fn zipper ->
|
||||
Igniter.Common.set_map_key(zipper, "b", ["c", "d"], fn zipper ->
|
||||
zipper
|
||||
|> Igniter.Common.prepend_new_to_list(zipper, "c")
|
||||
|> Igniter.Common.prepend_new_to_list(zipper, "d")
|
||||
end)
|
||||
|> case do
|
||||
{:ok, new_zipper} -> new_zipper
|
||||
_ -> zipper
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue