defelement macro

- add location: :keep to all calls to quote
This commit is contained in:
Mitchell Hanberg 2019-08-20 23:13:10 -04:00
parent b0a7f9da11
commit 74e7f3a25e
8 changed files with 153 additions and 73 deletions

View file

@ -1,6 +1,6 @@
defmodule Temple do
defmacro __using__(_) do
quote do
quote location: :keep do
import Temple
import Temple.Tags
import Temple.Form
@ -32,7 +32,7 @@ defmodule Temple do
```
"""
defmacro temple([do: block] = _block) do
quote do
quote location: :keep do
import Kernel, except: [div: 2]
with {:ok, var!(buff, Temple.Tags)} <- Temple.Utils.start_buffer([]) do
@ -61,7 +61,7 @@ defmodule Temple do
```
"""
defmacro text(text) do
quote do
quote location: :keep do
Temple.Utils.put_buffer(
var!(buff, Temple.Tags),
unquote(text) |> Temple.Utils.escape_content()
@ -96,7 +96,7 @@ defmodule Temple do
```
"""
defmacro partial(partial) do
quote do
quote location: :keep do
Temple.Utils.put_buffer(
var!(buff, Temple.Tags),
unquote(partial) |> Temple.Utils.from_safe()
@ -144,7 +144,7 @@ defmodule Temple do
```
"""
defmacro defcomponent(name, [do: _] = block) do
quote do
quote location: :keep do
defmacro unquote(name)() do
outer = unquote(Macro.escape(block))

View file

@ -1,15 +1,74 @@
defmodule Temple.Elements do
@moduledoc """
This module contains the primitives used to generate the macros in the `Temple.Tags` module.
"""
@doc """
Defines an element.
*Note*: Underscores are converted to hypens.
```elixir
defmodule MyElements do
import Temple.Elements
defelement :super_select, :nonvoid # <super-select></super-select>
defelement :super_input, :void # <super-input>
end
```
"""
defmacro defelement(name, type)
defmacro defelement(name, :nonvoid) do
quote location: :keep do
defmacro unquote(name)() do
Temple.Elements.nonvoid_element(unquote(name))
end
defmacro unquote(name)(attrs_or_content_or_block)
defmacro unquote(name)([{:do, _inner}] = block) do
Temple.Elements.nonvoid_element(unquote(name), block)
end
defmacro unquote(name)(attrs_or_content) do
Temple.Elements.nonvoid_element(unquote(name), attrs_or_content)
end
defmacro unquote(name)(attrs_or_content, block_or_attrs)
defmacro unquote(name)(attrs, [{:do, _inner}] = block) do
Temple.Elements.nonvoid_element(unquote(name), attrs, block)
end
defmacro unquote(name)(content, attrs) do
Temple.Elements.nonvoid_element(unquote(name), content, attrs)
end
end
end
defmacro defelement(name, :void) do
quote location: :keep do
defmacro unquote(name)(attrs \\ []) do
Temple.Elements.void_element(unquote(name), attrs)
end
end
end
@doc false
def nonvoid_element(el) do
quote do
quote location: :keep do
Temple.Utils.put_open_tag(var!(buff, Temple.Tags), unquote(el), [])
Temple.Utils.put_close_tag(var!(buff, Temple.Tags), unquote(el))
end
end
@doc false
def nonvoid_element(el, attrs_or_content_or_block)
def nonvoid_element(el, [{:do, inner}]) do
quote do
quote location: :keep do
Temple.Utils.put_open_tag(var!(buff, Temple.Tags), unquote(el), [])
_ = unquote(inner)
Temple.Utils.put_close_tag(var!(buff, Temple.Tags), unquote(el))
@ -17,16 +76,17 @@ defmodule Temple.Elements do
end
def nonvoid_element(el, attrs_or_content) do
quote do
quote location: :keep do
Temple.Utils.put_open_tag(var!(buff, Temple.Tags), unquote(el), unquote(attrs_or_content))
Temple.Utils.put_close_tag(var!(buff, Temple.Tags), unquote(el))
end
end
@doc false
def nonvoid_element(el, attrs_or_content, block_or_attrs)
def nonvoid_element(el, attrs, [{:do, inner}] = _block) do
quote do
quote location: :keep do
Temple.Utils.put_open_tag(var!(buff, Temple.Tags), unquote_splicing([el, attrs]))
_ = unquote(inner)
Temple.Utils.put_close_tag(var!(buff, Temple.Tags), unquote(el))
@ -34,21 +94,17 @@ defmodule Temple.Elements do
end
def nonvoid_element(el, content, attrs) do
quote do
quote location: :keep do
Temple.Utils.put_open_tag(var!(buff, Temple.Tags), unquote_splicing([el, attrs]))
text unquote(content)
Temple.Utils.put_close_tag(var!(buff, Temple.Tags), unquote(el))
end
end
@doc false
def void_element(el, attrs \\ []) do
quote do
attrs = unquote(attrs)
Temple.Utils.put_buffer(
var!(buff, Temple.Tags),
"<#{unquote(el)}#{Temple.Utils.compile_attrs(attrs)}>"
)
quote location: :keep do
Temple.Utils.put_void_tag(var!(buff, Temple.Tags), unquote_splicing([el, attrs]))
end
end
end

View file

@ -12,7 +12,7 @@ defmodule Temple.Engine do
# your_app_web.ex
def view do
quote do
quote location: :keep do
# ...
use Temple # Replaces the call to import Phoenix.HTML
end
@ -75,7 +75,7 @@ defmodule Temple.Engine do
|> Code.string_to_quoted!(file: path)
|> handle_assigns()
quote do
quote location: :keep do
use Temple
temple do: unquote(template)
@ -86,7 +86,7 @@ defmodule Temple.Engine do
quoted
|> Macro.prewalk(fn
{:@, _, [{key, _, _}]} ->
quote do
quote location: :keep do
case Access.fetch(var!(assigns), unquote(key)) do
{:ok, val} ->
val

View file

@ -12,7 +12,7 @@ defmodule Temple.Form do
See `Temple.Form.form_for/4` for more details
"""
defmacro form_for(form_data, action) do
quote do
quote location: :keep do
form_for(unquote_splicing([form_data, action]), [])
end
end
@ -42,7 +42,7 @@ defmodule Temple.Form do
```
"""
defmacro form_for(form_data, action, opts \\ [], block) do
quote do
quote location: :keep do
var!(form) = HTML.Form.form_for(unquote_splicing([form_data, action, opts]))
Utils.put_buffer(var!(buff, Temple.Tags), var!(form) |> HTML.Safe.to_iodata())
@ -79,7 +79,7 @@ defmodule Temple.Form do
defmacro unquote(helper)(form, field, opts \\ []) do
helper = unquote(helper)
quote do
quote location: :keep do
{:safe, input} =
apply(Phoenix.HTML.Form, unquote(helper), [unquote_splicing([form, field, opts])])
@ -94,7 +94,7 @@ defmodule Temple.Form do
Note: Temple defines this function as `text_area` with an underscore, whereas Phoenix.HTML defines it as `textarea` without an underscore.
"""
defmacro text_area(form, field, opts \\ []) do
quote do
quote location: :keep do
{:safe, input} = Phoenix.HTML.Form.textarea(unquote_splicing([form, field, opts]))
Utils.put_buffer(var!(buff, Temple.Tags), input)
@ -105,7 +105,7 @@ defmodule Temple.Form do
Please see `Phoenix.HTML.Form.reset/2` for details.
"""
defmacro reset(value, opts \\ []) do
quote do
quote location: :keep do
{:safe, input} = Phoenix.HTML.Form.reset(unquote_splicing([value, opts]))
Utils.put_buffer(var!(buff, Temple.Tags), input)
@ -116,7 +116,7 @@ defmodule Temple.Form do
Please see `Phoenix.HTML.Form.submit/1` for details.
"""
defmacro submit(do: block) do
quote do
quote location: :keep do
{:safe, content} =
temple do
unquote(block)
@ -129,7 +129,7 @@ defmodule Temple.Form do
end
defmacro submit(value) do
quote do
quote location: :keep do
{:safe, input} = Phoenix.HTML.Form.submit(unquote(value))
Utils.put_buffer(var!(buff, Temple.Tags), input)
@ -140,7 +140,7 @@ defmodule Temple.Form do
Please see `Phoenix.HTML.Form.submit/1` for details.
"""
defmacro submit(opts, do: block) do
quote do
quote location: :keep do
{:safe, content} =
temple do
unquote(block)
@ -153,7 +153,7 @@ defmodule Temple.Form do
end
defmacro submit(value, opts) do
quote do
quote location: :keep do
{:safe, input} = Phoenix.HTML.Form.submit(unquote_splicing([value, opts]))
Utils.put_buffer(var!(buff, Temple.Tags), input)
@ -164,7 +164,7 @@ defmodule Temple.Form do
Please see `Phoenix.HTML.Form.label/2` for details.
"""
defmacro phx_label(form, field) do
quote do
quote location: :keep do
{:safe, input} = Phoenix.HTML.Form.label(unquote_splicing([form, field]))
Utils.put_buffer(var!(buff, Temple.Tags), input)
@ -175,7 +175,7 @@ defmodule Temple.Form do
Please see `Phoenix.HTML.Form.label/3` for details.
"""
defmacro phx_label(form, field, do: block) do
quote do
quote location: :keep do
{:safe, content} =
temple do
unquote(block)
@ -188,7 +188,7 @@ defmodule Temple.Form do
end
defmacro phx_label(form, field, text_or_opts) do
quote do
quote location: :keep do
{:safe, input} = Phoenix.HTML.Form.label(unquote_splicing([form, field, text_or_opts]))
Utils.put_buffer(var!(buff, Temple.Tags), input)
@ -199,7 +199,7 @@ defmodule Temple.Form do
Please see `Phoenix.HTML.Form.label/4` for details.
"""
defmacro phx_label(form, field, opts, do: block) do
quote do
quote location: :keep do
{:safe, content} =
temple do
unquote(block)
@ -212,7 +212,7 @@ defmodule Temple.Form do
end
defmacro phx_label(form, field, text, opts) do
quote do
quote location: :keep do
{:safe, input} = Phoenix.HTML.Form.label(unquote_splicing([form, field, text, opts]))
Utils.put_buffer(var!(buff, Temple.Tags), input)
@ -223,7 +223,7 @@ defmodule Temple.Form do
Please see `Phoenix.HTML.Form.radio_button/4` for details.
"""
defmacro radio_button(form, field, value, attrs \\ []) do
quote do
quote location: :keep do
{:safe, input} =
Phoenix.HTML.Form.radio_button(unquote_splicing([form, field, value, attrs]))
@ -235,7 +235,7 @@ defmodule Temple.Form do
Please see `Phoenix.HTML.Form.multiple_select/4` for details.
"""
defmacro multiple_select(form, field, options, attrs \\ []) do
quote do
quote location: :keep do
{:safe, input} =
Phoenix.HTML.Form.multiple_select(unquote_splicing([form, field, options, attrs]))
@ -247,7 +247,7 @@ defmodule Temple.Form do
Please see `Phoenix.HTML.Form.select/4` for details.
"""
defmacro select(form, field, options, attrs \\ []) do
quote do
quote location: :keep do
{:safe, input} = Phoenix.HTML.Form.select(unquote_splicing([form, field, options, attrs]))
Utils.put_buffer(var!(buff, Temple.Tags), input)
@ -292,7 +292,7 @@ defmodule Temple.Form do
```
"""
defmacro inputs_for(form, field, options \\ [], do: block) do
quote do
quote location: :keep do
form = unquote(form)
field = unquote(field)
options = unquote(options)

View file

@ -10,7 +10,7 @@ defmodule Temple.Link do
Please see `Phoenix.HTML.Link.link/2` for details.
"""
defmacro phx_link(opts, do: block) do
quote do
quote location: :keep do
{:safe, content} =
temple do
unquote(block)
@ -23,7 +23,7 @@ defmodule Temple.Link do
end
defmacro phx_link(content, opts) do
quote do
quote location: :keep do
{:safe, link} = HTML.Link.link(unquote_splicing([content, opts]))
Utils.put_buffer(var!(buff, Temple.Tags), link)
@ -34,7 +34,7 @@ defmodule Temple.Link do
Please see `Phoenix.HTML.Link.button/2` for details.
"""
defmacro phx_button(opts, do: block) do
quote do
quote location: :keep do
{:safe, content} =
temple do
unquote(block)
@ -47,7 +47,7 @@ defmodule Temple.Link do
end
defmacro phx_button(content, opts) do
quote do
quote location: :keep do
{:safe, link} = HTML.Link.button(unquote_splicing([content, opts]))
Utils.put_buffer(var!(buff, Temple.Tags), link)

View file

@ -1,4 +1,6 @@
defmodule Temple.Tags do
require Temple.Elements
@moduledoc """
The `Temple.Tags` module defines macros for all HTML5 compliant elements.
@ -23,7 +25,7 @@ defmodule Temple.Tags do
# non-void element with keyword list attributes
div class: "text-red", id: "my-el"
#
# non-void element with map attributes
div %{:class => "text-red", "id" => "my-el"}
@ -87,42 +89,18 @@ defmodule Temple.Tags do
for el <- @nonvoid_elements do
@doc if File.exists?("./tmp/docs/#{el}.txt"), do: File.read!("./tmp/docs/#{el}.txt")
defmacro unquote(el)() do
Temple.Elements.nonvoid_element(unquote(el))
end
defmacro unquote(el)(attrs_or_content_or_block)
defmacro unquote(el)([{:do, _inner}] = block) do
Temple.Elements.nonvoid_element(unquote(el), block)
end
defmacro unquote(el)(attrs_or_content) do
Temple.Elements.nonvoid_element(unquote(el), attrs_or_content)
end
defmacro unquote(el)(attrs_or_content, block_or_attrs)
defmacro unquote(el)(attrs, [{:do, _inner}] = block) do
Temple.Elements.nonvoid_element(unquote(el), attrs, block)
end
defmacro unquote(el)(content, attrs) do
Temple.Elements.nonvoid_element(unquote(el), content, attrs)
end
Temple.Elements.defelement(unquote(el), :nonvoid)
end
for el <- @void_elements do
@doc if File.exists?("./tmp/docs/#{el}.txt"), do: File.read!("./tmp/docs/#{el}.txt")
defmacro unquote(el)(attrs \\ []) do
Temple.Elements.void_element(unquote(el), attrs)
end
Temple.Elements.defelement(unquote(el), :void)
end
@doc if File.exists?("./tmp/docs/html.txt"), do: File.read!("./tmp/docs/html.txt")
defmacro html(attrs \\ [], [{:do, _inner}] = block) do
doc_type =
quote do
quote location: :keep do
Temple.Utils.put_buffer(var!(buff, Temple.Tags), "<!DOCTYPE html>")
end

View file

@ -2,19 +2,31 @@ defmodule Temple.Utils do
@moduledoc false
def put_open_tag(buff, el, attrs) when is_list(attrs) or is_map(attrs) do
el = el |> snake_to_kebab
put_buffer(buff, "<#{el}#{compile_attrs(attrs)}>")
end
def put_open_tag(buff, el, content)
when is_binary(content) or is_number(content) or is_atom(content) do
el = el |> snake_to_kebab
put_buffer(buff, "<#{el}>")
put_buffer(buff, escape_content(content))
end
def put_close_tag(buff, el) do
el = el |> snake_to_kebab
put_buffer(buff, "</#{el}>")
end
def put_void_tag(buff, el, attrs) do
el = el |> snake_to_kebab
put_buffer(buff, "<#{el}#{Temple.Utils.compile_attrs(attrs)}>")
end
def from_safe({:safe, partial}) do
partial
end
@ -28,7 +40,7 @@ defmodule Temple.Utils do
end
def insert_props({:@, _, [{name, _, _}]}, props, _) when is_atom(name) do
quote do
quote location: :keep do
Access.get(unquote_splicing([props, name]))
end
end
@ -39,7 +51,7 @@ defmodule Temple.Utils do
def compile_attrs(attrs) do
for {name, value} <- attrs, into: "" do
name = name |> to_string() |> String.replace("_", "-")
name = snake_to_kebab(name)
" " <> name <> "=\"" <> to_string(value) <> "\""
end
@ -61,8 +73,11 @@ defmodule Temple.Utils do
|> Phoenix.HTML.safe_to_string()
end
defp snake_to_kebab(stringable),
do: stringable |> to_string() |> String.replace("_", "-")
def __quote__(outer) do
quote do: unquote(outer)
quote [location: :keep], do: unquote(outer)
end
def __insert_props__(block, props, inner) do

View file

@ -0,0 +1,31 @@
defmodule Temple.ElementsTest do
use ExUnit.Case, async: true
import Temple.Elements, only: [defelement: 2]
import Temple, only: [temple: 1, text: 1]
import Temple.Tags, only: [option: 2]
defelement(:my_select, :nonvoid)
defelement(:my_input, :void)
test "defines a nonvoid element" do
{:safe, result} =
temple do
my_select class: "hello" do
option "A", value: "A"
option "B", value: "B"
end
end
assert result ==
~s{<my-select class="hello"><option value="A">A</option><option value="B">B</option></my-select>}
end
test "defines a void element" do
{:safe, result} =
temple do
my_input(class: "hello")
end
assert result == ~s{<my-input class="hello">}
end
end