View Source Alembic.Filters (alembic v0.1.0)
Built-in, Liquid-compatible filter library. Zero runtime dependencies — everything below uses only Elixir/OTP stdlib.
Two intentional deviations from a literal reading of issue 1.4.3, both for correctness against real Liquid semantics:
url_encode/url_decodeuseURI.encode_www_form/1/URI.decode_www_form/1(space becomes+), notURI.encode/1/URI.decode/1— the latter percent-encodes spaces as%20and doesn't match Liquid's actualurl_encodeoutput.ceil/floorreturn integers (trunc(Float.ceil(x))), matching Liquid, rather thanFloat.ceil/1's own float return value.
join with no argument defaults to "" (empty separator) per issue
1.4.3's explicit instruction, not Liquid's own default of " " — flagged
here since it is a real behavioral divergence from upstream Liquid.
Type coercion reference
Every filter clause below is a private apply_builtin/3 head, so none of
them can carry their own @doc (the compiler warns — @doc has no effect
on a private function). This table is the substitute: it's the single
place documenting what each filter does when handed a value of the
"wrong" type, instead of that behavior being scattered implicitly across
56 private function clauses.
Coerces to a string (coerce_to_string/1)
upcase, downcase, capitalize, strip, lstrip, rstrip,
strip_newlines, prepend, append, replace, replace_first,
remove, remove_first, split, truncate, truncatewords,
newline_to_br, escape, escape_once, strip_html, url_encode,
url_decode, base64_encode, base64_decode, sort_natural (for
comparison only — the original items are returned), join's separator
argument (not its items). nil coerces to ""; numbers and booleans
coerce via Integer.to_string/1 / Float.to_string/1 / to_string/1;
anything else falls back to inspect/1 rather than raising.
slice also coerces its input via coerce_to_string/1 — unlike upstream
Liquid, it does not support slicing arrays, only strings (see
COMPATIBILITY.md).
Coerces to a number (coerce_to_number/1)
abs, ceil, floor, round, plus, minus, times, divided_by,
modulo, at_least, at_most. nil coerces to 0, true/false to
1/0, and a numeric string parses to an integer or float; a
non-numeric string or any other type coerces to 0 rather than raising.
No coercion — requires an exact type
size— matches only onis_binary/is_listguards; any other input falls through to the catch-all clause and returns{:error, {:invalid_filter_args, "size", args}}, not a crash.first,last,reverse,sort,uniq,compact,map,where,concat,push,pop,unshift,shift,flatten— all expect a list; a non-list input raises (FunctionClauseErrororProtocol.UndefinedError), the same as calling the underlyingList/Enumfunction directly with that value.date— accepts aDate/DateTime/NaiveDateTimestruct or an ISO 8601 string; anything else returns{:error, {:invalid_date, value}}, not a crash.default— no coercion; blankness is an exact-value check againstnil,false,"", and[](seeblank?/1), not a truthiness rule.inspect— accepts any term as-is viaKernel.inspect/1; there is nothing to coerce.
Summary
Functions
Applies a single named filter. Checks custom_filters first (matched by
Alembic.Filter.name/0), then falls back to the built-in catalog.
Applies a full filter chain in order, the output of each filter feeding
the next. Short-circuits on the first error. ctx.custom_filters (set
via Alembic.Context.custom_filters/2) is consulted for every filter in
the chain, ahead of the global config :alembic, :custom_filters list.
Types
Functions
Applies a single named filter. Checks custom_filters first (matched by
Alembic.Filter.name/0), then falls back to the built-in catalog.
custom_filters defaults to []; pass modules here for a per-call
override (Alembic.render/3's :custom_filters option) — they're tried
before config :alembic, :custom_filters and take precedence on a name
collision.
Examples
iex> Alembic.Filters.apply("upcase", "hello", [])
{:ok, "HELLO"}
iex> Alembic.Filters.apply("truncate", "hello world", [5])
{:ok, "he..."}
iex> Alembic.Filters.apply("nope", "x", [])
{:error, {:unknown_filter, "nope"}}
@spec apply_chain(any(), [{String.t(), [any()]}], Alembic.Context.t() | nil) :: {:ok, any()} | {:error, reason()}
Applies a full filter chain in order, the output of each filter feeding
the next. Short-circuits on the first error. ctx.custom_filters (set
via Alembic.Context.custom_filters/2) is consulted for every filter in
the chain, ahead of the global config :alembic, :custom_filters list.
Examples
iex> Alembic.Filters.apply_chain("hello", [{"upcase", []}, {"truncate", [3, ""]}], nil)
{:ok, "HEL"}
iex> Alembic.Filters.apply_chain("hello", [], nil)
{:ok, "hello"}