Current section
Files
Jump to
Current section
Files
lib/numa.ex
defmodule Numa do
@moduledoc """
Provides enum-like macros for defining a set of named constants with associated values.
This module allows you to easily define enums where each enum entry is represented as a macro
that returns a fixed value. It supports a mixed list of values and keyword pairs for flexible usage.
## Features
- Automatically creates macros named after enum keys (with a leading underscore).
- Supports keys that are atoms, strings, or integers.
- Supports values that can differ from the macro names.
- Provides helper functions to retrieve all enum keys, values, or key-value pairs.
- Validates inputs and raises clear errors on invalid types.
## Usage
### 1. Simple list of values
When enum names and values are identical, provide a list of values (strings, atoms, or integers):
use Numa, ["FULL", "1M"]
This generates macros:
defmacro _FULL(), do: "FULL"
defmacro _1M(), do: "1M" # Macro names are always prefixed with `_` to allow digits and avoid conflicts.
### 2. Keyword list with explicit keys and values
When macro names should differ from values, pass a keyword list:
use Numa, [M3: "3m", MINI: "MINI"]
This generates macros:
defmacro _M3(), do: "3m"
defmacro _MINI(), do: "MINI"
### 3. Using a function returning a list
You can also pass a zero-arity function that returns the enum list dynamically:
use Numa, fn -> ["A", "B", "C"] end
## Generated helper functions
- `values/0` — returns a list of all enum values.
- `keys/0` — returns a list of all macro names (as atoms).
- `all/0` — returns a list of `{key, value}` tuples representing all enums.
## Examples
iex> defmodule MyEnums do
...> use Numa, ["FULL", "1M", M3: "3m", mini: "MINI"]
...> end
iex> MyEnums._FULL()
"FULL"
iex> MyEnums._1M()
"1M"
iex> MyEnums._M3()
"3m"
iex> MyEnums._mini()
"MINI"
iex> MyEnums.values()
["FULL", "1M", "3m", "MINI"]
iex> MyEnums.keys()
[:_FULL, :_1M, :_M3, :_mini]
iex> MyEnums.all()
[
{:_FULL, "FULL"},
{:_1M, "1M"},
{:_M3, "3m"},
{:_mini, "MINI"}
]
## Error handling
Raises `ArgumentError` if the provided list contains unsupported types:
iex> defmodule A do use Numa, [%{}] end
** (ArgumentError) Invalid Numa list value: %{}, expected type: atom, binary or integer
## Notes
- Macro names are always prefixed with `_` to avoid conflicts and allow starting names with digits.
- Keys in keyword lists must be atoms.
- Values can be atoms, strings, or integers.
"""
defmacro __using__(values) do
quote bind_quoted: [values: values] do
underscore_string = fn
v when is_binary(v) -> String.to_atom("_" <> v)
end
underscore = fn
v when is_binary(v) -> v |> underscore_string.()
v when is_atom(v) -> v |> Atom.to_string() |> underscore_string.()
v when is_integer(v) -> v |> Integer.to_string() |> underscore_string.()
end
values = if is_function(values), do: values.(), else: values
kv =
if is_list(values) do
Enum.map(values, fn
{k, v} when is_atom(k) ->
{underscore.(k), v}
{k, _} ->
raise ArgumentError,
"Invalid Numa keyword: #{inspect(k)}, expected type: atom"
v when is_atom(v) or is_binary(v) or is_integer(v) ->
{underscore.(v), v}
v ->
raise ArgumentError,
"Invalid Numa list value: #{inspect(v)}, expected type: atom, binary or integer"
end)
else
raise ArgumentError, "use Numa expects a list or keyword list"
end
for {key, val} <- kv do
def unquote(key)(), do: unquote(val)
end
def values(), do: unquote(Enum.map(kv, &elem(&1, 1)))
def keys(), do: unquote(Enum.map(kv, &elem(&1, 0)))
def all(), do: unquote(kv)
end
end
end