Packages
A declarative text parsing library for Elixir
Current section
Files
Jump to
Current section
Files
lib/component.ex
defmodule Parselet.Component do
@moduledoc """
Defines the behaviour and DSL for Parselet components.
Components can define fields with `field/2` and can optionally provide a
`preprocess/1` hook to normalize or transform the raw text before field-level
parsing is performed. Components can also define a `postprocess/1` hook that
runs after all fields are extracted. Each component module also gains `parse/2`
and `parse!/2` convenience functions to parse input directly into the
corresponding struct.
"""
defmacro __using__(_opts) do
quote do
import Parselet.Component
Module.register_attribute(__MODULE__, :parselet_fields, accumulate: true)
@before_compile Parselet.Component
end
end
defmacro field(name, opts) do
quote do
@parselet_fields {unquote(name), unquote(Macro.escape(opts))}
end
end
@doc """
Define a preprocessing function for the component.
The supplied function is invoked once with the raw text and the returned text
is used for all subsequent field extraction within that component.
You can pass the function directly:
preprocess fn text ->
String.upcase(text)
end
or via shorthand function capture:
preprocess &String.upcase/1
"""
defmacro preprocess(opts) do
quote do
@parselet_preprocess unquote(Macro.escape(opts))
end
end
@doc """
Define a postprocessing function for the component.
After all fields have been extracted, this function is invoked once with the
map of parsed values. It must return either `:ok` or a map. When a map is
returned, its contents are merged into the parsed result.
You can pass the function directly:
postprocess &add_metadata/1
or via keyword syntax:
postprocess function: &add_metadata/1
"""
defmacro postprocess(opts) do
quote do
@parselet_postprocess unquote(Macro.escape(opts))
end
end
defmacro __before_compile__(env) do
fields =
Module.get_attribute(env.module, :parselet_fields)
|> Enum.map(fn {name, opts} ->
quote do
{unquote(name), Parselet.Field.new(unquote(name), unquote(opts))}
end
end)
preprocess =
case Module.get_attribute(env.module, :parselet_preprocess) do
nil -> nil
opts when is_list(opts) ->
raise ArgumentError,
"preprocess keyword syntax is no longer supported; use preprocess &fun/1 or preprocess fn text -> ... end"
opts -> opts
end
postprocess =
case Module.get_attribute(env.module, :parselet_postprocess) do
nil -> nil
opts when is_list(opts) ->
if Keyword.has_key?(opts, :function) do
Keyword.fetch!(opts, :function)
else
opts
end
opts -> opts
end
quote do
def __parselet_fields__ do
%{
unquote_splicing(fields)
}
end
def __parselet_preprocess__ do
unquote(preprocess)
end
def __parselet_postprocess__ do
unquote(postprocess)
end
def parse(text, opts \\ []) do
Parselet.parse(text, Keyword.put(opts, :structs, [__MODULE__]))
end
def parse!(text, opts \\ []) do
Parselet.parse!(text, Keyword.put(opts, :structs, [__MODULE__]))
end
end
end
end