Repository: mhanberg/temple
Pull request #202 (2023-06-13 03:10:46)

feat!: configure runtime attributes function

mhanberg: Current dependencies on/for this PR:
* main
  * **PR #202** <a href="https://app.graphite.dev/github/pr/mhanberg/temple/202" target="_blank"><img src="https://static.graphite.dev/graphite-32x32.png" alt="Graphite" width="10px" height="10px"/></a>  👈

This comment was auto-generated by [Graphite](https://app.graphite.dev/github/pr/mhanberg/temple/202?utm_source=stack-comment).

Patch:
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


Test patch:
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
+  import ExUnit.Assertions
+
   defmacro assert_html(expected, actual) do
-    quote do
-      assert unquote(expected) == Phoenix.HTML.safe_to_string(unquote(actual)), """
-      --- Expected ---
-      #{unquote(expected)}----------------
-
-      --- Actual ---
-      #{Phoenix.HTML.safe_to_string(unquote(actual))}--------------
-      """
+    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