├── .tool-versions ├── mix.lock.license ├── .tool-versions.license ├── documentation └── dsls │ ├── DSL-AshSlug.md.license │ └── DSL-AshSlug.md ├── test ├── test_helper.exs ├── support │ ├── domain.ex │ └── resource.ex └── ash_slug_test.exs ├── lib ├── ash_slug.ex └── ash_slug │ ├── info.ex │ ├── changes.ex │ └── changes │ └── slugify.ex ├── .github ├── dependabot.yml └── workflows │ └── elixir.yml ├── .formatter.exs ├── .gitignore ├── config └── config.exs ├── .check.exs ├── LICENSES └── MIT.txt ├── CHANGELOG.md ├── README.md ├── mix.exs ├── .credo.exs └── mix.lock /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 26.0.2 2 | elixir 1.18.1 3 | pipx 1.8.0 4 | -------------------------------------------------------------------------------- /mix.lock.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /.tool-versions.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /documentation/dsls/DSL-AshSlug.md.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | ExUnit.start() 6 | -------------------------------------------------------------------------------- /documentation/dsls/DSL-AshSlug.md: -------------------------------------------------------------------------------- 1 | 4 | # AshSlug 5 | 6 | An extension for slugifying attributes on a resource. 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/support/domain.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule AshSlugTest.Domain do 6 | @moduledoc false 7 | use Ash.Domain 8 | 9 | resources do 10 | resource(AshSlugTest.Resource) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/ash_slug.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule AshSlug do 6 | @moduledoc """ 7 | An extension for slugifying attributes on a resource. 8 | """ 9 | use Spark.Dsl.Extension, imports: [AshSlug.Changes] 10 | end 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | --- 6 | updates: 7 | - directory: / 8 | package-ecosystem: mix 9 | schedule: 10 | interval: monthly 11 | versioning-strategy: lockfile-only 12 | version: 2 13 | -------------------------------------------------------------------------------- /lib/ash_slug/info.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule AshSlug.Info do 6 | @moduledoc """ 7 | Introspection functions for the `AshSlug` extension. 8 | """ 9 | use Spark.InfoGenerator, extension: AshSlug 10 | end 11 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | spark_locals_without_parens = [] 6 | 7 | [ 8 | line_length: 120, 9 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], 10 | locals_without_parens: spark_locals_without_parens, 11 | export: [ 12 | locals_without_parens: spark_locals_without_parens 13 | ] 14 | ] 15 | -------------------------------------------------------------------------------- /.github/workflows/elixir.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: CI 6 | on: 7 | push: 8 | tags: 9 | - "v*" 10 | branches: [main] 11 | pull_request: 12 | branches: [main] 13 | workflow_call: 14 | jobs: 15 | ash-ci: 16 | uses: ash-project/ash/.github/workflows/ash-ci.yml@main 17 | with: 18 | reuse: true 19 | secrets: 20 | HEX_API_KEY: ${{ secrets.HEX_API_KEY }} 21 | -------------------------------------------------------------------------------- /lib/ash_slug/changes.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule AshSlug.Changes do 6 | @moduledoc """ 7 | Change functions for the `AshSlug` extension. 8 | """ 9 | 10 | @doc """ 11 | Slugify a string attribute on a changeset. 12 | 13 | ## Options 14 | 15 | #{Spark.Options.docs(AshSlug.Changes.Slugify.opt_schema())} 16 | 17 | ## Examples 18 | 19 | change slugify(:text) 20 | change slugify(:text, into: :text_slug, lowercase?: false, ignore: [".", "!"]) 21 | """ 22 | def slugify(attribute, opts \\ []) do 23 | {AshSlug.Changes.Slugify, Keyword.merge(opts, attribute: attribute)} 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # The directory Mix will write compiled artifacts to. 6 | /_build/ 7 | 8 | # If you run "mix test --cover", coverage assets end up here. 9 | /cover/ 10 | 11 | # The directory Mix downloads your dependencies sources to. 12 | /deps/ 13 | 14 | # Where third-party dependencies like ExDoc output generated docs. 15 | /doc/ 16 | 17 | # Ignore .fetch files in case you like to edit your project deps locally. 18 | /.fetch 19 | 20 | # If the VM crashes, it generates a dump, let's ignore it too. 21 | erl_crash.dump 22 | 23 | # Also ignore archive artifacts (built via "mix archive.build"). 24 | *.ez 25 | 26 | # Ignore package tarball (built via "mix hex.build"). 27 | ash_slug-*.tar 28 | 29 | # Temporary files, for example, from tests. 30 | /tmp/ 31 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | import Config 6 | 7 | config :ash, :disable_async?, true 8 | config :ash, :validate_domain_resource_inclusion?, false 9 | config :ash, :validate_domain_config_inclusion?, false 10 | 11 | if Mix.env() == :dev do 12 | config :git_ops, 13 | mix_project: AshSlug.MixProject, 14 | changelog_file: "CHANGELOG.md", 15 | repository_url: "https://github.com/ash-project/ash_slug", 16 | # Instructs the tool to manage your mix version in your `mix.exs` file 17 | # See below for more information 18 | manage_mix_version?: true, 19 | # Instructs the tool to manage the version in your README.md 20 | # Pass in `true` to use `"README.md"` or a string to customize 21 | manage_readme_version: [ 22 | "README.md" 23 | ], 24 | version_tag_prefix: "v" 25 | end 26 | -------------------------------------------------------------------------------- /.check.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | [ 6 | ## all available options with default values (see `mix check` docs for description) 7 | # parallel: true, 8 | # skipped: true, 9 | 10 | ## list of tools (see `mix check` docs for defaults) 11 | tools: [ 12 | ## curated tools may be disabled (e.g. the check for compilation warnings) 13 | # {:compiler, false}, 14 | 15 | ## ...or adjusted (e.g. use one-line formatter for more compact credo output) 16 | # {:credo, "mix credo --format oneline"}, 17 | 18 | {:check_formatter, command: "mix spark.formatter --check"}, 19 | {:doctor, false}, 20 | {:reuse, command: ["pipx", "run", "reuse", "lint", "-q"]} 21 | 22 | ## custom new tools may be added (mix tasks or arbitrary commands) 23 | # {:my_mix_task, command: "mix release", env: %{"MIX_ENV" => "prod"}}, 24 | # {:my_arbitrary_tool, command: "npm test", cd: "assets"}, 25 | # {:my_arbitrary_script, command: ["my_script", "argument with spaces"], cd: "scripts"} 26 | ] 27 | ] 28 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 15 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 16 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 18 | USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Change Log 8 | 9 | All notable changes to this project will be documented in this file. 10 | See [Conventional Commits](Https://conventionalcommits.org) for commit guidelines. 11 | 12 | 13 | 14 | ## [v0.2.1](https://github.com/ash-project/ash_slug/compare/v0.2.0...v0.2.1) (2025-02-11) 15 | 16 | 17 | 18 | 19 | ### Improvements: 20 | 21 | * update spark for docs improvements 22 | 23 | ## [v0.2.0](https://github.com/ash-project/ash_slug/compare/v0.1.1...v0.2.0) (2025-01-27) 24 | 25 | 26 | 27 | 28 | ### Features: 29 | 30 | * resource: add update action with slugify change 31 | 32 | ## [v0.1.1](https://github.com/ash-project/ash_slug/compare/v0.1.0...v0.1.1) (2024-11-21) 33 | 34 | ### Improvements: 35 | 36 | * Support case insensitive strings using `Ash.CiString` 37 | * Updated dependencies 38 | 39 | ## [v0.1.0](https://github.com/ash-project/ash_slug/compare/v0.1.0...v0.1.0) (2024-05-29) 40 | 41 | ### Improvements: 42 | 43 | * get things set up for CI and release 44 | -------------------------------------------------------------------------------- /test/support/resource.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule AshSlugTest.Resource do 6 | @moduledoc false 7 | 8 | use Ash.Resource, 9 | domain: AshSlugTest.Domain, 10 | data_layer: Ash.DataLayer.Ets, 11 | extensions: [AshSlug] 12 | 13 | ets do 14 | private?(true) 15 | end 16 | 17 | attributes do 18 | uuid_primary_key(:id) 19 | attribute(:text1, :string, public?: true) 20 | attribute(:text2, :string, public?: true) 21 | attribute(:text2_slug, :string) 22 | attribute(:text3, :ci_string, public?: true) 23 | attribute(:text3_slug, :string) 24 | attribute(:bool, :boolean, public?: true) 25 | end 26 | 27 | actions do 28 | create :create do 29 | accept([:text1, :text2, :text3, :bool]) 30 | 31 | change(slugify(:text1, lowercase?: false)) 32 | change(slugify(:text2, into: :text2_slug)) 33 | change(slugify(:text3, into: :text3_slug)) 34 | change(slugify(:bool)) 35 | end 36 | 37 | update :update do 38 | require_atomic?(false) 39 | accept([:text1]) 40 | 41 | change(slugify(:text1, lowercase?: false)) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ![Logo](https://github.com/ash-project/ash/blob/main/logos/cropped-for-header-black-text.png?raw=true#gh-light-mode-only) 8 | ![Logo](https://github.com/ash-project/ash/blob/main/logos/cropped-for-header-white-text.png?raw=true#gh-dark-mode-only) 9 | 10 | ![Elixir CI](https://github.com/ash-project/ash_slug/workflows/CI/badge.svg) 11 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 12 | [![Hex version badge](https://img.shields.io/hexpm/v/ash_slug.svg)](https://hex.pm/packages/ash_slug) 13 | [![Hexdocs badge](https://img.shields.io/badge/docs-hexdocs-purple)](https://hexdocs.pm/ash_slug) 14 | [![REUSE status](https://api.reuse.software/badge/github.com/ash-project/ash_slug)](https://api.reuse.software/info/github.com/ash-project/ash_slug) 15 | 16 | # AshSlug 17 | 18 | AshSlug is an [Ash](https://hexdocs.pm/ash) extension to slugify string attributes on a resource. 19 | 20 | The extension is a thin wrapper around the [Slugify](https://hex.pm/packages/slugify) library, and supports 21 | the same options. 22 | 23 | ### Example usage 24 | 25 | ```elixir 26 | defmodule MyDomain.Resource do 27 | @moduledoc false 28 | 29 | use Ash.Resource, 30 | domain: MyDomain, 31 | data_layer: Ash.DataLayer.Ets, 32 | extensions: [AshSlug] 33 | 34 | ets do 35 | private?(true) 36 | end 37 | 38 | attributes do 39 | uuid_primary_key(:id) 40 | attribute(:text, :string, public?: true) 41 | attribute(:text_slug, :string) 42 | end 43 | 44 | actions do 45 | create :create do 46 | accept([:text]) 47 | change slugify(:text, into: :text_slug) 48 | end 49 | end 50 | end 51 | ``` 52 | 53 | ## Reference 54 | 55 | - [AshSlug DSL](documentation/dsls/DSL-AshSlug.md) 56 | -------------------------------------------------------------------------------- /test/ash_slug_test.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule AshSlugTest do 6 | use ExUnit.Case 7 | doctest AshSlug 8 | 9 | test "ensure value is slugified" do 10 | resource = 11 | AshSlugTest.Resource 12 | |> Ash.Changeset.for_create(:create, %{text1: "Hello, World!"}) 13 | |> Ash.Changeset.set_context(%{foo: :bar}) 14 | |> Ash.create!() 15 | 16 | assert resource.text1 == "Hello-World" 17 | end 18 | 19 | test "ensure value is slugified when resource is updated" do 20 | resource = 21 | AshSlugTest.Resource 22 | |> Ash.Changeset.for_create(:create, %{text1: "Hello, World!"}) 23 | |> Ash.Changeset.set_context(%{foo: :bar}) 24 | |> Ash.create!() 25 | 26 | assert resource.text1 == "Hello-World" 27 | 28 | resource = 29 | resource 30 | |> Ash.Changeset.for_update(:update, %{text1: "Hello, World! Again"}) 31 | |> Ash.Changeset.set_context(%{foo: :bar}) 32 | |> Ash.update!() 33 | 34 | assert resource.text1 == "Hello-World-Again" 35 | end 36 | 37 | test "ensure Ash.CiString value is slugified" do 38 | resource = 39 | AshSlugTest.Resource 40 | |> Ash.Changeset.for_create(:create, %{text3: Ash.CiString.new("Hello, World!")}) 41 | |> Ash.Changeset.set_context(%{foo: :bar}) 42 | |> Ash.create!() 43 | 44 | assert resource.text3_slug == "hello-world" 45 | end 46 | 47 | test "ensure value is slugified into another attribute" do 48 | resource = 49 | AshSlugTest.Resource 50 | |> Ash.Changeset.for_create(:create, %{text2: "Hello, World!"}) 51 | |> Ash.Changeset.set_context(%{foo: :bar}) 52 | |> Ash.create!() 53 | 54 | assert resource.text2 == "Hello, World!" 55 | assert resource.text2_slug == "hello-world" 56 | end 57 | 58 | test "ensure non-string fields raise error" do 59 | assert_raise Ash.Error.Invalid, ~r/is not a string value/, fn -> 60 | AshSlugTest.Resource 61 | |> Ash.Changeset.for_create(:create, %{bool: false}) 62 | |> Ash.Changeset.set_context(%{foo: :bar}) 63 | |> Ash.create!() 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/ash_slug/changes/slugify.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule AshSlug.Changes.Slugify do 6 | @moduledoc false 7 | use Ash.Resource.Change 8 | 9 | @opt_schema [ 10 | attribute: [ 11 | doc: "The attribute to slugify.", 12 | required: true, 13 | type: :atom 14 | ], 15 | into: [ 16 | doc: "The attribute to store the slug in. Unless specified, the slug will be stored in the same attribute.", 17 | type: :atom, 18 | required: false 19 | ], 20 | lowercase?: [ 21 | doc: "Whether to lowercase the slug.", 22 | type: :boolean, 23 | default: true 24 | ], 25 | separator: [ 26 | doc: "The separator to use between words in the slug.", 27 | type: :string, 28 | default: "-" 29 | ], 30 | truncate: [ 31 | doc: "Truncates the slug at this character length, shortened to the nearest word.", 32 | type: :integer, 33 | required: false 34 | ], 35 | ignore: [ 36 | doc: "A string or list of strings of characters to ignore when slugifying.", 37 | type: {:wrap_list, :string}, 38 | default: [] 39 | ] 40 | ] 41 | 42 | def opt_schema, do: @opt_schema 43 | 44 | @impl true 45 | def init(opts) do 46 | case Spark.Options.validate(opts, opt_schema()) do 47 | {:ok, opts} -> {:ok, replace_lowercase_opts(opts)} 48 | {:error, error} -> {:error, Exception.message(error)} 49 | end 50 | end 51 | 52 | @impl true 53 | def change(changeset, opts, _) do 54 | Ash.Changeset.before_action(changeset, fn changeset -> 55 | with {attribute, opts} <- Keyword.pop(opts, :attribute), 56 | {into, opts} <- Keyword.pop(opts, :into, attribute), 57 | {:ok, value} when is_binary(value) <- get_attribute(changeset, attribute), 58 | slug <- Slug.slugify(value, opts) do 59 | changeset 60 | |> Ash.Changeset.force_change_attribute(into, slug) 61 | else 62 | {:ok, _} -> Ash.Changeset.add_error(changeset, field: opts[:attribute], message: "is not a string value") 63 | :error -> changeset 64 | end 65 | end) 66 | end 67 | 68 | defp get_attribute(changeset, attribute) do 69 | case Ash.Changeset.fetch_argument_or_change(changeset, attribute) do 70 | {:ok, %Ash.CiString{} = value} -> {:ok, Ash.CiString.value(value)} 71 | res -> res 72 | end 73 | end 74 | 75 | @spec replace_lowercase_opts(Keyword.t()) :: Keyword.t() 76 | defp replace_lowercase_opts(opts) do 77 | Keyword.pop(opts, :lowercase?) 78 | |> then(fn {lowercase, opts} -> Keyword.put(opts, :lowercase, lowercase) end) 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule AshSlug.MixProject do 6 | use Mix.Project 7 | 8 | @version "0.2.1" 9 | 10 | def project do 11 | [ 12 | app: :ash_slug, 13 | version: @version, 14 | elixir: "~> 1.14", 15 | start_permanent: Mix.env() == :prod, 16 | elixirc_paths: elixirc_paths(Mix.env()), 17 | deps: deps(), 18 | docs: &docs/0, 19 | package: package(), 20 | aliases: aliases(), 21 | description: "An Ash extension for slugifying attributes of a resource.", 22 | source_url: "https://github.com/ash-project/ash_slug", 23 | homepage_url: "https://github.com/ash-project/ash_slug" 24 | ] 25 | end 26 | 27 | defp docs do 28 | [ 29 | main: "readme", 30 | source_ref: "v#{@version}", 31 | extras: [ 32 | {"README.md", title: "Home"}, 33 | {"documentation/dsls/DSL-AshSlug.md", search_data: Spark.Docs.search_data_for(AshSlug)} 34 | ], 35 | groups_for_extras: [ 36 | Reference: ~r"documentation/dsls" 37 | ], 38 | before_closing_head_tag: fn type -> 39 | if type == :html do 40 | """ 41 | 50 | """ 51 | end 52 | end 53 | ] 54 | end 55 | 56 | # Run "mix help compile.app" to learn about applications. 57 | def application do 58 | [ 59 | extra_applications: [:logger] 60 | ] 61 | end 62 | 63 | defp package do 64 | [ 65 | maintainers: [ 66 | "Rolf Håvard Blindheim ", 67 | "Zach Daneial " 68 | ], 69 | licenses: ["MIT"], 70 | files: ~w(lib .formatter.exs mix.exs README* LICENSE* 71 | CHANGELOG* documentation), 72 | links: %{ 73 | "GitHub" => "https://github.com/ash-project/ash_slug", 74 | "Changelog" => "https://github.com/ash-project/ash_slug/blob/main/CHANGELOG.md", 75 | "Discord" => "https://discord.gg/HTHRaaVPUc", 76 | "Website" => "https://ash-hq.org", 77 | "Forum" => "https://elixirforum.com/c/elixir-framework-forums/ash-framework-forum", 78 | "REUSE Compliance" => "https://api.reuse.software/info/github.com/ash-project/ash_slug" 79 | } 80 | ] 81 | end 82 | 83 | defp elixirc_paths(:test), do: ["lib", "test/support"] 84 | defp elixirc_paths(_), do: ["lib"] 85 | 86 | # Run "mix help deps" to learn about dependencies. 87 | defp deps do 88 | [ 89 | {:ash, "~> 3.0"}, 90 | {:slugify, "~> 1.3"}, 91 | {:igniter, "~> 0.5", only: [:dev, :test]}, 92 | {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, 93 | {:ex_check, "~> 0.12", only: [:dev, :test]}, 94 | {:credo, ">= 0.0.0", only: [:dev, :test], runtime: false}, 95 | {:dialyxir, ">= 0.0.0", only: [:dev, :test], runtime: false}, 96 | {:sobelow, ">= 0.0.0", only: [:dev, :test], runtime: false}, 97 | {:git_ops, "~> 2.5", only: [:dev, :test]}, 98 | {:mix_test_watch, "~> 1.0", only: :dev, runtime: false}, 99 | {:mix_audit, ">= 0.0.0", only: [:dev, :test], runtime: false} 100 | ] 101 | end 102 | 103 | defp aliases do 104 | [ 105 | sobelow: "sobelow --skip", 106 | credo: "credo --strict", 107 | docs: [ 108 | "spark.cheat_sheets", 109 | "docs", 110 | "spark.replace_doc_links" 111 | ], 112 | "spark.formatter": "spark.formatter --extensions AshSlug", 113 | "spark.cheat_sheets_in_search": "spark.cheat_sheets_in_search --extensions AshSlug", 114 | "spark.cheat_sheets": "spark.cheat_sheets --extensions AshSlug" 115 | ] 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /.credo.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 ash_slug contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # This file contains the configuration for Credo and you are probably reading 6 | # this after creating it with `mix credo.gen.config`. 7 | # 8 | # If you find anything wrong or unclear in this file, please report an 9 | # issue on GitHub: https://github.com/rrrene/credo/issues 10 | # 11 | %{ 12 | # 13 | # You can have as many configs as you like in the `configs:` field. 14 | configs: [ 15 | %{ 16 | # 17 | # Run any config using `mix credo -C `. If no config name is given 18 | # "default" is used. 19 | # 20 | name: "default", 21 | # 22 | # These are the files included in the analysis: 23 | files: %{ 24 | # 25 | # You can give explicit globs or simply directories. 26 | # In the latter case `**/*.{ex,exs}` will be used. 27 | # 28 | included: [ 29 | "lib/", 30 | "src/", 31 | "test/", 32 | "web/", 33 | "apps/*/lib/", 34 | "apps/*/src/", 35 | "apps/*/test/", 36 | "apps/*/web/" 37 | ], 38 | excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] 39 | }, 40 | # 41 | # Load and configure plugins here: 42 | # 43 | plugins: [], 44 | # 45 | # If you create your own checks, you must specify the source files for 46 | # them here, so they can be loaded by Credo before running the analysis. 47 | # 48 | requires: [], 49 | # 50 | # If you want to enforce a style guide and need a more traditional linting 51 | # experience, you can change `strict` to `true` below: 52 | # 53 | strict: false, 54 | # 55 | # To modify the timeout for parsing files, change this value: 56 | # 57 | parse_timeout: 5000, 58 | # 59 | # If you want to use uncolored output by default, you can change `color` 60 | # to `false` below: 61 | # 62 | color: true, 63 | # 64 | # You can customize the parameters of any check by adding a second element 65 | # to the tuple. 66 | # 67 | # To disable a check put `false` as second element: 68 | # 69 | # {Credo.Check.Design.DuplicatedCode, false} 70 | # 71 | checks: [ 72 | # 73 | ## Consistency Checks 74 | # 75 | {Credo.Check.Consistency.ExceptionNames, []}, 76 | {Credo.Check.Consistency.LineEndings, []}, 77 | {Credo.Check.Consistency.ParameterPatternMatching, []}, 78 | {Credo.Check.Consistency.SpaceAroundOperators, []}, 79 | {Credo.Check.Consistency.SpaceInParentheses, []}, 80 | {Credo.Check.Consistency.TabsOrSpaces, []}, 81 | 82 | # 83 | ## Design Checks 84 | # 85 | # You can customize the priority of any check 86 | # Priority values are: `low, normal, high, higher` 87 | # 88 | {Credo.Check.Design.AliasUsage, false}, 89 | # You can also customize the exit_status of each check. 90 | # If you don't want TODO comments to cause `mix credo` to fail, just 91 | # set this value to 0 (zero). 92 | # 93 | {Credo.Check.Design.TagTODO, [exit_status: 2]}, 94 | {Credo.Check.Design.TagFIXME, []}, 95 | 96 | # 97 | ## Readability Checks 98 | # 99 | {Credo.Check.Readability.AliasOrder, []}, 100 | {Credo.Check.Readability.FunctionNames, []}, 101 | {Credo.Check.Readability.LargeNumbers, []}, 102 | {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, 103 | {Credo.Check.Readability.ModuleAttributeNames, []}, 104 | {Credo.Check.Readability.ModuleDoc, []}, 105 | {Credo.Check.Readability.ModuleNames, []}, 106 | {Credo.Check.Readability.ParenthesesInCondition, []}, 107 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, 108 | {Credo.Check.Readability.PredicateFunctionNames, []}, 109 | {Credo.Check.Readability.PreferImplicitTry, []}, 110 | {Credo.Check.Readability.RedundantBlankLines, []}, 111 | {Credo.Check.Readability.Semicolons, []}, 112 | {Credo.Check.Readability.SpaceAfterCommas, []}, 113 | {Credo.Check.Readability.StringSigils, []}, 114 | {Credo.Check.Readability.TrailingBlankLine, []}, 115 | {Credo.Check.Readability.TrailingWhiteSpace, []}, 116 | {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, 117 | {Credo.Check.Readability.VariableNames, []}, 118 | 119 | # 120 | ## Refactoring Opportunities 121 | # 122 | {Credo.Check.Refactor.CondStatements, []}, 123 | {Credo.Check.Refactor.CyclomaticComplexity, false}, 124 | {Credo.Check.Refactor.FunctionArity, [max_arity: 13]}, 125 | {Credo.Check.Refactor.LongQuoteBlocks, false}, 126 | {Credo.Check.Refactor.MapInto, false}, 127 | {Credo.Check.Refactor.MatchInCondition, []}, 128 | {Credo.Check.Refactor.NegatedConditionsInUnless, []}, 129 | {Credo.Check.Refactor.NegatedConditionsWithElse, []}, 130 | {Credo.Check.Refactor.Nesting, [max_nesting: 6]}, 131 | {Credo.Check.Refactor.UnlessWithElse, []}, 132 | {Credo.Check.Refactor.WithClauses, []}, 133 | 134 | # 135 | ## Warnings 136 | # 137 | {Credo.Check.Warning.BoolOperationOnSameValues, []}, 138 | {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, 139 | {Credo.Check.Warning.IExPry, []}, 140 | {Credo.Check.Warning.IoInspect, []}, 141 | {Credo.Check.Warning.LazyLogging, false}, 142 | {Credo.Check.Warning.MixEnv, false}, 143 | {Credo.Check.Warning.OperationOnSameValues, []}, 144 | {Credo.Check.Warning.OperationWithConstantResult, []}, 145 | {Credo.Check.Warning.RaiseInsideRescue, []}, 146 | {Credo.Check.Warning.UnusedEnumOperation, []}, 147 | {Credo.Check.Warning.UnusedFileOperation, []}, 148 | {Credo.Check.Warning.UnusedKeywordOperation, []}, 149 | {Credo.Check.Warning.UnusedListOperation, []}, 150 | {Credo.Check.Warning.UnusedPathOperation, []}, 151 | {Credo.Check.Warning.UnusedRegexOperation, []}, 152 | {Credo.Check.Warning.UnusedStringOperation, []}, 153 | {Credo.Check.Warning.UnusedTupleOperation, []}, 154 | {Credo.Check.Warning.UnsafeExec, []}, 155 | 156 | # 157 | # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) 158 | 159 | # 160 | # Controversial and experimental checks (opt-in, just replace `false` with `[]`) 161 | # 162 | {Credo.Check.Readability.StrictModuleLayout, false}, 163 | {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, 164 | {Credo.Check.Consistency.UnusedVariableNames, false}, 165 | {Credo.Check.Design.DuplicatedCode, false}, 166 | {Credo.Check.Readability.AliasAs, false}, 167 | {Credo.Check.Readability.MultiAlias, false}, 168 | {Credo.Check.Readability.Specs, false}, 169 | {Credo.Check.Readability.SinglePipe, false}, 170 | {Credo.Check.Readability.WithCustomTaggedTuple, false}, 171 | {Credo.Check.Refactor.ABCSize, false}, 172 | {Credo.Check.Refactor.AppendSingleItem, false}, 173 | {Credo.Check.Refactor.DoubleBooleanNegation, false}, 174 | {Credo.Check.Refactor.ModuleDependencies, false}, 175 | {Credo.Check.Refactor.NegatedIsNil, false}, 176 | {Credo.Check.Refactor.PipeChainStart, false}, 177 | {Credo.Check.Refactor.VariableRebinding, false}, 178 | {Credo.Check.Warning.LeakyEnvironment, false}, 179 | {Credo.Check.Warning.MapGetUnsafePass, false}, 180 | {Credo.Check.Warning.UnsafeToAtom, false} 181 | 182 | # 183 | # Custom checks can be created using `mix credo.gen.check`. 184 | # 185 | ] 186 | } 187 | ] 188 | } 189 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "ash": {:hex, :ash, "3.6.2", "90d1c8296be777b90caabf51b99323d6618a0b92594dfab92b02bdf848ac38bf", [:mix], [{: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]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {: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.11", [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.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [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", "3546b5798dd24576cc451f6e03f3d6e3bb62666c0921bfe8aae700c599d9c38d"}, 3 | "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, 4 | "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [: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", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, 5 | "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, 6 | "dialyxir": {:hex, :dialyxir, "1.4.6", "7cca478334bf8307e968664343cbdb432ee95b4b68a9cba95bdabb0ad5bdfd9a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "8cf5615c5cd4c2da6c501faae642839c8405b49f8aa057ad4ae401cb808ef64d"}, 7 | "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, 8 | "ecto": {:hex, :ecto, "3.13.3", "6a983f0917f8bdc7a89e96f2bf013f220503a0da5d8623224ba987515b3f0d80", [: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", "1927db768f53a88843ff25b6ba7946599a8ca8a055f69ad8058a1432a399af94"}, 9 | "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, 10 | "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, 11 | "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, 12 | "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "0630c3ca67d40f3b4ce88d65af14c8960c571ef7", []}, 13 | "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, 14 | "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, 15 | "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, 16 | "git_ops": {:hex, :git_ops, "2.8.0", "29ac9ab68bf9645973cb2752047b987e75cbd3d9761489c615e3ba80018fa885", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "b535e4ad6b5d13e14c455e76f65825659081b5530b0827eb0232d18719530eec"}, 17 | "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, 18 | "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, 19 | "igniter": {:hex, :igniter, "0.6.30", "83a466369ebb8fe009e0823c7bf04314dc545122c2d48f896172fc79df33e99d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "76a14d5b7f850bb03b5243088c3649d54a2e52e34a2aa1104dee23cf50a8bae0"}, 20 | "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, 21 | "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, 22 | "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, 23 | "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, 24 | "makeup_elixir": {:hex, :makeup_elixir, "1.0.0", "74bb8348c9b3a51d5c589bf5aebb0466a84b33274150e3b6ece1da45584afc82", [: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", "49159b7d7d999e836bedaf09dcf35ca18b312230cf901b725a64f3f42e407983"}, 25 | "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, 26 | "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, 27 | "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, 28 | "mix_audit": {:hex, :mix_audit, "2.1.5", "c0f77cee6b4ef9d97e37772359a187a166c7a1e0e08b50edf5bf6959dfe5a016", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "87f9298e21da32f697af535475860dc1d3617a010e0b418d2ec6142bc8b42d69"}, 29 | "mix_test_watch": {:hex, :mix_test_watch, "1.3.0", "2ffc9f72b0d1f4ecf0ce97b044e0e3c607c3b4dc21d6228365e8bc7c2856dc77", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "f9e5edca976857ffac78632e635750d158df14ee2d6185a15013844af7570ffe"}, 30 | "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, 31 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, 32 | "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, 33 | "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, 34 | "reactor": {:hex, :reactor, "0.17.0", "eb8bdb530dbae824e2d36a8538f8ec4f3aa7c2d1b61b04959fa787c634f88b49", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-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]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "3c3bf71693adbad9117b11ec83cfed7d5851b916ade508ed9718de7ae165bf25"}, 35 | "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{: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, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, 36 | "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, 37 | "slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"}, 38 | "sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"}, 39 | "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, 40 | "spark": {:hex, :spark, "2.3.5", "f30d30ecc3b4ab9b932d9aada66af7677fc1f297a2c349b0bcec3eafb9f996e8", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "0e9d339704d5d148f77f2b2fef3bcfc873a9e9bb4224fcf289c545d65827202f"}, 41 | "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, 42 | "splode": {:hex, :splode, "0.2.9", "3a2776e187c82f42f5226b33b1220ccbff74f4bcc523dd4039c804caaa3ffdc7", [:mix], [], "hexpm", "8002b00c6e24f8bd1bcced3fbaa5c33346048047bb7e13d2f3ad428babbd95c3"}, 43 | "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, 44 | "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, 45 | "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, 46 | "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, 47 | "yaml_elixir": {:hex, :yaml_elixir, "2.12.0", "30343ff5018637a64b1b7de1ed2a3ca03bc641410c1f311a4dbdc1ffbbf449c7", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "ca6bacae7bac917a7155dca0ab6149088aa7bc800c94d0fe18c5238f53b313c6"}, 48 | "ymlr": {:hex, :ymlr, "5.1.4", "b924d61e1fc1ec371cde6ab3ccd9311110b1e052fc5c2460fb322e8380e7712a", [:mix], [], "hexpm", "75f16cf0709fbd911b30311a0359a7aa4b5476346c01882addefd5f2b1cfaa51"}, 49 | } 50 | --------------------------------------------------------------------------------