├── priv ├── plts │ └── .gitkeep └── tables │ └── .gitkeep ├── .github ├── FUNDING.yml └── workflows │ └── elixir.yml ├── test ├── test_helper.exs └── quantum_storage_ets_test.exs ├── .formatter.exs ├── lib ├── quantum_storage_ets │ └── state.ex └── quantum_storage_ets.ex ├── config └── .credo.exs ├── CHANGELOG.md ├── .gitignore ├── mix.exs ├── README.md └── LICENSE /priv/plts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /priv/tables/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: quantum 2 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start(capture_log: true) 2 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: [".formatter.exs", "mix.exs", "{config,lib,test}/**/*.{ex,exs}"] 3 | ] 4 | -------------------------------------------------------------------------------- /lib/quantum_storage_ets/state.ex: -------------------------------------------------------------------------------- 1 | defmodule QuantumStoragePersistentEts.State do 2 | @moduledoc false 3 | 4 | @type t :: %__MODULE__{table: :ets.tid()} 5 | 6 | @enforce_keys [:table] 7 | defstruct @enforce_keys 8 | end 9 | -------------------------------------------------------------------------------- /config/.credo.exs: -------------------------------------------------------------------------------- 1 | %{ 2 | configs: [ 3 | %{ 4 | name: "default", 5 | files: %{ 6 | included: ["lib/", "test/", "mix.exs"], 7 | excluded: [] 8 | }, 9 | checks: [ 10 | # For others you can also set parameters 11 | {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 120}, 12 | {Credo.Check.Design.TagTODO, false} 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## Unreleased 6 | 7 | Diff for [unreleased](https://github.com/quantum-elixir/quantum-storage-persistent-ets/compare/v1.0.0...HEAD) 8 | 9 | ## v1.0.0 10 | 11 | Diff for [v1.0.0](https://github.com/quantum-elixir/quantum-storage-persistent-ets/compare/v1.0.0-rc.1...v1.0.0) 12 | 13 | ## v1.0.0-rc.1 14 | 15 | ### Added 16 | 17 | * Initial Commit 18 | 19 | Files for [v1.0.0-rc.1](https://github.com/quantum-elixir/quantum-storage-persistent-ets/tree/v1.0.0-rc.1) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | quantum_storage_persistent_ets-*.tar 24 | 25 | # Ignore mix.lock 26 | /mix.lock 27 | 28 | # Ets Tables 29 | /priv/tables/**/*.tab 30 | 31 | # Dialyzer 32 | /priv/plts/*.plt 33 | /priv/plts/*.hash -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule QuantumStoragePersistentEts.MixProject do 2 | @moduledoc false 3 | 4 | use Mix.Project 5 | 6 | @version "1.0.0" 7 | 8 | def project do 9 | [ 10 | app: :quantum_storage_persistent_ets, 11 | version: @version, 12 | elixir: "~> 1.8", 13 | build_embedded: Mix.env() == :prod, 14 | start_permanent: Mix.env() == :prod, 15 | deps: deps(), 16 | docs: docs(), 17 | name: "Quantum Storage Persistent ETS", 18 | description: "Quantum Storage Adapter based on Persistent ETS", 19 | elixirc_paths: elixirc_paths(Mix.env()), 20 | package: package(), 21 | test_coverage: [tool: ExCoveralls], 22 | build_embedded: (System.get_env("BUILD_EMBEDDED") || "false") in ["1", "true"], 23 | dialyzer: 24 | [ 25 | ignore_warnings: "dialyzer.ignore-warnings" 26 | ] ++ 27 | if (System.get_env("DIALYZER_PLT_PRIV") || "false") in ["1", "true"] do 28 | [ 29 | plt_file: {:no_warn, "priv/plts/dialyzer.plt"} 30 | ] 31 | else 32 | [] 33 | end 34 | ] 35 | end 36 | 37 | # Run "mix help compile.app" to learn about applications. 38 | def application do 39 | [ 40 | extra_applications: [:logger] 41 | ] 42 | end 43 | 44 | defp elixirc_paths(:test), do: ["lib", "test/support"] 45 | defp elixirc_paths(_), do: ["lib"] 46 | 47 | defp package do 48 | %{ 49 | maintainers: [ 50 | "Jonatan Männchen" 51 | ], 52 | licenses: ["Apache License 2.0"], 53 | exclude_patterns: [~r[priv/(tables|plts)]], 54 | links: %{ 55 | "Changelog" => 56 | "https://github.com/quantum-elixir/quantum-storage-persistent-ets/blob/master/CHANGELOG.md", 57 | "GitHub" => "https://github.com/quantum-elixir/quantum-storage-persistent-ets" 58 | } 59 | } 60 | end 61 | 62 | defp docs do 63 | [ 64 | main: "readme", 65 | source_ref: "v#{@version}", 66 | source_url: "https://github.com/quantum-elixir/quantum-storage-persistent-ets", 67 | extras: [ 68 | "README.md", 69 | "CHANGELOG.md" 70 | ] 71 | ] 72 | end 73 | 74 | # Run "mix help deps" to learn about dependencies. 75 | defp deps do 76 | [ 77 | {:persistent_ets, "~> 0.2"}, 78 | {:quantum, "~> 3.0"}, 79 | {:ex_doc, "~> 0.13", only: [:dev, :docs], runtime: false}, 80 | {:excoveralls, "~> 0.13", only: [:dev, :test], runtime: false}, 81 | {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}, 82 | {:credo, "~> 1.4", only: [:dev, :test], runtime: false} 83 | ] 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /test/quantum_storage_ets_test.exs: -------------------------------------------------------------------------------- 1 | defmodule QuantumStoragePersistentEtsTest do 2 | @moduledoc false 3 | 4 | use ExUnit.Case 5 | doctest QuantumStoragePersistentEts 6 | 7 | defmodule Scheduler do 8 | @moduledoc false 9 | 10 | use Quantum, otp_app: :quantum_storage_persistent_ets 11 | end 12 | 13 | setup %{test: test} do 14 | storage = 15 | start_supervised!({QuantumStoragePersistentEts, name: Module.concat(__MODULE__, test)}) 16 | 17 | assert :ok = QuantumStoragePersistentEts.purge(storage) 18 | 19 | {:ok, storage: storage} 20 | end 21 | 22 | describe "purge/1" do 23 | test "purges correct module", %{storage: storage} do 24 | assert :ok = QuantumStoragePersistentEts.add_job(storage, Scheduler.new_job()) 25 | assert :ok = QuantumStoragePersistentEts.purge(storage) 26 | assert :not_applicable = QuantumStoragePersistentEts.jobs(storage) 27 | end 28 | end 29 | 30 | describe "add_job/2" do 31 | test "adds job", %{storage: storage} do 32 | job = Scheduler.new_job() 33 | assert :ok = QuantumStoragePersistentEts.add_job(storage, job) 34 | assert [^job] = QuantumStoragePersistentEts.jobs(storage) 35 | end 36 | end 37 | 38 | describe "delete_job/2" do 39 | test "deletes job", %{storage: storage} do 40 | job = Scheduler.new_job() 41 | assert :ok = QuantumStoragePersistentEts.add_job(storage, job) 42 | assert :ok = QuantumStoragePersistentEts.delete_job(storage, job.name) 43 | assert [] = QuantumStoragePersistentEts.jobs(storage) 44 | end 45 | 46 | test "does not fail when deleting unknown job", %{storage: storage} do 47 | job = Scheduler.new_job() 48 | assert :ok = QuantumStoragePersistentEts.add_job(storage, job) 49 | 50 | assert :ok = QuantumStoragePersistentEts.delete_job(storage, make_ref()) 51 | end 52 | end 53 | 54 | describe "update_job_state/2" do 55 | test "updates job", %{storage: storage} do 56 | job = Scheduler.new_job() 57 | assert :ok = QuantumStoragePersistentEts.add_job(storage, job) 58 | assert :ok = QuantumStoragePersistentEts.update_job_state(storage, job.name, :inactive) 59 | assert [%{state: :inactive}] = QuantumStoragePersistentEts.jobs(storage) 60 | end 61 | 62 | test "does not fail when updating unknown job", %{storage: storage} do 63 | job = Scheduler.new_job() 64 | assert :ok = QuantumStoragePersistentEts.add_job(storage, job) 65 | 66 | assert :ok = QuantumStoragePersistentEts.update_job_state(storage, make_ref(), :inactive) 67 | end 68 | end 69 | 70 | describe "update_last_execution_date/2" do 71 | test "sets time on scheduler", %{storage: storage} do 72 | date = NaiveDateTime.utc_now() 73 | assert :ok = QuantumStoragePersistentEts.update_last_execution_date(storage, date) 74 | assert ^date = QuantumStoragePersistentEts.last_execution_date(storage) 75 | end 76 | end 77 | 78 | describe "last_execution_date/1" do 79 | test "gets time", %{storage: storage} do 80 | date = NaiveDateTime.utc_now() 81 | assert :ok = QuantumStoragePersistentEts.update_last_execution_date(storage, date) 82 | assert ^date = QuantumStoragePersistentEts.last_execution_date(storage) 83 | end 84 | 85 | test "get unknown otherwise", %{storage: storage} do 86 | assert :unknown = QuantumStoragePersistentEts.last_execution_date(storage) 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/quantum_storage_ets.ex: -------------------------------------------------------------------------------- 1 | defmodule QuantumStoragePersistentEts do 2 | @moduledoc """ 3 | `PersistentEts` based implementation of a `Quantum.Storage`. 4 | """ 5 | 6 | use GenServer 7 | 8 | require Logger 9 | 10 | alias __MODULE__.State 11 | 12 | @behaviour Quantum.Storage 13 | 14 | @doc false 15 | def start_link(opts), 16 | do: GenServer.start_link(__MODULE__, opts, opts) 17 | 18 | @doc false 19 | @impl GenServer 20 | def init(opts) do 21 | table_name = 22 | opts 23 | |> Keyword.fetch!(:name) 24 | |> Module.concat(Table) 25 | 26 | path = 27 | Application.app_dir( 28 | :quantum_storage_persistent_ets, 29 | "priv/tables/#{table_name}.tab" 30 | ) 31 | 32 | File.mkdir_p!(Path.dirname(path)) 33 | 34 | table = 35 | PersistentEts.new(table_name, path, [ 36 | :named_table, 37 | :ordered_set, 38 | :protected 39 | ]) 40 | 41 | {:ok, %State{table: table}} 42 | end 43 | 44 | @doc false 45 | @impl Quantum.Storage 46 | def jobs(storage_pid), do: GenServer.call(storage_pid, :jobs) 47 | 48 | @doc false 49 | @impl Quantum.Storage 50 | def add_job(storage_pid, job), do: GenServer.cast(storage_pid, {:add_job, job}) 51 | 52 | @doc false 53 | @impl Quantum.Storage 54 | def delete_job(storage_pid, job_name), do: GenServer.cast(storage_pid, {:delete_job, job_name}) 55 | 56 | @doc false 57 | @impl Quantum.Storage 58 | def update_job_state(storage_pid, job_name, state), 59 | do: GenServer.cast(storage_pid, {:update_job_state, job_name, state}) 60 | 61 | @doc false 62 | @impl Quantum.Storage 63 | def last_execution_date(storage_pid), do: GenServer.call(storage_pid, :last_execution_date) 64 | 65 | @doc false 66 | @impl Quantum.Storage 67 | def update_last_execution_date(storage_pid, last_execution_date), 68 | do: GenServer.cast(storage_pid, {:update_last_execution_date, last_execution_date}) 69 | 70 | @doc false 71 | @impl Quantum.Storage 72 | def purge(storage_pid), do: GenServer.cast(storage_pid, :purge) 73 | 74 | @doc false 75 | @impl GenServer 76 | def handle_cast({:add_job, job}, %State{table: table} = state) do 77 | :ets.insert(table, entry = {job_key(job.name), job}) 78 | :ets.insert(table, {:init_jobs}) 79 | 80 | Logger.debug(fn -> 81 | "[#{inspect(Node.self())}][#{__MODULE__}] inserting [#{inspect(entry)}] into Persistent ETS table [#{ 82 | table 83 | }]" 84 | end) 85 | 86 | {:noreply, state} 87 | end 88 | 89 | def handle_cast({:delete_job, job_name}, %State{table: table} = state) do 90 | :ets.delete(table, job_key(job_name)) 91 | 92 | {:noreply, state} 93 | end 94 | 95 | def handle_cast({:update_job_state, job_name, job_state}, %State{table: table} = state) do 96 | table 97 | |> :ets.lookup(job_key(job_name)) 98 | |> Enum.map(&{elem(&1, 0), %{elem(&1, 1) | state: job_state}}) 99 | |> Enum.each(&:ets.update_element(table, elem(&1, 0), {2, elem(&1, 1)})) 100 | 101 | {:noreply, state} 102 | end 103 | 104 | def handle_cast( 105 | {:update_last_execution_date, last_execution_date}, 106 | %State{table: table} = state 107 | ) do 108 | :ets.insert(table, {:last_execution_date, last_execution_date}) 109 | 110 | {:noreply, state} 111 | end 112 | 113 | def handle_cast(:purge, %State{table: table} = state) do 114 | :ets.delete_all_objects(table) 115 | 116 | {:noreply, state} 117 | end 118 | 119 | @doc false 120 | @impl GenServer 121 | def handle_call(:jobs, _from, %State{table: table} = state) do 122 | {:reply, 123 | table 124 | |> :ets.lookup(:init_jobs) 125 | |> case do 126 | [{:init_jobs}] -> 127 | table 128 | |> :ets.match({{:job, :_}, :"$1"}) 129 | |> List.flatten() 130 | 131 | [] -> 132 | :not_applicable 133 | end, state} 134 | end 135 | 136 | def handle_call(:last_execution_date, _from, %State{table: table} = state) do 137 | {:reply, 138 | table 139 | |> :ets.lookup(:last_execution_date) 140 | |> case do 141 | [] -> :unknown 142 | [{:last_execution_date, date} | _t] -> date 143 | end, state} 144 | end 145 | 146 | defp job_key(job_name) do 147 | {:job, job_name} 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quantum Storage Persistent Ets 2 | 3 | [![Financial Contributors on Open Collective](https://opencollective.com/quantum/all/badge.svg?label=financial+contributors)](https://opencollective.com/quantum) 4 | [![Hex.pm Version](http://img.shields.io/hexpm/v/quantum_storage_persistent_ets.svg)](https://hex.pm/packages/quantum_storage_persistent_ets) 5 | [![Hex docs](http://img.shields.io/badge/hex.pm-docs-green.svg?style=flat)](https://hexdocs.pm/quantum_storage_persistent_ets) 6 | ![.github/workflows/elixir.yml](https://github.com/quantum-elixir/quantum-storage-persistent-ets/workflows/.github/workflows/elixir.yml/badge.svg) 7 | [![Coverage Status](https://coveralls.io/repos/quantum-elixir/quantum-storage-persistent-ets/badge.svg?branch=master)](https://coveralls.io/r/quantum-elixir/quantum-storage-persistent-ets?branch=master) 8 | [![Hex.pm](https://img.shields.io/hexpm/dt/quantum_storage_persistent_ets.svg)](https://hex.pm/packages/quantum_storage_persistent_ets) 9 | 10 | Adds a persistent storage adapter for ETS. 11 | 12 | ## Installation 13 | 14 | The package can be installed by adding `quantum_storage_persistent_ets` to your list 15 | of dependencies in `mix.exs`: 16 | 17 | ```elixir 18 | def deps do 19 | [ 20 | {:quantum_storage_persistent_ets, "~> 1.0"} 21 | ] 22 | end 23 | ``` 24 | 25 | To enable the storage adpater, add this to your `config.exs`: 26 | 27 | ```elixir 28 | use Mix.Config 29 | 30 | config :quantum_test, QuantumTest.Scheduler, 31 | storage: QuantumStoragePersistentEts 32 | ``` 33 | 34 | The docs can be found at [https://hexdocs.pm/quantum_storage_persistent_ets](https://hexdocs.pm/quantum_storage_persistent_ets). 35 | 36 | ## Contribution 37 | 38 | This project uses the [Collective Code Construction Contract (C4)](http://rfc.zeromq.org/spec:42/C4/) 39 | for all code changes. 40 | 41 | > "Everyone, without distinction or discrimination, SHALL have an equal right to become a Contributor under the 42 | terms of this contract." 43 | 44 | ### tl;dr 45 | 46 | 1. Check for [open issues](https://github.com/quantum-elixir/quantum-storage-persistent-ets/issues) or [open a new issue](https://github.com/quantum-elixir/quantum-storage-persistent-ets/issues/new) to start 47 | a discussion around [a problem](https://www.youtube.com/watch?v=_QF9sFJGJuc). 48 | 2. Issues SHALL be named as "Problem: _description of the problem_". 49 | 3. Fork the [quantum-storage-persistent-ets repository on GitHub](https://github.com/quantum-elixir/quantum-storage-persistent-ets) to start making your changes 50 | 4. If possible, write a test which shows that the problem was solved. 51 | 5. Send a pull request. 52 | 6. Pull requests SHALL be named as "Solution: _description of your solution_" 53 | 7. Your pull request is merged and you are added to the [list of contributors](https://github.com/quantum-elixir/quantum-storage-persistent-ets/graphs/contributors) 54 | 55 | ### Code Contributors 56 | 57 | This project exists thanks to all the people who contribute. 58 | 59 | 60 | ### Financial Contributors 61 | 62 | Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/quantum/contribute)] 63 | 64 | #### Individuals 65 | 66 | 67 | 68 | #### Organizations 69 | 70 | Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/quantum/contribute)] 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | ## License 84 | 85 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /.github/workflows/elixir.yml: -------------------------------------------------------------------------------- 1 | on: 2 | - push 3 | - pull_request 4 | 5 | env: 6 | BUILD_EMBEDDED: true 7 | DIALYZER_PLT_PRIV: true 8 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9 | 10 | jobs: 11 | deps: 12 | name: Install Deps 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-elixir@v1 19 | with: 20 | otp-version: 22.3 21 | elixir-version: 1.10.3 22 | - uses: actions/cache@v2 23 | id: cache 24 | with: 25 | path: deps 26 | key: deps-${{ runner.os }}-${{ github.ref }} 27 | restore-keys: | 28 | deps-${{ runner.os }}- 29 | deps- 30 | - run: mix deps.get 31 | - uses: actions/upload-artifact@v1 32 | with: 33 | name: deps 34 | path: deps 35 | - uses: actions/upload-artifact@v1 36 | with: 37 | name: deps_lock 38 | path: mix.lock 39 | 40 | compile_dev: 41 | name: Compile Dev Environment 42 | 43 | runs-on: ubuntu-latest 44 | 45 | needs: ['deps'] 46 | 47 | steps: 48 | - uses: actions/checkout@v2 49 | - uses: actions/setup-elixir@v1 50 | with: 51 | otp-version: 22.3 52 | elixir-version: 1.10.3 53 | - uses: actions/download-artifact@v1 54 | with: 55 | name: deps 56 | path: deps 57 | - uses: actions/download-artifact@v1 58 | with: 59 | name: deps_lock 60 | path: . 61 | - uses: actions/cache@v2 62 | id: cache 63 | with: 64 | path: _build/dev 65 | key: compile_dev-${{ runner.os }}-${{ github.ref }} 66 | restore-keys: | 67 | compile_dev-${{ runner.os }}- 68 | compile_dev- 69 | - run: mix deps.compile 70 | env: 71 | MIX_ENV: dev 72 | - run: mix compile --warning-as-errors 73 | env: 74 | MIX_ENV: dev 75 | - uses: actions/upload-artifact@v1 76 | with: 77 | name: compile_dev 78 | path: _build/dev 79 | 80 | compile_docs: 81 | name: Compile Docs Environment 82 | 83 | runs-on: ubuntu-latest 84 | 85 | needs: ['deps'] 86 | 87 | steps: 88 | - uses: actions/checkout@v2 89 | - uses: actions/setup-elixir@v1 90 | with: 91 | otp-version: 22.3 92 | elixir-version: 1.10.3 93 | - uses: actions/download-artifact@v1 94 | with: 95 | name: deps 96 | path: deps 97 | - uses: actions/download-artifact@v1 98 | with: 99 | name: deps_lock 100 | path: . 101 | - uses: actions/cache@v2 102 | id: cache 103 | with: 104 | path: _build/docs 105 | key: compile_docs${{ runner.os }}-${{ github.ref }} 106 | restore-keys: | 107 | compile_docs-${{ runner.os }}- 108 | compile_docs- 109 | - run: mix deps.compile 110 | env: 111 | MIX_ENV: docs 112 | - run: mix compile --warning-as-errors 113 | env: 114 | MIX_ENV: docs 115 | - uses: actions/upload-artifact@v1 116 | with: 117 | name: compile_docs 118 | path: _build/docs 119 | 120 | compile_test: 121 | name: Compile Test Environment (OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}) 122 | 123 | runs-on: ubuntu-latest 124 | 125 | needs: ['deps'] 126 | 127 | strategy: 128 | fail-fast: false 129 | matrix: 130 | otp: [21.3, 22.3, 23.0] 131 | elixir: [1.8.2, 1.9.4, 1.10.3] 132 | 133 | steps: 134 | - uses: actions/checkout@v2 135 | - uses: actions/setup-elixir@v1 136 | with: 137 | otp-version: ${{matrix.otp}} 138 | elixir-version: ${{matrix.elixir}} 139 | - uses: actions/download-artifact@v1 140 | with: 141 | name: deps 142 | path: deps 143 | - uses: actions/download-artifact@v1 144 | with: 145 | name: deps_lock 146 | path: . 147 | - uses: actions/cache@v2 148 | id: cache 149 | with: 150 | path: _build/test 151 | key: compile_test-${{matrix.otp}}-${{matrix.elixir}}-${{ runner.os }}-${{ github.ref }} 152 | restore-keys: | 153 | compile_test-${{matrix.otp}}-${{matrix.elixir}}-${{ runner.os }}- 154 | compile_test-${{matrix.otp}}-${{matrix.elixir}}- 155 | - run: mix deps.compile 156 | env: 157 | MIX_ENV: test 158 | - run: mix compile --warning-as-errors 159 | env: 160 | MIX_ENV: test 161 | - uses: actions/upload-artifact@v1 162 | with: 163 | name: compile_test-${{matrix.otp}}-${{matrix.elixir}} 164 | path: _build/test 165 | 166 | compile_prod: 167 | name: Compile Prod Environment 168 | 169 | runs-on: ubuntu-latest 170 | 171 | needs: ['deps'] 172 | 173 | steps: 174 | - uses: actions/checkout@v2 175 | - uses: actions/setup-elixir@v1 176 | with: 177 | otp-version: 22.3 178 | elixir-version: 1.10.3 179 | - uses: actions/download-artifact@v1 180 | with: 181 | name: deps 182 | path: deps 183 | - uses: actions/download-artifact@v1 184 | with: 185 | name: deps_lock 186 | path: . 187 | - uses: actions/cache@v2 188 | id: cache 189 | with: 190 | path: _build/prod 191 | key: compile_prod-${{ runner.os }}-${{ github.ref }} 192 | restore-keys: | 193 | compile_prod-${{ runner.os }}- 194 | compile_prod- 195 | - run: mix deps.compile 196 | env: 197 | MIX_ENV: prod 198 | - run: mix compile --warning-as-errors 199 | env: 200 | MIX_ENV: prod 201 | - uses: actions/upload-artifact@v1 202 | with: 203 | name: compile_prod 204 | path: _build/prod 205 | 206 | format: 207 | name: Check Formatting 208 | 209 | runs-on: ubuntu-latest 210 | 211 | needs: ['deps'] 212 | 213 | steps: 214 | - uses: actions/checkout@v2 215 | - uses: actions/setup-elixir@v1 216 | with: 217 | otp-version: 22.3 218 | elixir-version: 1.10.3 219 | - uses: actions/download-artifact@v1 220 | with: 221 | name: deps 222 | path: deps 223 | - uses: actions/download-artifact@v1 224 | with: 225 | name: deps_lock 226 | path: . 227 | - run: mix format --check-formatted 228 | env: 229 | MIX_ENV: dev 230 | 231 | test: 232 | name: Run Tests & Submit Coverage (OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}) 233 | 234 | runs-on: ubuntu-latest 235 | 236 | strategy: 237 | fail-fast: false 238 | matrix: 239 | otp: [21.3, 22.3, 23.0] 240 | elixir: [1.8.2, 1.9.4, 1.10.3] 241 | 242 | needs: ['deps', 'compile_test'] 243 | 244 | steps: 245 | - uses: actions/checkout@v2 246 | - uses: actions/setup-elixir@v1 247 | with: 248 | otp-version: ${{matrix.otp}} 249 | elixir-version: ${{matrix.elixir}} 250 | - uses: actions/download-artifact@v1 251 | with: 252 | name: deps 253 | path: deps 254 | - uses: actions/download-artifact@v1 255 | with: 256 | name: deps_lock 257 | path: . 258 | - uses: actions/download-artifact@v1 259 | with: 260 | name: compile_test-${{matrix.otp}}-${{matrix.elixir}} 261 | path: _build/test 262 | - run: mix coveralls.github 263 | env: 264 | MIX_ENV: test 265 | 266 | credo: 267 | name: Check Credo 268 | 269 | runs-on: ubuntu-latest 270 | 271 | needs: ['deps', 'compile_dev'] 272 | 273 | steps: 274 | - uses: actions/checkout@v2 275 | - uses: actions/setup-elixir@v1 276 | with: 277 | otp-version: 22.3 278 | elixir-version: 1.10.3 279 | - uses: actions/download-artifact@v1 280 | with: 281 | name: deps 282 | path: deps 283 | - uses: actions/download-artifact@v1 284 | with: 285 | name: deps_lock 286 | path: . 287 | - uses: actions/download-artifact@v1 288 | with: 289 | name: compile_dev 290 | path: _build/dev 291 | - run: mix credo --strict 292 | env: 293 | MIX_ENV: dev 294 | 295 | dialyzer_plt: 296 | name: Generate Dialyzer PLT 297 | 298 | runs-on: ubuntu-latest 299 | 300 | needs: ['deps', 'compile_dev'] 301 | 302 | steps: 303 | - uses: actions/checkout@v2 304 | - uses: actions/setup-elixir@v1 305 | with: 306 | otp-version: 22.3 307 | elixir-version: 1.10.3 308 | - uses: actions/download-artifact@v1 309 | with: 310 | name: deps 311 | path: deps 312 | - uses: actions/download-artifact@v1 313 | with: 314 | name: deps_lock 315 | path: . 316 | - uses: actions/download-artifact@v1 317 | with: 318 | name: compile_dev 319 | path: _build/dev 320 | - uses: actions/cache@v2 321 | id: cache 322 | with: 323 | path: priv/plts/ 324 | key: dialyzer_plt_dev-${{ runner.os }}-${{ github.ref }} 325 | restore-keys: | 326 | dialyzer_plt_dev-${{ runner.os }}- 327 | dialyzer_plt_dev- 328 | - run: mix dialyzer --plt 329 | env: 330 | MIX_ENV: dev 331 | - uses: actions/upload-artifact@v1 332 | with: 333 | name: dialyzer_plt_dev 334 | path: priv/plts/ 335 | 336 | dialyzer_test: 337 | name: "Check Dialyzer" 338 | 339 | runs-on: ubuntu-latest 340 | 341 | needs: ['deps', 'compile_dev', 'dialyzer_plt'] 342 | 343 | steps: 344 | - uses: actions/checkout@v2 345 | - uses: actions/setup-elixir@v1 346 | with: 347 | otp-version: 22.3 348 | elixir-version: 1.10.3 349 | - uses: actions/download-artifact@v1 350 | with: 351 | name: deps 352 | path: deps 353 | - uses: actions/download-artifact@v1 354 | with: 355 | name: deps_lock 356 | path: . 357 | - uses: actions/download-artifact@v1 358 | with: 359 | name: compile_dev 360 | path: _build/dev 361 | - uses: actions/download-artifact@v1 362 | with: 363 | name: dialyzer_plt_dev 364 | path: priv/plts/ 365 | - run: mix dialyzer --halt-exit-status 366 | env: 367 | MIX_ENV: dev 368 | 369 | docs: 370 | name: "Generate Docs" 371 | 372 | runs-on: ubuntu-latest 373 | 374 | needs: ['deps', 'compile_docs'] 375 | 376 | steps: 377 | - uses: actions/checkout@v2 378 | - uses: actions/setup-elixir@v1 379 | with: 380 | otp-version: 22.3 381 | elixir-version: 1.10.3 382 | - uses: actions/download-artifact@v1 383 | with: 384 | name: deps 385 | path: deps 386 | - uses: actions/download-artifact@v1 387 | with: 388 | name: deps_lock 389 | path: . 390 | - uses: actions/download-artifact@v1 391 | with: 392 | name: compile_docs 393 | path: _build/docs 394 | - run: mix docs 395 | env: 396 | MIX_ENV: docs 397 | - uses: actions/upload-artifact@v1 398 | with: 399 | name: docs 400 | path: doc --------------------------------------------------------------------------------