Output AST to EEx

This commit is contained in:
Mitchell Hanberg 2021-04-08 23:04:26 -04:00
parent 4e9c7e95b4
commit 8bb4245761
26 changed files with 504 additions and 99 deletions

View file

@ -1,7 +1,5 @@
defmodule Temple.Ast do
defstruct content: nil, attrs: [], children: [], meta: %{}
def new(opts \\ []) do
struct(__MODULE__, opts)
def new(module, opts \\ []) do
struct(module, opts)
end
end

3
lib/temple/eex.ex Normal file
View file

@ -0,0 +1,3 @@
defprotocol Temple.EEx do
def to_eex(ast)
end

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.AnonymousFunctions do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@ -20,19 +22,56 @@ defmodule Temple.Parser.AnonymousFunctions do
def run({_name, _, args} = expression) do
{_do_and_else, args} = Temple.Parser.Private.split_args(args)
{_args, func_arg, _args2} = Temple.Parser.Private.split_on_fn(args, {[], nil, []})
{_args, func_arg, _args2} =
Temple.Parser.Private.split_on_fn(args, {[], nil, []})
{_func, _, [{_arrow, _, [[{_arg, _, _}], block]}]} = func_arg
children = Temple.Parser.parse(block)
Temple.Ast.new(
__MODULE__,
meta: %{type: :anonymous_functions},
content: expression,
children: children
)
end
defimpl Temple.EEx do
def to_eex(%{content: {name, _, args}, children: children}) do
{_do_and_else, args} = Temple.Parser.Private.split_args(args)
{args, {func, _, [{arrow, _, [[{arg, _, _}], _block]}]}, args2} =
Temple.Parser.Private.split_on_fn(args, {[], nil, []})
[
"<%= ",
to_string(name),
" ",
Enum.map(args, &Macro.to_string(&1)) |> Enum.join(", "),
", ",
to_string(func),
" ",
to_string(arg),
" ",
to_string(arrow),
" %>",
"\n",
for(child <- children, do: Temple.EEx.to_eex(child)),
if Enum.any?(args2) do
[
"<% end, ",
Enum.map(args2, fn arg -> Macro.to_string(arg) end)
|> Enum.join(", "),
" %>"
]
else
["<% end %>", "\n"]
end
]
end
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.Components do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Buffer
def applicable?({:c, _, _}) do
@ -10,19 +12,71 @@ defmodule Temple.Parser.Components do
def applicable?(_), do: false
def run({:c, meta, [component_module, [do: _] = block]}) do
run({:c, meta, [component_module, [], block]})
end
def run({:c, _meta, [component_module | args]}) do
{assigns, block} =
case args do
[assigns, [do: block]] ->
{assigns, block}
[[do: block]] ->
{[], block}
[assigns] ->
{assigns, nil}
_ ->
{[], nil}
end
children =
if block == nil do
[]
else
Temple.Parser.parse(block)
end
def run({:c, _meta, [component_module, args, [do: block]]}) do
Temple.Ast.new(
__MODULE__,
meta: %{type: :component},
content: Macro.expand_once(component_module, __ENV__),
attrs: args,
children: block
attrs: assigns,
children: children
)
end
defimpl Temple.EEx do
def to_eex(%{content: component_module, attrs: assigns, children: []}) do
[
"<%= Phoenix.View.render",
" ",
Macro.to_string(component_module),
", ",
":self,",
" ",
Macro.to_string(assigns),
" ",
"%>"
]
end
def to_eex(%{content: component_module, attrs: assigns, children: children}) do
[
"<%= Phoenix.View.render_layout ",
Macro.to_string(component_module),
", ",
":self",
", ",
Macro.to_string(assigns),
" ",
"do %>",
"\n",
for(child <- children, do: Temple.EEx.to_eex(child)),
"\n",
"<% end %>"
]
end
end
def run({:c, _meta, [component_module | args]}, buffer) do
import Temple.Parser.Private

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.Default do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@ -10,6 +12,7 @@ defmodule Temple.Parser.Default do
def run(ast) do
Temple.Ast.new(
__MODULE__,
meta: %{type: :default},
content: ast
)
@ -29,4 +32,10 @@ defmodule Temple.Parser.Default do
:ok
end
defimpl Temple.EEx do
def to_eex(%{content: expression}) do
["<%= ", Macro.to_string(expression), " %>"]
end
end
end

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.DoExpressions do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@ -17,15 +19,39 @@ defmodule Temple.Parser.DoExpressions do
do_body = Temple.Parser.parse(do_and_else[:do])
else_body = Temple.Parser.parse(do_and_else[:else])
else_body =
if do_and_else[:else] == nil do
nil
else
Temple.Parser.parse(do_and_else[:else])
end
Temple.Ast.new(
__MODULE__,
meta: %{type: :do_expression},
children: [do_body, else_body],
content: {name, meta, args}
)
end
defimpl Temple.EEx do
def to_eex(%{content: expression, children: [do_body, else_body]}) do
[
"<%= ",
Macro.to_string(expression),
" 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))],
else: ""
),
"\n",
"<% end %>"
]
end
end
@impl Parser
def run({name, meta, args}, buffer) do
import Temple.Parser.Private

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.Empty do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Ast
@ -10,11 +12,20 @@ defmodule Temple.Parser.Empty do
def applicable?(_), do: false
def run(_ast) do
Ast.new(meta: %{type: :empty})
Ast.new(
__MODULE__,
meta: %{type: :empty}
)
end
@impl Parser
def run(_ast, _buffer) do
:ok
end
defimpl Temple.EEx do
def to_eex(_) do
[]
end
end
end

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.Match do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@ -14,11 +16,18 @@ defmodule Temple.Parser.Match do
def run(macro) do
Temple.Ast.new(
__MODULE__,
meta: %{type: :match},
content: macro
)
end
defimpl Temple.EEx do
def to_eex(%{content: content}) do
["<% ", Macro.to_string(content), " %>"]
end
end
@impl Parser
def run({_, _, args} = macro, buffer) do
import Temple.Parser.Private

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.NonvoidElementsAliases do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@ -35,6 +37,7 @@ defmodule Temple.Parser.NonvoidElementsAliases do
children = Temple.Parser.parse(do_and_else[:do])
Temple.Ast.new(
__MODULE__,
content: to_string(name),
meta: %{type: :nonvoid_alias},
attrs: args,
@ -42,6 +45,21 @@ defmodule Temple.Parser.NonvoidElementsAliases do
)
end
defimpl Temple.EEx do
def to_eex(%{content: content, attrs: attrs, children: children}) do
[
"<",
content,
Temple.Parser.Private.compile_attrs(attrs),
">\n",
for(child <- children, do: Temple.EEx.to_eex(child)),
"\n</",
content,
">"
]
end
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.RightArrow do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@ -13,12 +15,24 @@ defmodule Temple.Parser.RightArrow do
children = Temple.Parser.parse(args)
Temple.Ast.new(
__MODULE__,
meta: %{type: :right_arrow},
content: pattern,
children: children
)
end
defimpl Temple.EEx do
def to_eex(%{content: content, children: children}) do
[
"<% ",
Macro.to_string(content),
" -> %>\n",
for(child <- children, do: Temple.EEx.to_eex(child))
]
end
end
@impl Parser
def run({_, _, [[pattern], args]}, buffer) do
import Temple.Parser.Private

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.TempleNamespaceNonvoid do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@ -12,35 +14,9 @@ defmodule Temple.Parser.TempleNamespaceNonvoid do
def applicable?(_), do: false
def run({name, _, args}) do
def run({name, meta, args}) do
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
name = Parser.nonvoid_elements_lookup()[name]
{do_and_else, args} =
args
|> Temple.Parser.Private.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
children = Temple.Parser.parse(do_and_else[:do])
Temple.Ast.new(
content: to_string(name),
meta: %{type: :temple_nonvoid},
attrs: args,
children: children
)
Temple.Parser.NonvoidElementsAliases.run({name, meta, args})
end
@impl Parser

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.TempleNamespaceVoid do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@ -12,16 +14,10 @@ defmodule Temple.Parser.TempleNamespaceVoid do
def applicable?(_), do: false
def run({name, _, [args]}) do
def run({name, meta, args}) do
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
name = Parser.void_elements_lookup()[name]
Temple.Ast.new(
content: to_string(name),
meta: %{type: :temple_void},
attrs: args
)
Temple.Parser.VoidElementsAliases.run({name, meta, args})
end
@impl Parser

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.Text do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Buffer
alias Temple.Parser
alias Temple.Ast
@ -11,7 +13,11 @@ defmodule Temple.Parser.Text do
def applicable?(_), do: false
def run(text) do
Ast.new(content: text, meta: %{type: :text})
Ast.new(
__MODULE__,
content: text,
meta: %{type: :text}
)
end
@impl Parser
@ -21,4 +27,10 @@ defmodule Temple.Parser.Text do
:ok
end
defimpl Temple.EEx do
def to_eex(%{content: text}) do
[text]
end
end
end

View file

@ -2,6 +2,8 @@ defmodule Temple.Parser.VoidElementsAliases do
@moduledoc false
@behaviour Temple.Parser
defstruct content: nil, attrs: [], children: []
alias Temple.Parser
alias Temple.Buffer
@ -18,12 +20,24 @@ defmodule Temple.Parser.VoidElementsAliases do
name = Parser.void_elements_lookup()[name]
Temple.Ast.new(
__MODULE__,
content: name,
meta: %{type: :void_alias},
attrs: args
)
end
defimpl Temple.EEx do
def to_eex(%{content: content, attrs: attrs}) do
[
"<",
to_string(content),
Temple.Parser.Private.compile_attrs(attrs),
">\n"
]
end
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private

View file

@ -54,12 +54,10 @@ defmodule Temple.Parser.AnonymousFunctionsTest do
ast = AnonymousFunctions.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :anonymous_functions},
assert %AnonymousFunctions{
content: _,
children: [
%Temple.Ast{
meta: %{type: :default},
%Temple.Parser.Default{
content: ^expected_child,
children: []
}
@ -67,4 +65,24 @@ defmodule Temple.Parser.AnonymousFunctionsTest do
} = ast
end
end
describe "Temple.EEx.to_eex/1" do
test "emits eex" do
raw_ast =
quote do
form_for changeset, Routes.foo_path(conn, :create), fn form ->
Does.something!(form)
end
end
result =
raw_ast
|> AnonymousFunctions.run()
|> struct(children: [])
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= form_for changeset, Routes.foo_path(conn, :create), fn form -> %>\n<% end %>\n|
end
end
end

View file

@ -40,8 +40,7 @@ defmodule Temple.Parser.ComponentsTest do
ast = Components.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :component},
assert %Components{
content: SomeModule,
attrs: [],
children: _
@ -60,12 +59,60 @@ defmodule Temple.Parser.ComponentsTest do
ast = Components.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :component},
assert %Components{
content: SomeModule,
attrs: [foo: :bar],
children: _
} = ast
end
test "adds a node to the buffer that without a block" do
raw_ast =
quote do
c SomeModule, foo: :bar
end
ast = Components.run(raw_ast)
assert %Components{
content: SomeModule,
attrs: [foo: :bar],
children: []
} = ast
end
end
describe "Temple.EEx.to_eex/1" do
test "emits eex for non void component" do
raw_ast =
quote do
c SomeModule, foo: :bar do
"I'm a component!"
end
end
result =
raw_ast
|> Components.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= Phoenix.View.render_layout SomeModule, :self, [foo: :bar] do %>\nI'm a component!\n<% end %>|
end
test "emits eex for void component" do
raw_ast =
quote do
c SomeModule, foo: :bar
end
result =
raw_ast
|> Components.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= Phoenix.View.render SomeModule, :self, [foo: :bar] %>|
end
end
end

View file

@ -23,11 +23,23 @@ defmodule Temple.Parser.DefaultTest do
ast = Default.run(expression)
assert %Temple.Ast{
meta: %{type: :default},
assert %Default{
content: expression,
children: []
} == ast
end
end
describe "to_eex/1" do
test "emits eex" do
result =
quote do
Foo.bar!(baz)
end
|> Default.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() == ~s|<%= Foo.bar!(baz) %>|
end
end
end

View file

@ -27,26 +27,44 @@ defmodule Temple.Parser.DoExpressionsTest do
ast = DoExpressions.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :do_expression},
assert %DoExpressions{
content: _,
children: [
[
%Temple.Ast{
meta: %{type: :text},
content: "bob",
children: []
}
],
[
%Temple.Ast{
meta: %{type: :empty},
content: nil,
children: []
}
]
[%Temple.Parser.Text{content: "bob", children: []}], nil
]
} = ast
end
end
describe "to_eex/1" do
test "emits eex" do
result =
quote do
for big <- boys do
"bob"
end
end
|> DoExpressions.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= for(big <- boys) do %>\nbob\n<% end %>|
end
test "emits eex for that includes in else clause" do
result =
quote do
if foo? do
"bob"
else
"carol"
end
end
|> DoExpressions.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= if(foo?) do %>\nbob\n<% else %>\ncarol\n<% end %>|
end
end
end

View file

@ -26,12 +26,27 @@ defmodule Temple.Parser.EmptyTest do
for _ <- [nil, []] do
ast = Empty.run(nil)
assert %Temple.Ast{
meta: %{type: :empty},
assert %Empty{
content: nil,
children: []
} == ast
end
end
end
describe "Temple.EEx.to_eex/1" do
test "emits eex for non void component" do
raw_ast =
quote do
nil
end
result =
raw_ast
|> Empty.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() == ""
end
end
end

View file

@ -33,11 +33,43 @@ defmodule Temple.Parser.MatchTest do
ast = Match.run(expression)
assert %Temple.Ast{
meta: %{type: :match},
assert %Match{
content: expression,
children: []
} == ast
end
end
describe "Temple.EEx.to_eex/1" do
test "emits eex" do
raw_ast =
quote do
yolo = :synergy
end
result =
raw_ast
|> Match.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() == ~s|<% yolo = :synergy %>|
end
test "emits eex big boy" do
raw_ast =
quote do
yolo =
if true do
:synergy
end
end
result =
raw_ast
|> Match.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() == ~s|<% yolo = if(true) do\n :synergy\nend %>|
end
end
end

View file

@ -60,18 +60,17 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do
ast = NonvoidElementsAliases.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :nonvoid_alias},
assert %NonvoidElementsAliases{
content: "div",
attrs: [class: "foo", id: {:var, [], _}],
children: [
%Temple.Ast{
%NonvoidElementsAliases{
content: "select",
children: [
%Temple.Ast{
%NonvoidElementsAliases{
content: "option",
children: [
%Temple.Ast{content: "foo"}
%Temple.Parser.Text{content: "foo"}
]
}
]
@ -80,4 +79,24 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do
} = ast
end
end
describe "to_eex/1" do
test "emits eex" do
result =
quote do
div class: "foo", id: var do
select__ do
option do
"foo"
end
end
end
end
|> NonvoidElementsAliases.run()
|> 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>|
end
end
end

View file

@ -49,12 +49,10 @@ defmodule Temple.Parser.RightArrowTest do
ast = RightArrow.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :right_arrow},
assert %RightArrow{
content: :bing,
children: [
%Temple.Ast{
meta: %{type: :default},
%Temple.Parser.Default{
content: ^bong,
children: []
}
@ -62,4 +60,20 @@ defmodule Temple.Parser.RightArrowTest do
} = ast
end
end
describe "to_eex/1" do
test "emits eex" do
result =
quote do
:bing ->
:bong
end
|> List.first()
|> RightArrow.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<% :bing -> %>\n<%= :bong %>|
end
end
end

View file

@ -1,6 +1,7 @@
defmodule Temple.Parser.TempleNamespaceNonvoidTest do
use ExUnit.Case, async: true
alias Temple.Parser.NonvoidElementsAliases
alias Temple.Parser.TempleNamespaceNonvoid
describe "applicable?/1" do
@ -46,12 +47,27 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do
ast = TempleNamespaceNonvoid.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :temple_nonvoid},
assert %NonvoidElementsAliases{
content: "div",
attrs: [class: "foo", id: {:var, [], _}],
children: [%Temple.Ast{content: "foo"}]
children: [%Temple.Parser.Text{content: "foo"}]
} = ast
end
end
describe "to_eex/1" do
test "emits eex" do
result =
quote do
Temple.div class: "foo", id: var do
"foo"
end
end
|> TempleNamespaceNonvoid.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<div class="foo" id="<%= var %>">\nfoo\n</div>|
end
end
end

View file

@ -2,6 +2,7 @@ defmodule Temple.Parser.TempleNamespaceVoidTest do
use ExUnit.Case, async: true
alias Temple.Parser.TempleNamespaceVoid
alias Temple.Parser.VoidElementsAliases
describe "applicable?/1" do
test "returns true when the node is a Temple aliased nonvoid element" do
@ -42,12 +43,24 @@ defmodule Temple.Parser.TempleNamespaceVoidTest do
ast = TempleNamespaceVoid.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :temple_void},
content: "meta",
assert %VoidElementsAliases{
content: :meta,
attrs: [class: "foo", id: {:var, [], _}],
children: []
} = ast
end
end
describe "to_eex/1" do
test "emits eex" do
result =
quote do
Temple.meta(content: "foo")
end
|> TempleNamespaceVoid.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() == ~s|<meta content="foo">\n|
end
end
end

View file

@ -25,11 +25,21 @@ defmodule Temple.Parser.TextTest do
text = "string literal"
ast = Text.run(text)
assert %Temple.Ast{
meta: %{type: :text},
assert %Text{
content: text,
children: []
} == ast
end
end
describe "Temple.EEx.to_eex/1" do
test "emits eex" do
result =
"string literal"
|> Text.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() == ~s|string literal|
end
end
end

View file

@ -48,12 +48,24 @@ defmodule Temple.Parser.VoidElementsAliasesTest do
ast = VoidElementsAliases.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :void_alias},
assert %VoidElementsAliases{
content: :meta,
attrs: [content: "foo"],
children: []
} = ast
end
end
describe "to_eex/1" do
test "emits eex" do
result =
quote do
meta content: "foo"
end
|> VoidElementsAliases.run()
|> Temple.EEx.to_eex()
assert result |> :erlang.iolist_to_binary() == ~s|<meta content="foo">\n|
end
end
end