Hook the AST generation in to the temple macros

- Removes the old way
- Removes the ability to compact an element
This commit is contained in:
Mitchell Hanberg 2021-04-08 23:49:44 -04:00
parent 8bb4245761
commit 41f9b94d0f
22 changed files with 88 additions and 395 deletions

View file

@ -1,35 +0,0 @@
defmodule Temple.Buffer do
@moduledoc false
use Agent
def start_link(state \\ []) do
Agent.start_link(fn -> state end)
end
def put(buffer, value) do
Agent.update(buffer, fn b -> [value | b] end)
end
def remove_new_line(buffer) do
Agent.update(buffer, fn
["\n" | rest] ->
rest
rest ->
rest
end)
end
def get(buffer) do
buffer
|> Agent.get(& &1)
|> Enum.reverse()
|> Enum.join()
|> String.trim()
end
def stop(buffer) do
Agent.stop(buffer)
end
end

View file

@ -59,30 +59,6 @@ defmodule Temple do
You can pass a keyword list to an element as element attributes, but there is currently a reserved keyword.
#### Compact
Passing `compact: true` will not emit a new line between the opening tag, the content, and the closing tag. This is useful if you are trying to use the `:empty` psuedo selector.
```elixir
temple do
p compact: true do
"Foo"
end
p do
"Bar"
end
end
```
would evaluate to
```html
<p>Foo</p>
<p>
Bar
</p>
```
### Configuration
#### Aliases
@ -142,7 +118,11 @@ defmodule Temple do
```
"""
defmacro temple([do: block] = _block) do
markup = Parser.old_parse(block)
markup =
block
|> Parser.parse()
|> Enum.map(&Temple.EEx.to_eex/1)
|> :erlang.iolist_to_binary()
quote location: :keep do
unquote(markup)
@ -151,7 +131,10 @@ defmodule Temple do
defmacro temple(block) do
quote location: :keep do
Parser.old_parse(unquote(block))
unquote(block)
|> Parser.parse()
|> Enum.map(&Temple.EEx.to_eex/1)
|> :erlang.iolist_to_binary()
end
end
@ -173,7 +156,11 @@ defmodule Temple do
```
"""
defmacro compile(engine, [do: block] = _block) do
markup = Parser.old_parse(block)
markup =
block
|> Parser.parse()
|> Enum.map(&Temple.EEx.to_eex/1)
|> :erlang.iolist_to_binary()
EEx.compile_string(markup, engine: engine, line: __CALLER__.line, file: __CALLER__.file)
end

View file

@ -14,6 +14,20 @@ defmodule Temple.Parser do
alias Temple.Parser.Match
alias Temple.Parser.Default
@type ast ::
%Empty{}
| %Text{}
| %TempleNamespaceNonvoid{}
| %TempleNamespaceVoid{}
| %Components{}
| %NonvoidElementsAliases{}
| %VoidElementsAliases{}
| %AnonymousFunctions{}
| %RightArrow{}
| %DoExpressions{}
| %Match{}
| %Default{}
@doc """
Should return true if the parser should apply for the given AST.
"""
@ -24,9 +38,7 @@ defmodule Temple.Parser do
Should return `:ok` if the parsing pass is over, or `{:component_applied, ast}` if the pass should be restarted.
"""
@callback run(ast :: Macro.t(), buffer :: pid()) :: :ok | {:component_applied, Macro.t()}
alias Temple.Buffer
@callback run(ast :: Macro.t()) :: ast()
@aliases Application.get_env(:temple, :aliases, [])
@ -89,6 +101,16 @@ defmodule Temple.Parser do
Temple.Parser.Default
]
def parse({:__block__, _, asts}) do
parse(asts)
end
def parse(asts) when is_list(asts) do
Enum.flat_map(asts, fn ast ->
parse(ast)
end)
end
def parse(ast) do
with {_, false} <- {Empty, Empty.applicable?(ast)},
{_, false} <- {Text, Text.applicable?(ast)},
@ -111,17 +133,6 @@ defmodule Temple.Parser do
end
end
def old_parse(ast) do
{:ok, buffer} = Buffer.start_link()
Temple.Parser.Private.traverse(buffer, ast)
markup = Buffer.get(buffer)
Buffer.stop(buffer)
markup
end
defmodule Private do
@moduledoc false

View file

@ -5,7 +5,6 @@ defmodule Temple.Parser.AnonymousFunctions do
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@impl Parser
def applicable?({_, _, args}) do
@ -19,6 +18,7 @@ defmodule Temple.Parser.AnonymousFunctions do
def applicable?(_), do: false
@impl Parser
def run({_name, _, args} = expression) do
{_do_and_else, args} = Temple.Parser.Private.split_args(args)
@ -31,7 +31,6 @@ defmodule Temple.Parser.AnonymousFunctions do
Temple.Ast.new(
__MODULE__,
meta: %{type: :anonymous_functions},
content: expression,
children: children
)
@ -71,47 +70,4 @@ defmodule Temple.Parser.AnonymousFunctions do
]
end
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private
{_do_and_else, args} =
args
|> split_args()
{args, func_arg, args2} = split_on_fn(args, {[], nil, []})
{func, _, [{arrow, _, [[{arg, _, _}], block]}]} = func_arg
Buffer.put(
buffer,
"<%= " <>
to_string(name) <>
" " <>
(Enum.map(args, &Macro.to_string(&1)) |> Enum.join(", ")) <>
", " <>
to_string(func) <> " " <> to_string(arg) <> " " <> to_string(arrow) <> " %>"
)
Buffer.put(buffer, "\n")
traverse(buffer, block)
if Enum.any?(args2) do
Buffer.put(
buffer,
"<% end, " <>
(Enum.map(args2, fn arg -> Macro.to_string(arg) end)
|> Enum.join(", ")) <> " %>"
)
Buffer.put(buffer, "\n")
else
Buffer.put(buffer, "<% end %>")
Buffer.put(buffer, "\n")
end
:ok
end
end

View file

@ -4,14 +4,14 @@ defmodule Temple.Parser.Components do
defstruct content: nil, attrs: [], children: []
alias Temple.Buffer
@impl Temple.Parser
def applicable?({:c, _, _}) do
true
end
def applicable?(_), do: false
@impl Temple.Parser
def run({:c, _meta, [component_module | args]}) do
{assigns, block} =
case args do
@ -76,45 +76,4 @@ defmodule Temple.Parser.Components do
]
end
end
def run({:c, _meta, [component_module | args]}, buffer) do
import Temple.Parser.Private
{assigns, children} =
case args do
[assigns, [do: block]] ->
{assigns, block}
[[do: block]] ->
{[], block}
[assigns] ->
{assigns, nil}
_ ->
{[], nil}
end
if children do
Buffer.put(
buffer,
"<%= Phoenix.View.render_layout #{Macro.to_string(component_module)}, :self, #{
Macro.to_string(assigns)
} do %>"
)
traverse(buffer, children)
Buffer.put(buffer, "<% end %>")
else
Buffer.put(
buffer,
"<%= Phoenix.View.render #{Macro.to_string(component_module)}, :self, #{
Macro.to_string(assigns)
} %>"
)
end
:ok
end
end

View file

@ -5,37 +5,21 @@ defmodule Temple.Parser.Default do
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@impl Parser
def applicable?(_ast), do: true
@impl Parser
def run(ast) do
Temple.Ast.new(
__MODULE__,
meta: %{type: :default},
content: ast
)
end
@impl Parser
def run({_, _, args} = macro, buffer) do
import Temple.Parser.Private
{do_and_else, _args} =
args
|> split_args()
Buffer.put(buffer, "<%= " <> Macro.to_string(macro) <> " %>")
Buffer.put(buffer, "\n")
traverse(buffer, do_and_else[:do])
:ok
end
defimpl Temple.EEx do
def to_eex(%{content: expression}) do
["<%= ", Macro.to_string(expression), " %>"]
["<%= ", Macro.to_string(expression), " %>\n"]
end
end
end

View file

@ -1,12 +1,11 @@
defmodule Temple.Parser.DoExpressions do
@moduledoc false
@behaviour Temple.Parser
alias Temple.Parser
@behaviour Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@impl Parser
def applicable?({_, _, args}) when is_list(args) do
Enum.any?(args, fn arg -> match?([{:do, _} | _], arg) end)
@ -14,6 +13,7 @@ defmodule Temple.Parser.DoExpressions do
def applicable?(_), do: false
@impl Parser
def run({name, meta, args}) do
{do_and_else, args} = Temple.Parser.Private.split_args(args)
@ -28,7 +28,6 @@ defmodule Temple.Parser.DoExpressions do
Temple.Ast.new(
__MODULE__,
meta: %{type: :do_expression},
children: [do_body, else_body],
content: {name, meta, args}
)
@ -43,37 +42,11 @@ defmodule Temple.Parser.DoExpressions do
"\n",
for(child <- do_body, do: Temple.EEx.to_eex(child)),
if(else_body != nil,
do: ["\n<% else %>\n", for(child <- else_body, do: Temple.EEx.to_eex(child))],
do: ["<% else %>\n", for(child <- else_body, do: Temple.EEx.to_eex(child))],
else: ""
),
"\n",
"<% end %>"
]
end
end
@impl Parser
def run({name, meta, args}, buffer) do
import Temple.Parser.Private
{do_and_else, args} =
args
|> split_args()
Buffer.put(buffer, "<%= " <> Macro.to_string({name, meta, args}) <> " do %>")
Buffer.put(buffer, "\n")
traverse(buffer, do_and_else[:do])
if Keyword.has_key?(do_and_else, :else) do
Buffer.put(buffer, "<% else %>")
Buffer.put(buffer, "\n")
traverse(buffer, do_and_else[:else])
end
Buffer.put(buffer, "<% end %>")
Buffer.put(buffer, "\n")
:ok
end
end

View file

@ -11,6 +11,7 @@ defmodule Temple.Parser.Empty do
def applicable?(ast) when ast in [nil, []], do: true
def applicable?(_), do: false
@impl Parser
def run(_ast) do
Ast.new(
__MODULE__,
@ -18,11 +19,6 @@ defmodule Temple.Parser.Empty do
)
end
@impl Parser
def run(_ast, _buffer) do
:ok
end
defimpl Temple.EEx do
def to_eex(_) do
[]

View file

@ -5,7 +5,6 @@ defmodule Temple.Parser.Match do
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@impl Parser
def applicable?({name, _, _}) do
@ -14,6 +13,7 @@ defmodule Temple.Parser.Match do
def applicable?(_), do: false
@impl Parser
def run(macro) do
Temple.Ast.new(
__MODULE__,
@ -27,19 +27,4 @@ defmodule Temple.Parser.Match do
["<% ", Macro.to_string(content), " %>"]
end
end
@impl Parser
def run({_, _, args} = macro, buffer) do
import Temple.Parser.Private
{do_and_else, _args} =
args
|> split_args()
Buffer.put(buffer, "<% " <> Macro.to_string(macro) <> " %>")
Buffer.put(buffer, "\n")
traverse(buffer, do_and_else[:do])
:ok
end
end

View file

@ -5,7 +5,6 @@ defmodule Temple.Parser.NonvoidElementsAliases do
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@impl Parser
def applicable?({name, _, _}) do
@ -14,6 +13,7 @@ defmodule Temple.Parser.NonvoidElementsAliases do
def applicable?(_), do: false
@impl Parser
def run({name, _, args}) do
name = Parser.nonvoid_elements_lookup()[name]
@ -59,39 +59,4 @@ defmodule Temple.Parser.NonvoidElementsAliases do
]
end
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private
{do_and_else, args} =
args
|> split_args()
{do_and_else, args} =
case args do
[args] when is_list(args) ->
{do_value, args} = Keyword.pop(args, :do)
do_and_else = Keyword.put_new(do_and_else, :do, do_value)
{do_and_else, args}
_ ->
{do_and_else, args}
end
name = Parser.nonvoid_elements_lookup()[name]
{compact?, args} = pop_compact?(args)
Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
unless compact?, do: Buffer.put(buffer, "\n")
traverse(buffer, do_and_else[:do])
if compact?, do: Buffer.remove_new_line(buffer)
Buffer.put(buffer, "</#{name}>")
Buffer.put(buffer, "\n")
:ok
end
end

View file

@ -1,18 +1,18 @@
defmodule Temple.Parser.RightArrow do
@moduledoc false
@behaviour Temple.Parser
alias Temple.Parser
@behaviour Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@impl Parser
def applicable?({:->, _, _}), do: true
def applicable?(_), do: false
@impl Parser
def run({_, _, [[pattern], args]}) do
children = Temple.Parser.parse(args)
children = Parser.parse(args)
Temple.Ast.new(
__MODULE__,
@ -32,14 +32,4 @@ defmodule Temple.Parser.RightArrow do
]
end
end
@impl Parser
def run({_, _, [[pattern], args]}, buffer) do
import Temple.Parser.Private
Buffer.put(buffer, "<% " <> Macro.to_string(pattern) <> " -> %>\n")
traverse(buffer, args)
:ok
end
end

View file

@ -5,7 +5,6 @@ defmodule Temple.Parser.TempleNamespaceNonvoid do
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@impl Parser
def applicable?({{:., _, [{:__aliases__, _, [:Temple]}, name]}, _meta, _args}) do
@ -14,44 +13,9 @@ defmodule Temple.Parser.TempleNamespaceNonvoid do
def applicable?(_), do: false
@impl Parser
def run({name, meta, args}) do
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
Temple.Parser.NonvoidElementsAliases.run({name, meta, args})
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
{do_and_else, args} =
args
|> split_args()
{do_and_else, args} =
case args do
[args] when is_list(args) ->
{do_value, args} = Keyword.pop(args, :do)
do_and_else = Keyword.put_new(do_and_else, :do, do_value)
{do_and_else, args}
_ ->
{do_and_else, args}
end
name = Parser.nonvoid_elements_lookup()[name]
{compact?, args} = pop_compact?(args)
Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
unless compact?, do: Buffer.put(buffer, "\n")
traverse(buffer, do_and_else[:do])
if compact?, do: Buffer.remove_new_line(buffer)
Buffer.put(buffer, "</#{name}>")
Buffer.put(buffer, "\n")
:ok
end
end

View file

@ -5,7 +5,6 @@ defmodule Temple.Parser.TempleNamespaceVoid do
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@impl Parser
def applicable?({{:., _, [{:__aliases__, _, [:Temple]}, name]}, _meta, _args}) do
@ -14,26 +13,10 @@ defmodule Temple.Parser.TempleNamespaceVoid do
def applicable?(_), do: false
@impl Parser
def run({name, meta, args}) do
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
Temple.Parser.VoidElementsAliases.run({name, meta, args})
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
{_do_and_else, args} =
args
|> split_args()
name = Parser.void_elements_lookup()[name]
Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
Buffer.put(buffer, "\n")
:ok
end
end

View file

@ -4,7 +4,6 @@ defmodule Temple.Parser.Text do
defstruct content: nil, attrs: [], children: []
alias Temple.Buffer
alias Temple.Parser
alias Temple.Ast
@ -12,25 +11,17 @@ defmodule Temple.Parser.Text do
def applicable?(text) when is_binary(text), do: true
def applicable?(_), do: false
@impl Parser
def run(text) do
Ast.new(
__MODULE__,
content: text,
meta: %{type: :text}
content: text
)
end
@impl Parser
def run(text, buffer) do
Buffer.put(buffer, text)
Buffer.put(buffer, "\n")
:ok
end
defimpl Temple.EEx do
def to_eex(%{content: text}) do
[text]
[text, "\n"]
end
end
end

View file

@ -5,7 +5,6 @@ defmodule Temple.Parser.VoidElementsAliases do
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@impl Parser
def applicable?({name, _, _}) do
@ -14,6 +13,7 @@ defmodule Temple.Parser.VoidElementsAliases do
def applicable?(_), do: false
@impl Parser
def run({name, _, args}) do
{_do_and_else, [args]} = Temple.Parser.Private.split_args(args)
@ -37,20 +37,4 @@ defmodule Temple.Parser.VoidElementsAliases do
]
end
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private
{_do_and_else, args} =
args
|> split_args()
name = Parser.void_elements_lookup()[name]
Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
Buffer.put(buffer, "\n")
:ok
end
end

View file

@ -39,7 +39,7 @@ defmodule Temple.Parser.DefaultTest do
|> Default.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() == ~s|<%= Foo.bar!(baz) %>|
assert result |> :erlang.iolist_to_binary() == ~s|<%= Foo.bar!(baz) %>\n|
end
end
end

View file

@ -30,7 +30,8 @@ defmodule Temple.Parser.DoExpressionsTest do
assert %DoExpressions{
content: _,
children: [
[%Temple.Parser.Text{content: "bob", children: []}], nil
[%Temple.Parser.Text{content: "bob", children: []}],
nil
]
} = ast
end
@ -56,6 +57,8 @@ defmodule Temple.Parser.DoExpressionsTest do
quote do
if foo? do
"bob"
"bobby"
else
"carol"
end
@ -64,7 +67,22 @@ defmodule Temple.Parser.DoExpressionsTest do
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= if(foo?) do %>\nbob\n<% else %>\ncarol\n<% end %>|
~s|<%= if(foo?) do %>\nbob\nbobby\n<% else %>\ncarol\n<% end %>|
end
test "emits eex for a case expression" do
result =
quote do
case foo? do
:bing ->
:bong
end
end
|> DoExpressions.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= case(foo?) do %>\n<% :bing -> %>\n<%= :bong %>\n<% end %>|
end
end
end

View file

@ -96,7 +96,7 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<div class="foo" id="<%= var %>">\n<select>\n<option>\nfoo\n</option>\n</select>\n</div>|
~s|<div class="foo" id="<%= var %>">\n<select>\n<option>\nfoo\n\n</option>\n</select>\n</div>|
end
end
end

View file

@ -73,7 +73,7 @@ defmodule Temple.Parser.RightArrowTest do
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<% :bing -> %>\n<%= :bong %>|
~s|<% :bing -> %>\n<%= :bong %>\n|
end
end
end

View file

@ -67,7 +67,7 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<div class="foo" id="<%= var %>">\nfoo\n</div>|
~s|<div class="foo" id="<%= var %>">\nfoo\n\n</div>|
end
end
end

View file

@ -39,7 +39,7 @@ defmodule Temple.Parser.TextTest do
|> Text.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() == ~s|string literal|
assert result |> :erlang.iolist_to_binary() == ~s|string literal\n|
end
end
end

View file

@ -269,24 +269,6 @@ defmodule TempleTest do
~s{<div class="font-bold">Hello, world</div><div class="font-bold">Hello, world</div><div>Hello, world</div>}
end
test "`do` passed as keyword will compile compacted markup" do
import Temple.Support.Utils, only: []
import Kernel
result =
temple do
p compact: true do
"Bob"
end
p compact: true do
foo
end
end
assert result == ~s{<p>Bob</p>\n<p><%= foo %></p>}
end
test "for with 2 generators" do
result =
temple do