Current section
Files
Jump to
Current section
Files
lib/guardsafe.ex
defmodule Guardsafe do
@vsn "0.4.0"
@doc false
def version, do: @vsn
@moduledoc """
Provides readability-enhancing macros that can safely be used in guard clauses.
## Examples
defmodule MacrofyAllTheThings do
import Guardsafe
require Integer
def magic(number) when number |> nil? do
"Staring into the void..."
end
def magic(number) when number |> even? do
"That's not odd."
end
def magic(number) when number |> divisible_by? 5 do
"High five!"
end
end
iex> MacrofyAllTheThings.magic(8)
"That's not odd."
"""
@doc """
Returns true if the integer number is evenly divisible by the divisor.
## Examples
iex> 25 |> divisible_by? 5
true
"""
defmacro divisible_by?(number, divisor) do
quote do
rem(unquote(number), unquote(divisor)) == 0
end
end
@doc """
Returns true for even integers.
## Examples
iex> even? 7
false
"""
defmacro even?(number) do
quote do
divisible_by?(unquote(number), 2)
end
end
@doc """
Returns true for odd integers.
## Examples
iex> odd? 5
true
"""
defmacro odd?(number) do
quote do
not even?(unquote(number))
end
end
require_warning = fn(module) ->
unless module == Kernel, do: "\n\nRemember to `require #{module}` before use."
end
generate_predicates = fn({module, types}) ->
@module String.to_atom "Elixir.#{module}"
types
|> Enum.each fn(type) ->
@predicate String.to_atom("#{type}?")
@function String.to_atom("is_#{type}")
@doc """
Expands `#{@predicate}(term)` into `#{module}.#{@function}(term)`.
#{require_warning.(@module)}
## Examples
iex> some_#{type}_variable |> #{@predicate}
true
"""
defmacro unquote(@predicate)(term) do
quote do
unquote(@module).unquote(@function)(unquote(term))
end
end
end
end
@doc """
Returns true if the term is between the low and high values, inclusively.
## Examples
iex> 5 |> within? 2, 10
true
"""
defmacro within?(term, low, high) do
quote do
unquote(low) <= unquote(term) and unquote(term) <= unquote(high)
end
end
@doc """
Returns true if the term is considered to be a date tuple.
The date is not checked for validity other than the months being in
the 1..12 range and the days being in the 1..31 range.
## Examples
iex> 5 |> date? {2015, 3, 20}
true
"""
defmacro date?(term) do
quote do
is_tuple(unquote(term))
and (tuple_size(unquote(term)) == 3)
and is_integer(elem(unquote(term), 0))
and is_integer(elem(unquote(term), 1))
and (elem(unquote(term), 1) |> within? 1, 12)
and is_integer(elem(unquote(term), 2))
and (elem(unquote(term), 2) |> within? 1, 31)
end
end
@doc """
Returns true if the term is considered to be a time tuple.
The time is not checked for validity other than the hours being in
the 0..23 range and the minutes and seconds being in the 0..59 range.
## Examples
iex> 5 |> time? {15, 33, 42}
true
"""
defmacro time?(term) do
quote do
is_tuple(unquote(term))
and (tuple_size(unquote(term)) == 3)
and is_integer(elem(unquote(term), 0))
and (elem(unquote(term), 0) |> within? 0, 23)
and is_integer(elem(unquote(term), 1))
and (elem(unquote(term), 1) |> within? 0, 59)
and is_integer(elem(unquote(term), 2))
and (elem(unquote(term), 2) |> within? 0, 59)
end
end
@doc """
Returns true if the term is considered to be a time tuple.
`date?` and `time?` are used for checking and validating the
date and time portions of the datetime.
## Examples
iex> datetime? {{2015, 3, 20}, {15, 33, 42}}
true
"""
defmacro datetime?(term) do
quote do
is_tuple(unquote(term))
and (tuple_size(unquote(term)) == 2)
and date?(elem(unquote(term), 0))
and time?(elem(unquote(term), 1))
end
end
[
Kernel: ~w(atom binary bitstring boolean float function integer list map nil number pid port reference tuple),
]
|> Enum.each generate_predicates
@doc """
Expands `function?(term, arity)` into `is_function(term, arity)`
## Examples
iex> &String.to_integer/1 |> function?(2)
false
"""
defmacro function?(term, arity) do
quote do
is_function(unquote(term), unquote(arity))
end
end
end