From dc57221bc99e165530134559097b27b1dfe95dbe Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Mon, 12 Jun 2023 23:38:16 -0400 Subject: [PATCH] feat!: configure runtime attributes function (#202) --- guides/getting-started.md | 7 +++++-- lib/temple.ex | 38 +++++++++++++++++++++++++++------- lib/temple/ast/utils.ex | 18 +++++++++++++--- lib/temple/component.ex | 2 +- test/support/helpers.ex | 25 +++++++++++++++------- test/temple/ast/utils_test.exs | 4 ++-- test/temple_test.exs | 8 ++++++- 7 files changed, 78 insertions(+), 24 deletions(-) diff --git a/guides/getting-started.md b/guides/getting-started.md index a5b6022..b35f128 100644 --- a/guides/getting-started.md +++ b/guides/getting-started.md @@ -42,13 +42,16 @@ Temple works out of the box without any configuration, but here are a couple of ### Engine -By default, Temple uses the built in `EEx.SmartEngine`. If you want to use a different engine, this is as easy as setting the `:engine` configuration option. +By default, Temple uses the built in `Phoenix.HTML.Engine`. If you want to use a different engine, this is as easy as setting the `:engine` configuration option. + +You can also configure the function that is used for runtime attributes. By default, Temple uses `Phoenix.HTML.attributes_escape/1`. ```elixir # config/config.exs config :temple, - engine: Phoenix.HTML.Engine + engine: EEx.SmartEngine, + attributes: {Temple, :attributes} ``` ### Aliases diff --git a/lib/temple.ex b/lib/temple.ex index d7a2b40..a796bbf 100644 --- a/lib/temple.ex +++ b/lib/temple.ex @@ -96,16 +96,40 @@ defmodule Temple do require Temple.Renderer Temple.Renderer.compile(unquote(block)) - |> then(fn - {:safe, template} -> - template - - template -> - template - end) end end @doc false defdelegate engine, to: Temple.Renderer + + @doc """ + Compiles runtime attributes. + + To use this function, you set it in application config. + + By default, Temple uses `{Phoenix.HTML, :attributes_escape}`. This is useful if you want to use `EEx.SmartEngine`. + + ```elixir + config :temple, + engine: EEx.SmartEngine, + attributes: {Temple, :attributes} + ``` + + > #### Note {: .info} + > + > This function does not do any HTML escaping + + > #### Note {: .info} + > + > This function is used by the compiler and shouldn't need to be used directly. + """ + def attributes(attributes) do + for {key, value} <- attributes, into: "" do + case value do + true -> ~s| #{key}| + false -> "" + value -> ~s| #{key}="#{value}"| + end + end + end end diff --git a/lib/temple/ast/utils.ex b/lib/temple/ast/utils.ex index f9163e5..dbb7c1b 100644 --- a/lib/temple/ast/utils.ex +++ b/lib/temple/ast/utils.ex @@ -1,6 +1,12 @@ defmodule Temple.Ast.Utils do @moduledoc false + @attributes Application.compile_env( + :temple, + :attributes, + {Phoenix.HTML, :attributes_escape} + ) + def snake_to_kebab(stringable), do: stringable |> to_string() |> String.replace_trailing("_", "") |> String.replace("_", "-") @@ -34,7 +40,7 @@ defmodule Temple.Ast.Utils do [ {:expr, quote do - Phoenix.HTML.attributes_escape(unquote(List.first(attrs))) + unquote(__MODULE__).__attributes__(unquote(List.first(attrs))) end} ] end @@ -57,7 +63,7 @@ defmodule Temple.Ast.Utils do def build_attr("rest!", {_, _, _} = value) do expr = quote do - Phoenix.HTML.attributes_escape(unquote(value)) + unquote(__MODULE__).__attributes__(unquote(value)) end [{:expr, expr}] @@ -66,7 +72,7 @@ defmodule Temple.Ast.Utils do def build_attr(name, {_, _, _} = value) do expr = quote do - Phoenix.HTML.attributes_escape([{unquote(name), unquote(value)}]) + unquote(__MODULE__).__attributes__([{unquote(name), unquote(value)}]) end [{:expr, expr}] @@ -154,4 +160,10 @@ defmodule Temple.Ast.Utils do ast end + + def __attributes__(attributes) do + {mod, func} = @attributes + + apply(mod, func, [attributes]) + end end diff --git a/lib/temple/component.ex b/lib/temple/component.ex index 5b1c358..30410c9 100644 --- a/lib/temple/component.ex +++ b/lib/temple/component.ex @@ -55,7 +55,7 @@ defmodule Temple.Component do import Temple @doc false def component(func, assigns, _) do - {:safe, apply(func, [assigns])} + apply(func, [assigns]) end defmacro inner_block(_name, do: do_block) do diff --git a/test/support/helpers.ex b/test/support/helpers.ex index 7b7d5fb..420536e 100644 --- a/test/support/helpers.ex +++ b/test/support/helpers.ex @@ -1,13 +1,22 @@ defmodule Temple.Support.Helpers do - defmacro assert_html(expected, actual) do - quote do - assert unquote(expected) == Phoenix.HTML.safe_to_string(unquote(actual)), """ - --- Expected --- - #{unquote(expected)}---------------- + import ExUnit.Assertions - --- Actual --- - #{Phoenix.HTML.safe_to_string(unquote(actual))}-------------- - """ + defmacro assert_html(expected, actual) do + quote location: :keep do + unquote(__MODULE__).__assert_html__(unquote_splicing([expected, actual])) end end + + def __assert_html__(expected, actual) do + actual = actual |> Phoenix.HTML.Engine.encode_to_iodata!() |> IO.iodata_to_binary() + + assert expected == actual, + """ + --- Expected --- + #{expected}---------------- + + --- Actual --- + #{actual}-------------- + """ + end end diff --git a/test/temple/ast/utils_test.exs b/test/temple/ast/utils_test.exs index f1715a6..4e0370e 100644 --- a/test/temple/ast/utils_test.exs +++ b/test/temple/ast/utils_test.exs @@ -25,7 +25,7 @@ defmodule Temple.Ast.UtilsTest do assert Macro.to_string( quote do - Phoenix.HTML.attributes_escape([{"class", unquote(class_ast)}]) + Temple.Ast.Utils.__attributes__([{"class", unquote(class_ast)}]) end ) == Macro.to_string(actual) end @@ -74,7 +74,7 @@ defmodule Temple.Ast.UtilsTest do assert Macro.to_string( quote do - Phoenix.HTML.attributes_escape(unquote(rest_ast)) + Temple.Ast.Utils.__attributes__(unquote(rest_ast)) end ) == Macro.to_string(rest_actual) end diff --git a/test/temple_test.exs b/test/temple_test.exs index 646b9d0..b6cd520 100644 --- a/test/temple_test.exs +++ b/test/temple_test.exs @@ -14,7 +14,7 @@ defmodule TempleTest do end end end - |> :erlang.iolist_to_binary() + |> Phoenix.HTML.safe_to_string() # heex expected = """ @@ -30,4 +30,10 @@ defmodule TempleTest do assert expected == result end end + + describe "attributes/1" do + test "compiles runtime attributes" do + assert ~s| disabled class="foo"| == attributes(disabled: true, checked: false, class: "foo") + end + end end