From 95a75b542a5da4cac5fd3186885a9b139435fcb9 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Apr 2023 11:43:36 -0600 Subject: [PATCH] init --- .check.exs | 21 +++ .credo.exs | 184 +++++++++++++++++++++ .formatter.exs | 11 ++ .github/CODE_OF_CONDUCT.md | 76 +++++++++ .github/CONTRIBUTING.md | 10 ++ .github/ISSUE_TEMPLATE/bug_report.md | 27 +++ .github/ISSUE_TEMPLATE/proposal.md | 36 ++++ .github/PULL_REQUEST_TEMPLATE.md | 4 + .github/workflows/elixir.yml | 14 ++ .gitignore | 26 +++ .tool-versions | 2 + FUNDING.yml | 1 + LICENSE | 21 +++ README.md | 21 +++ lib/ash_fsm.ex | 63 +++++++ lib/info.ex | 8 + lib/transformers/fill_in_event_defaults.ex | 40 +++++ logos/small-logo.png | Bin 0 -> 3088 bytes mix.exs | 153 +++++++++++++++++ mix.lock | 16 ++ test/ash_fsm_test.exs | 31 ++++ test/test_helper.exs | 1 + 22 files changed, 766 insertions(+) create mode 100644 .check.exs create mode 100644 .credo.exs create mode 100644 .formatter.exs create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/proposal.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/elixir.yml create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 FUNDING.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lib/ash_fsm.ex create mode 100644 lib/info.ex create mode 100644 lib/transformers/fill_in_event_defaults.ex create mode 100644 logos/small-logo.png create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 test/ash_fsm_test.exs create mode 100644 test/test_helper.exs diff --git a/.check.exs b/.check.exs new file mode 100644 index 0000000..865ff7e --- /dev/null +++ b/.check.exs @@ -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"}, + + {:check_formatter, command: "mix spark.formatter --check"} + + ## 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"} + ] +] diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..3d2c080 --- /dev/null +++ b/.credo.exs @@ -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 `. 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, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapInto, false}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, [max_nesting: 6]}, + {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`. + # + ] + } + ] +} diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..f22b4b1 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,11 @@ +spark_locals_without_parens = [] + +# Used by "mix format" +[ + import_deps: [:ash], + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], + locals_without_parens: spark_locals_without_parens, + export: [ + locals_without_parens: spark_locals_without_parens + ] +] diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7aa6f74 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -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 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..1253191 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contributing to Ash + +* 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. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..a4ff0ac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug, needs review +assignees: '' + +-https://hexdocs.pm/ash_json_api-- + +**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 + - Ash version + - any related extension versions + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/proposal.md b/.github/ISSUE_TEMPLATE/proposal.md new file mode 100644 index 0000000..f347dcb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/proposal.md @@ -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 + end +``` + +Or + +```elixir + Api.read(:resource, bar: 10) # <- Adding `bar` here would cause +``` + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..8c13744 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,4 @@ +### Contributor checklist + +- [ ] Bug fixes include regression tests +- [ ] Features include unit/acceptance tests diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml new file mode 100644 index 0000000..0f6cfe0 --- /dev/null +++ b/.github/workflows/elixir.yml @@ -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 }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0206707 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +ash_fsm-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..55e53bf --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +erlang 25.2.2 +elixir 1.14.3 diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..b98cb0d --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1 @@ +github: zachdaniel \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4eb51a5 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4815254 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# AshFsm + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `ash_fsm` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:ash_fsm, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/lib/ash_fsm.ex b/lib/ash_fsm.ex new file mode 100644 index 0000000..7505956 --- /dev/null +++ b/lib/ash_fsm.ex @@ -0,0 +1,63 @@ +defmodule AshFsm do + @moduledoc """ + Documentation for `AshFsm`. + """ + + defmodule Event do + @moduledoc """ + The configuration for an event. + """ + defstruct [:action, :from, :to] + end + + @event %Spark.Dsl.Entity{ + name: :event, + target: Event, + args: [:action], + identifier: :action, + schema: [ + action: [ + type: :atom, + doc: "The corresponding action that is invoked for the event." + ], + from: [ + type: {:or, [{:list, :atom}, :atom]}, + doc: + "The states in which this action may be called. If not specified, then any state is accepted." + ], + to: [ + type: {:or, [{:list, :atom}, :atom]}, + doc: + "The states that this action may move to. If not specified, then any state is accepted." + ] + ] + } + + @events %Spark.Dsl.Section{ + name: :events, + entities: [ + @event + ] + } + + @fsm %Spark.Dsl.Section{ + name: :fsm, + schema: [ + state_attribute: [ + type: :atom, + doc: "The attribute to store the state in.", + default: :state + ], + initial_states: [ + type: {:or, [{:list, :atom}, :atom]}, + doc: + "The allowed starting states of this state machine. If not specified, all states are allowed." + ] + ], + sections: [ + @events + ] + } + + use Spark.Dsl.Extension, sections: [@fsm] +end diff --git a/lib/info.ex b/lib/info.ex new file mode 100644 index 0000000..3402357 --- /dev/null +++ b/lib/info.ex @@ -0,0 +1,8 @@ +defmodule AshFsm.Info do + use Spark.InfoGenerator, extension: AshFsm, sections: [:fsm] + + @spec fsm_all_states(Ash.Resource.record() | map()) :: list(atom) + def fsm_all_states(resource_or_dsl) do + Spark.Dsl.Extension.get_persisted(resource_or_dsl, :all_fsm_states, []) + end +end diff --git a/lib/transformers/fill_in_event_defaults.ex b/lib/transformers/fill_in_event_defaults.ex new file mode 100644 index 0000000..f86edd0 --- /dev/null +++ b/lib/transformers/fill_in_event_defaults.ex @@ -0,0 +1,40 @@ +defmodule AshFsm.Transformers.FillInEventDefaults do + use Spark.Dsl.Transformer + alias Spark.Dsl.Transformer + @moduledoc false + + def transform(dsl_state) do + initial_states = List.wrap(AshFsm.Info.fsm_initial_states!(dsl_state)) + + events = + dsl_state + |> AshFsm.Info.fsm_events() + + all_states = + events + |> Enum.flat_map(fn event -> + List.wrap(event.from) ++ List.wrap(event.to) + end) + |> Enum.uniq() + |> Enum.concat(List.wrap(initial_states)) + + dsl_state = + case AshFsm.Info.fsm_initial_states(dsl_state) do + {:ok, value} when not is_nil(value) and value != [] -> + dsl_state + + _ -> + Transformer.set_option(dsl_state, [:fsm], :initial_states, all_states) + end + + events + |> Enum.reduce(dsl_state, fn event, dsl_state -> + Transformer.replace_entity(dsl_state, [:fsm], %{ + event + | from: event.from || all_states, + to: event.to || all_states + }) + end) + |> Transformer.persist(:all_fsm_states, all_states) + end +end diff --git a/logos/small-logo.png b/logos/small-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc9aa1801a2a5d20a6525f31fe7a619fb39f044 GIT binary patch literal 3088 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 D^yu%Z literal 0 HcmV?d00001 diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..225010b --- /dev/null +++ b/mix.exs @@ -0,0 +1,153 @@ +defmodule AshFsm.MixProject do + use Mix.Project + + @version "0.1.0" + + @description """ + An Ash.Resource extension for building finite state machines + """ + + def project do + [ + app: :ash_fsm, + version: @version, + elixir: "~> 1.14", + start_permanent: Mix.env() == :prod, + package: package(), + aliases: aliases(), + deps: deps(), + elixirc_paths: elixirc_paths(Mix.env()), + dialyzer: [plt_add_apps: [:ash]], + docs: docs(), + description: @description, + source_url: "https://github.com/ash-project/ash_fsm", + homepage_url: "https://github.com/ash-project/ash_fsm" + ] + end + + defp package do + [ + name: :ash_fsm, + licenses: ["MIT"], + files: ~w(lib .formatter.exs mix.exs README* LICENSE* + CHANGELOG* documentation), + links: %{ + GitHub: "https://github.com/ash-project/ash_fsm" + } + ] + end + + defp elixirc_paths(:test) do + elixirc_paths(:dev) ++ ["test/support"] + end + + defp elixirc_paths(_) do + ["lib"] + end + + defp extras() do + "documentation/**/*.md" + |> Path.wildcard() + |> Enum.map(fn path -> + title = + path + |> Path.basename(".md") + |> String.split(~r/[-_]/) + |> Enum.map(&String.capitalize/1) + |> Enum.join(" ") + |> case do + "F A Q" -> + "FAQ" + + other -> + other + end + + {String.to_atom(path), + [ + title: title + ]} + end) + end + + defp groups_for_extras() do + "documentation/*" + |> Path.wildcard() + |> Enum.map(fn folder -> + name = + folder + |> Path.basename() + |> String.split(~r/[-_]/) + |> Enum.map(&String.capitalize/1) + |> Enum.join(" ") + + {name, folder |> Path.join("**") |> Path.wildcard()} + end) + end + + defp docs do + [ + main: "AshFsm", + source_ref: "v#{@version}", + logo: "logos/small-logo.png", + extra_section: "GUIDES", + spark: [ + extensions: [ + %{ + module: AshFsm, + name: "AshFsm", + target: "Ash.Resource", + type: "Fsm Resource" + }, + %{ + module: AshGraphql.Api, + name: "AshGraphql Api", + target: "Ash.Api", + type: "GraphQL Api" + } + ] + ], + extras: extras(), + groups_for_extras: groups_for_extras(), + groups_for_modules: [ + AshGraphql: [ + AshGraphql + ], + Introspection: [ + AshGraphql.Resource.Info, + AshGraphql.Api.Info + ], + Miscellaneous: [ + AshGraphql.Resource.Helpers + ], + Internals: ~r/.*/ + ] + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:ash, "~> 2.7"}, + {:spark, path: "../spark", override: true} + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end + + defp aliases do + [ + sobelow: "sobelow --skip", + credo: "credo --strict", + docs: ["docs", "ash.replace_doc_links"], + "spark.formatter": "spark.formatter --extensions AshGraphql.Resource,AshGraphql.Api" + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..7fb6bbd --- /dev/null +++ b/mix.lock @@ -0,0 +1,16 @@ +%{ + "ash": {:hex, :ash, "2.7.1", "e8915f4ebb4dbf8e8f689020430adf30f68ac2f4bd2613e416e6f69fd8f4dc69", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [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: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b6e708fac305147f99d292823782bf8f7d2897efae7fe4fdf688e3c7cad777f"}, + "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [:mix], [{:decimal, "~> 1.6 or ~> 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", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"}, + "elixir_make": {:hex, :elixir_make, "0.7.6", "67716309dc5d43e16b5abbd00c01b8df6a0c2ab54a8f595468035a50189f9169", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5a0569756b0f7873a77687800c164cca6dfc03a09418e6fcf853d78991f49940"}, + "ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, + "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, + "sourceror": {:hex, :sourceror, "0.12.2", "2ae55efd149193572e0eb723df7c7a1bda9ab33c43373c82642931dbb2f4e428", [:mix], [], "hexpm", "7ad74ade6fb079c71f29fae10c34bcf2323542d8c51ee1bcd77a546cfa89d59c"}, + "spark": {:hex, :spark, "1.0.8", "bc5c7db652b6adca5c26e79b70d000c96864d9a30a7b8030aa3137ed86bad3df", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "0ce14815466ac5c5a1cbdf742f85cab6369d3f25bda13ea7dde80e3a9bd699fd"}, + "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, +} diff --git a/test/ash_fsm_test.exs b/test/ash_fsm_test.exs new file mode 100644 index 0000000..b3314f4 --- /dev/null +++ b/test/ash_fsm_test.exs @@ -0,0 +1,31 @@ +defmodule AshFsmTest do + use ExUnit.Case + doctest AshFsm + + defmodule TwoStates do + use Ash.Resource, + data_layer: Ash.DataLayer.Ets, + extensions: [AshFsm] + + fsm do + initial_states([:pending]) + + events do + event(:begin, from: :pending, to: :executing) + event(:complete, from: :executing, to: :complete) + end + end + + ets do + private? true + end + + attributes do + uuid_primary_key :id + end + end + + test "greets the world" do + IO.inspect(AshFsm.Info.fsm_all_states(TwoStates)) + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()