Packages

An enum type without ecto compatibility

Current section

Files

Jump to
enum_helper lib enum_helper.ex
Raw

lib/enum_helper.ex

# Copyright (c) 2021 Anand Panchapakesan
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
defmodule EnumHelper 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 use statement takes a keyword list as arguments. The options include
1. name : either an atom as a singular name or a tuple with singular and plural names. If only a singular name is given, then it is used for both singular and plural
2. values : a list of valid values for the enum. This can either be a list of atoms or a keyword list of atom and integer
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_<plural_name>/0` - returns a list of valid values (either atoms or atoms and integers)
`valid_<singular_name>?/1` - returns boolean indicating whether the supplied value is valid
Enum also generates a typespec for use with dialyzer, available as `<singular_name>()` type
"""
import Keyword, only: [keys: 1, values: 1, keyword?: 1]
@doc """
Define the enum.
"""
@spec __using__(keyword()) :: Macro.output()
defmacro __using__(name: name, values: values) do
{s_name, p_name} = if is_tuple(name), do: name, else: {name, name}
# coveralls-ignore-start
unless is_atom(s_name) and is_atom(p_name),
do: raise(CompileError, description: "name should be an atom")
unless (is_list(values) and Enum.all?(values, &is_atom/1)) or
(keyword?(values) and
(values |> keys() |> Enum.all?(&is_atom/1) and
values |> values() |> Enum.all?(&is_integer/1))),
do:
raise(CompileError,
description: "#{values}: atoms or atom/integer keyword list expected"
)
# coveralls-ignore-stop
value_list = if(keyword?(values), do: keys(values) ++ values(values), else: values)
typespec =
value_list
|> Enum.reverse()
|> Enum.reduce(&{:|, [], [&1, &2]})
|> Macro.escape()
fun_valid_values = :"valid_#{p_name}"
fun_valid? = :"valid_#{s_name}?"
quote bind_quoted: binding() do
@type unquote(s_name)() :: unquote(typespec)
@spec unquote(fun_valid_values)() :: list()
def unquote(fun_valid_values)(), do: unquote(value_list)
@spec unquote(fun_valid?)(unquote(s_name)()) :: boolean()
def unquote(fun_valid?)(value)
when is_atom(value) or is_integer(value),
do: Enum.member?(unquote(value_list), value)
def unquote(fun_valid?)(_), do: false
end
end
end