Packages

A helper library for Ecto, Nebulex and other utilties

Current section

Files

Jump to
helper lib helper enum.ex
Raw

lib/helper/enum.ex

# Copyright (c) 2021 Anand Panchapakesan
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
defmodule Helper.Enum do
@moduledoc """
This module is `use`d to create an Enum like so:
```elixir
use Helper.Enum, :name, :ready, :set, :go
```
It is heavily inspired (copied) from Ecto.Enum, but pruned to meet
our needs, which did not include Ecto integration
The option is a keyword list of name/value pairs
The name argument takes an atom or a tuple with the singular and plural values as atoms.
For values argument, it accepts a list of atoms
Below are examples of a valid argument:
```elixir
use Helper.Enum, name: :status, values: [:registered, :active, :inactive, :archived]
use Helper.Enum, name: [:state, :states], values: [registered: 0, active: 1, inactive: 2, archived: 3]
```
This helper provides functions for inspecting the enum at runtime.
`__valid_states__/0` - returns the valid values
`valid_state?/1` - returns boolean to indicate valid value
Enum also generates a typespec for use with dialyzer, available as `state()` type
"""
@spec __using__(keyword()) :: Macro.output()
@doc """
Define the enum.
"""
defmacro __using__(name: name, values: values) do
quote bind_quoted: [name: name, values: values] do
{s_name, p_name} = if is_tuple(name), do: name, else: {name, name}
unless is_atom(s_name) and is_atom(p_name),
do: raise(ArgumentError, "name should be an atom")
fun_valid_values = :"valid_#{p_name}"
fun_valid? = :"valid_#{s_name}?"
unless (is_list(values) and Enum.all?(values, &is_atom/1)) or
(Keyword.keyword?(values) and
(values |> Keyword.keys() |> Enum.all?(&is_atom/1) and
values |> Keyword.values() |> Enum.all?(&is_integer/1))),
do: raise(ArgumentError, "#{values}: atoms or atom/integer keyword list expected")
valid_values =
if Keyword.keyword?(values),
do: Keyword.keys(values) ++ Keyword.values(values),
else: values
typespec_values =
valid_values
|> Enum.reverse()
|> Enum.reduce(fn v, acc -> {:|, [], [v, acc]} end)
@type unquote(s_name)() :: unquote(typespec_values)
def unquote(fun_valid_values)(), do: unquote(Macro.escape(valid_values))
def unquote(fun_valid?)(value) when is_atom(value) or is_integer(value),
do: unquote(Macro.escape(valid_values)) |> Enum.member?(value)
def unquote(fun_valid?)(_), do: false
end
end
end