Skip to content

Commit 6422b46

Browse files
Accept function as custom_filters (#182)
1 parent 24a8f82 commit 6422b46

File tree

4 files changed

+63
-7
lines changed

4 files changed

+63
-7
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,31 @@ end
9696
# 42
9797
```
9898

99+
Alternatively, you can pass a function to the `custom_filters` option. This allows
100+
your filters to access predefined state. An example use-case could be to allow the
101+
filters to render strings in the user's default locale (or to override it by
102+
argument).
103+
104+
``` elixir
105+
user_locale = "en"
106+
107+
"{{ number | format_number }}"
108+
|> Solid.parse!()
109+
|> Solid.render!(%{ "number" => 41}, custom_filters: fn
110+
"format_number", [num] ->
111+
{:ok, Cldr.Number.to_string(num, locale: user_locale)}
112+
113+
"format_number", [num, locale] ->
114+
{:ok, Cldr.Number.to_string(num, locale: locale)}
115+
116+
_, _ ->
117+
:error
118+
end)
119+
|> IO.puts()
120+
```
121+
122+
The callback must return either `{:ok, value}` or `:error`.
123+
99124
## Strict rendering
100125

101126
If there are any missing variables/filters and `strict_variables: true` or `strict_filters: true` are passed as options `Solid.render/3` returns `{:error, errors, result}` where errors is the list of collected errors and `result` is the rendered template.

lib/solid.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ defmodule Solid do
129129
130130
- `file_system`: a tuple of {FileSystemModule, options}. If this option is not specified, `Solid` uses `Solid.BlankFileSystem` which returns an error when the `render` tag is used. `Solid.LocalFileSystem` can be used or a custom module may be implemented. See `Solid.FileSystem` for more details.
131131
132-
- `custom_filters`: a module name where additional filters are defined. The base filters (those from `Solid.StandardFilter`) still can be used, however, custom filters always take precedence.
132+
- `custom_filters`: a module name where additional filters are defined. The base filters (those from `Solid.StandardFilter`) still can be used, however, custom filters always take precedence. May also be a function that receives the filter name and the arguments and must return `{:ok, term} | :error`.
133133
134134
- `strict_variables`: if `true`, it collects an error when a variable is referenced in the template, but not given in the map
135135

lib/solid/standard_filter.ex

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ defmodule Solid.StandardFilter do
1010
@spec apply(String.t(), list(), Solid.Parser.Loc.t(), keyword()) ::
1111
{:ok, any()} | {:error, Exception.t(), any()} | {:error, Exception.t()}
1212
def apply(filter, args, loc, opts) do
13-
custom_module =
13+
custom_module_or_callback =
1414
opts[:custom_filters] || Application.get_env(:solid, :custom_filters, __MODULE__)
1515

1616
strict_filters = Keyword.get(opts, :strict_filters, false)
1717

18-
with :error <- apply_filter(custom_module, filter, args, loc),
18+
with :error <- apply_filter(custom_module_or_callback, filter, args, loc),
1919
:error <- apply_filter(__MODULE__, filter, args, loc) do
2020
if strict_filters do
2121
{:error, %Solid.UndefinedFilterError{loc: loc, filter: filter}, List.first(args)}
@@ -43,9 +43,13 @@ defmodule Solid.StandardFilter do
4343
end
4444
end
4545

46-
defp apply_filter(mod, func, args, loc) do
47-
func = String.to_existing_atom(func)
48-
{:ok, Kernel.apply(mod, func, args)}
46+
defp apply_filter(mod_or_callback, func, args, loc) do
47+
if is_function(mod_or_callback, 2) do
48+
mod_or_callback.(func, args)
49+
else
50+
func = String.to_existing_atom(func)
51+
{:ok, Kernel.apply(mod_or_callback, func, args)}
52+
end
4953
rescue
5054
# Unknown function name atom or unknown function -> fallback
5155
ArgumentError ->
@@ -56,7 +60,7 @@ defmodule Solid.StandardFilter do
5660
{:error, %{e | loc: loc}}
5761

5862
UndefinedFunctionError ->
59-
find_correct_function(mod, String.to_existing_atom(func), Enum.count(args), loc)
63+
find_correct_function(mod_or_callback, String.to_existing_atom(func), Enum.count(args), loc)
6064
end
6165

6266
@doc """

test/solid/standard_filter_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,31 @@ defmodule Solid.StandardFilterTest do
4545
}
4646
end
4747
end
48+
49+
test "it applies a function as custom filter" do
50+
user_locale = "en"
51+
52+
custom_filters = fn
53+
"format_number", [num] ->
54+
{:ok, "#{user_locale}: #{num}"}
55+
56+
"format_number", [num, locale] ->
57+
{:ok, "#{locale}: #{num}"}
58+
59+
_, _ ->
60+
:error
61+
end
62+
63+
assert "en: 41" ==
64+
"{{ number | format_number }}"
65+
|> Solid.parse!()
66+
|> Solid.render!(%{"number" => 41}, custom_filters: custom_filters)
67+
|> to_string()
68+
69+
assert "fr: 41" ==
70+
"{{ number | format_number: 'fr' }}"
71+
|> Solid.parse!()
72+
|> Solid.render!(%{"number" => 41}, custom_filters: custom_filters)
73+
|> to_string()
74+
end
4875
end

0 commit comments

Comments
 (0)