defmodule Tempus.Sigils do
@moduledoc "Handy sigils to instantiate `Tempus.Slot` and `Tempus.Slots`"
alias Tempus.Slot
@doc ~S"""
Handles the sigil `~I` for `Tempus.Slot`.
It returns a slot without interpolations and without escape
characters, except for the escaping of the closing sigil character
itself.
_Allowed separators:_ `|`, `→`, `->`, `..` (might be surrounded by spaces)
_Allowed modifiers:_
- _none_ — expects two instances of `DateTime`
- _single letter_, one of `d`, `t`, `u` — both values are expected to be of that type
- _any combination_ of two letters `d`, `t`, `u` — the values are treated respectively
- `g` — the value types are to be guessed
## Examples
iex> import Tempus.Sigils
iex> ~I(2021-03-30T06:35:40Z|2021-03-30T06:36:00Z)
%Tempus.Slot{from: ~U[2021-03-30 06:35:40Z], to: ~U[2021-03-30 06:36:00Z]}
iex> ~I(2021-03-30 → 2021-03-31)d
%Tempus.Slot{from: ~U[2021-03-30 00:00:00.000000Z], to: ~U[2021-03-31 23:59:59.999999Z]}
iex> ~I(2021-03-30)d
%Tempus.Slot{from: ~U[2021-03-30 00:00:00.000000Z], to: ~U[2021-03-30 23:59:59.999999Z]}
iex> ~I(2021-03-30 06:35:40Z .. 2021-03-30 06:36:00Z)g
%Tempus.Slot{from: ~U[2021-03-30 06:35:40Z], to: ~U[2021-03-30 06:36:00Z]}
"""
defmacro sigil_I({:<<>>, _, [binary]}, 'g') when is_binary(binary) do
from_iso = List.duplicate(&Tempus.guess/1, 2)
do_ast(binary, from_iso, __CALLER__)
end
defmacro sigil_I({:<<>>, _, [binary]}, modifiers) when is_binary(binary) do
by_mod = fn
?d -> Date
?t -> Time
?u -> DateTime
end
from_iso =
modifiers
|> case do
[] -> [DateTime, DateTime]
[ft] when ft in 'dtu' -> List.duplicate(by_mod.(ft), 2)
[f, t] = mods when f in 'dtu' and t in 'dtu' -> Enum.map(mods, by_mod)
end
|> Enum.map(&Function.capture(&1, :from_iso8601, 1))
do_ast(binary, from_iso, __CALLER__)
end
defp do_ast(binary, from_iso, caller) do
result =
~r{\s*(?:\||→|\->|\.\.)\s*}
|> Regex.split(binary)
|> Enum.zip(from_iso)
|> Enum.map(fn {value, mapper} ->
case mapper.(value) do
{:ok, result} ->
Slot.wrap(result)
{:ok, result, _} ->
Slot.wrap(result)
{:error, error} ->
raise CompileError,
file: caller.file,
line: caller.line,
description: "`~I` sigil input is malformed, error: " <> inspect(error)
end
end)
|> Slot.join()
|> Macro.escape()
quote(generated: true, location: :keep, do: unquote(result))
end
end