lib/exonerate/formats/duration.ex

defmodule Exonerate.Formats.Duration do
  @moduledoc """
  Module which provides a macro that generates special code for a duration
  filter.

  This format is governed by appendix A of RFC 3339:
  https://www.rfc-editor.org/rfc/rfc3339.txt
  """

  alias Exonerate.Cache

  @doc """
  Creates a `NimbleParsec` parser `~duration/1`.

  This function returns `{:ok, ...}` if the passed string is a valid duration,
  or `{:error, reason, ...}` if it is not.  See `NimbleParsec` for more
  information on the return tuples.

  The function will only be created once per module, and it is safe to call
  the macro more than once.

  ## Options:
  - `:name` (atom): the name of the function to create.  Defaults to
    `:"~duration"`
  """
  defmacro filter(opts \\ []) do
    name = Keyword.get(opts, :name, :"~duration")

    if Cache.register_context(__CALLER__.module, name) do
      quote do
        require Pegasus
        import NimbleParsec

        Pegasus.parser_from_string("""
        DUR_DIGIT <- [0-9]
        DUR_DIGITS <- DUR_DIGIT+

        second <- DUR_DIGITS "S"
        minute <- DUR_DIGITS "M" second?
        hour   <- DUR_DIGITS "H" minute?
        time   <- "T" (hour / minute / second)
        day    <- DUR_DIGITS "D"
        week   <- DUR_DIGITS "W"
        month  <- DUR_DIGITS "M" day?
        year   <- DUR_DIGITS "Y" month?
        date   <- (day / month / year) time?

        duration   <- "P" (date / time / week)
        """)

        defparsec(unquote(name), parsec(:duration) |> eos)
      end
    end
  end
end