Skip to content

Commit

Permalink
chore: prepare for v0.5.0 release (#13)
Browse files Browse the repository at this point in the history
* chore: prepare for v0.5.0 release

* docs: update docs

Also:
- Added dialyzer CI Job

* test: full test coverage

* test: minor test tweak

* feat: raise if invalid pipeline is defined
  • Loading branch information
hpopp authored Sep 8, 2024
1 parent 8f7aec5 commit f8a00bc
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 18 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ jobs:
run: mix deps.get
- name: Run formatter
run: mix format --check-formatted
dialyzer:
name: Dialyzer
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: "1.17.2"
otp-version: "27.0.1"
- name: Restore dependencies cache
uses: actions/cache@v3
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- name: Install dependencies
run: mix deps.get
- name: Run dialyzer
run: mix dialyzer
test:
name: Test
runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,11 @@ erl_crash.dump
# Ignore package tarball (built via "mix hex.build").
commandex-*.tar

# Ignore dialyzer files.
/priv/plts/*.plt
/priv/plts/*.plt.hash

# Misc
.DS_Store
.iex.exs
*.swp
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
## [0.5.0] - 2024-09-08

### Added

- New `run/0` function for commands that don't define any parameters.
- `run/0` function for commands that don't define any parameters.

### Changed

- Raise `ArgumentError` if an invalid `pipeline` is defined.

## [0.4.1] - 2020-06-26

Expand Down
41 changes: 38 additions & 3 deletions lib/commandex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Commandex do
@moduledoc """
Defines a command struct.
Commandex structs are a loose implementation of the command pattern, making it easy
Commandex is a loose implementation of the command pattern, making it easy
to wrap parameters, data, and errors into a well-defined struct.
## Example
Expand Down Expand Up @@ -70,7 +70,7 @@ defmodule Commandex do
or map with atom/string keys.
`&run/1` takes a command struct and runs it through the pipeline functions defined
in the command. Functions are executed *in the order in which they are defined*.
in the command. **Functions are executed in the order in which they are defined**.
If a command passes through all pipelines without calling `halt/1`, `:success`
will be set to `true`. Otherwise, subsequent pipelines after the `halt/1` will
be ignored and `:success` will be set to `false`.
Expand All @@ -88,6 +88,21 @@ defmodule Commandex do
%{success: false, errors: _error} ->
# I'm a lazy programmer that writes catch-all error handling
end
## Parameter-less Commands
If a command does not have any parameters defined, a `run/0` will be generated
automatically. Useful for diagnostic jobs and internal tasks.
iex> GenerateReport.run()
%GenerateReport{
pipelines: [:fetch_data, :calculate_results],
data: %{total_valid: 183220, total_invalid: 781215},
params: %{},
halted: false,
errors: %{},
success: true
}
"""

@typedoc """
Expand Down Expand Up @@ -418,10 +433,30 @@ defmodule Commandex do
Module.put_attribute(mod, :data, {name, nil})
end

def __pipeline__(mod, name) do
def __pipeline__(mod, name) when is_atom(name) do
Module.put_attribute(mod, :pipelines, name)
end

def __pipeline__(mod, fun) when is_function(fun, 1) do
Module.put_attribute(mod, :pipelines, fun)
end

def __pipeline__(mod, fun) when is_function(fun, 3) do
Module.put_attribute(mod, :pipelines, fun)
end

def __pipeline__(mod, {m, f}) do
Module.put_attribute(mod, :pipelines, {m, f})
end

def __pipeline__(mod, {m, f, a}) do
Module.put_attribute(mod, :pipelines, {m, f, a})
end

def __pipeline__(_mod, name) do
raise ArgumentError, "pipeline #{inspect(name)} is not valid"
end

defp get_param(params, key, default) do
case Map.get(params, key) do
nil ->
Expand Down
21 changes: 20 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,42 @@ defmodule Commandex.MixProject do
use Mix.Project

@source_url "https://github.com/codedge-llc/commandex"
@version "0.4.1"
@version "0.5.0"

def project do
[
app: :commandex,
deps: deps(),
dialyzer: dialyzer(),
docs: docs(),
elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
name: "Commandex",
package: package(),
source_url: "https://github.com/codedge-llc/commandex",
start_permanent: Mix.env() == :prod,
test_coverage: test_coverage(),
version: @version
]
end

defp deps do
[
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0", only: [:dev], runtime: false},
{:ex_doc, "~> 0.31", only: :dev}
]
end

defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]

defp dialyzer do
[
plt_file: {:no_warn, "priv/plts/dialyzer.plt"}
]
end

defp docs do
[
extras: [
Expand Down Expand Up @@ -57,6 +66,16 @@ defmodule Commandex.MixProject do
]
end

defp test_coverage do
[
ignore_modules: [
GenerateReport,
RegisterUser
],
summary: [threshold: 70]
]
end

def application do
[
extra_applications: [:logger]
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [: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", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"},
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
"excoveralls": {:hex, :excoveralls, "0.12.1", "a553c59f6850d0aff3770e4729515762ba7c8e41eedde03208182a8dc9d0ce07", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
Expand Down
94 changes: 90 additions & 4 deletions test/commandex_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
defmodule CommandexTest do
use ExUnit.Case
doctest Commandex
alias Commandex.RegisterUser

@email "[email protected]"
@password "test1234"
Expand Down Expand Up @@ -58,18 +57,105 @@ defmodule CommandexTest do
end
end

describe "param/2 macro" do
test "raises if duplicate defined" do
assert_raise ArgumentError, fn ->
defmodule ExampleParamInvalid do
import Commandex

command do
param :key_1
param :key_2
param :key_1
end
end
end
end
end

describe "data/1 macro" do
test "raises if duplicate defined" do
assert_raise ArgumentError, fn ->
defmodule ExampleDataInvalid do
import Commandex

command do
data :key_1
data :key_2
data :key_1
end
end
end
end
end

describe "pipeline/1 macro" do
test "accepts valid pipeline arguments" do
try do
defmodule ExamplePipelineValid do
import Commandex

command do
pipeline :example
pipeline {ExamplePipelineValid, :example}
pipeline {ExamplePipelineValid, :example_args, ["test"]}
pipeline &ExamplePipelineValid.example_single/1
pipeline &ExamplePipelineValid.example/3
end

def example(command, _params, _data) do
command
end

def example_single(command) do
command
end

def example_args(command, _params, _data, _custom_value) do
command
end
end

ExamplePipelineValid.run()
rescue
FunctionClauseError -> flunk("Should not raise.")
end
end

test "raises if invalid argument defined" do
assert_raise ArgumentError, fn ->
defmodule ExamplePipelineInvalid do
import Commandex

command do
pipeline 1234
end
end
end
end
end

describe "halt/1" do
test "ignores remaining pipelines" do
command = RegisterUser.run(%{agree_tos: false})

refute command.success
assert command.errors === %{tos: :not_accepted}
end
end

describe "run/0" do
test "is defined if no params are defined" do
assert Kernel.function_exported?(Commandex.GenerateReport, :run, 0)
assert Kernel.function_exported?(GenerateReport, :run, 0)

command = Commandex.GenerateReport.run()
command = GenerateReport.run()
assert command.success
assert command.data.total_valid > 0
assert command.data.total_invalid > 0
end

test "is not defined if params are defined" do
refute Kernel.function_exported?(Commandex.RegisterUser, :run, 0)
refute Kernel.function_exported?(RegisterUser, :run, 0)
end
end

Expand Down
15 changes: 8 additions & 7 deletions test/support/generate_report.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Commandex.GenerateReport do
defmodule GenerateReport do
@moduledoc """
Example command that generates fake data.
Expand All @@ -11,17 +11,18 @@ defmodule Commandex.GenerateReport do
data :total_valid
data :total_invalid

pipeline :calculate_valid
pipeline :calculate_invalid
pipeline :fetch_data
pipeline :calculate_results
end

def calculate_valid(command, _params, _data) do
def fetch_data(command, _params, _data) do
# Not real.
command
|> put_data(:total_valid, :rand.uniform(1_000_000))
end

def calculate_invalid(command, _params, _data) do
def calculate_results(command, _params, _data) do
command
|> put_data(:total_invalid, :rand.uniform(1_000_000))
|> put_data(:total_valid, 183_220)
|> put_data(:total_invalid, 781_215)
end
end
2 changes: 1 addition & 1 deletion test/support/register_user.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Commandex.RegisterUser do
defmodule RegisterUser do
@moduledoc """
Example command that registers a user.
"""
Expand Down

0 comments on commit f8a00bc

Please sign in to comment.