Skip to content

Commit

Permalink
Better whitespace handling and control (#145)
Browse files Browse the repository at this point in the history
* Fine tune whitespace

The EEx outut now emits more human-readable and predictable formatting.
This includes proper indenting, at least for each "root" template.

* Internal whitespace control

You can now use a bang version of any nonvoid tag to emit the markup
witout the internal whitespace. This means that there will not be a
newline emitted after the opening tag and before the closing tag.
  • Loading branch information
mhanberg committed Aug 29, 2021
1 parent 87ddbaa commit c965048
Show file tree
Hide file tree
Showing 32 changed files with 613 additions and 148 deletions.
9 changes: 1 addition & 8 deletions .formatter.exs
Expand Up @@ -16,7 +16,6 @@ locals_without_parens = ~w[
details summary menuitem menu
meta link base
area br col embed hr img input keygen param source track wbr
txt partial
animate animateMotion animateTransform circle clipPath
color-profile defs desc discard ellipse feBlend
Expand All @@ -26,13 +25,7 @@ locals_without_parens = ~w[
marker mask mesh meshgradient meshpatch meshrow metadata mpath path pattern polygon
polyline radialGradient rect set solidcolor stop svg switch symbol text
textPath tspan unknown use view
form_for inputs_for
checkbox color_input checkbox color_input date_input date_select datetime_local_input
datetime_select email_input file_input hidden_input number_input password_input range_input
search_input telephone_input textarea text_input time_input time_select url_input
reset submit phx_label radio_button multiple_select select phx_link phx_button
]a |> Enum.map(fn e -> {e, :*} end)
]a |> Enum.flat_map(fn e -> [{e, :*}, {:"#{e}!", :*}] end)

[
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"],
Expand Down
39 changes: 35 additions & 4 deletions lib/temple.ex
Expand Up @@ -29,7 +29,7 @@ defmodule Temple do
# The class attribute also can take a keyword list of classes to conditionally render, based on the boolean result of the value.
div class: ["text-red-500": false, "text-green-500": true ] do
div class: ["text-red-500": false, "text-green-500": true] do
"Alert!"
end
Expand Down Expand Up @@ -71,6 +71,34 @@ defmodule Temple do
end
```
## Whitespace Control
By default, Temple will emit internal whitespace into tags, something like this.
```elixir
span do
"Hello, world!"
end
```
```html
<span>
Hello, world!
</span>
```
If you need to create a "tight" tag, you can call the "bang" version of the desired tag.
```elixir
span! do
"Hello, world!"
end
```
```html
<span>Hello, world!</span>
```
## Configuration
### Mode
Expand Down Expand Up @@ -151,7 +179,8 @@ defmodule Temple do
markup =
block
|> Parser.parse()
|> Enum.map(&Temple.Generator.to_eex/1)
|> Enum.map(fn parsed -> Temple.Generator.to_eex(parsed, 0) end)
|> Enum.intersperse("\n")
|> :erlang.iolist_to_binary()

quote location: :keep do
Expand All @@ -163,7 +192,8 @@ defmodule Temple do
quote location: :keep do
unquote(block)
|> Parser.parse()
|> Enum.map(&Temple.Generator.to_eex/1)
|> Enum.map(fn parsed -> Temple.Generator.to_eex(parsed, 0) end)
|> Enum.intersperse("\n")
|> :erlang.iolist_to_binary()
end
end
Expand All @@ -190,7 +220,8 @@ defmodule Temple do
markup =
block
|> Parser.parse()
|> Enum.map(&Temple.Generator.to_eex/1)
|> Enum.map(fn parsed -> Temple.Generator.to_eex(parsed, 0) end)
|> Enum.intersperse("\n")
|> :erlang.iolist_to_binary()

EEx.compile_string(markup, engine: engine, line: __CALLER__.line, file: __CALLER__.file)
Expand Down
2 changes: 1 addition & 1 deletion lib/temple/generator.ex
@@ -1,5 +1,5 @@
defprotocol Temple.Generator do
@moduledoc false

def to_eex(ast)
def to_eex(ast, indent \\ 0)
end
2 changes: 1 addition & 1 deletion lib/temple/parser.ex
Expand Up @@ -58,7 +58,7 @@ defmodule Temple.Parser do
option textarea output progress meter
details summary menuitem menu
html
]a
]a |> Enum.flat_map(fn el -> [el, :"#{el}!"] end)

@nonvoid_elements_aliases Enum.map(@nonvoid_elements, fn el ->
Keyword.get(@aliases, el, el)
Expand Down
10 changes: 5 additions & 5 deletions lib/temple/parser/anonymous_functions.ex
Expand Up @@ -32,14 +32,14 @@ defmodule Temple.Parser.AnonymousFunctions do
end

defimpl Temple.Generator do
def to_eex(%{elixir_ast: {name, _, args}, children: children}) do
def to_eex(%{elixir_ast: {name, _, args}, children: children}, indent \\ 0) do
{_do_and_else, args} = Temple.Parser.Utils.split_args(args)

{args, {func, _, [{arrow, _, [[{arg, _, _}], _block]}]}, args2} =
Temple.Parser.Utils.split_on_fn(args, {[], nil, []})

[
"<%= ",
"#{Parser.Utils.indent(indent)}<%= ",
to_string(name),
" ",
Enum.map(args, &Macro.to_string(&1)) |> Enum.join(", "),
Expand All @@ -51,16 +51,16 @@ defmodule Temple.Parser.AnonymousFunctions do
to_string(arrow),
" %>",
"\n",
for(child <- children, do: Temple.Generator.to_eex(child)),
for(child <- children, do: Temple.Generator.to_eex(child, indent + 1)),
if Enum.any?(args2) do
[
"<% end, ",
"#{Parser.Utils.indent(indent)}<% end, ",
Enum.map(args2, fn arg -> Macro.to_string(arg) end)
|> Enum.join(", "),
" %>"
]
else
["<% end %>", "\n"]
["#{Parser.Utils.indent(indent)}<% end %>", "\n"]
end
]
end
Expand Down
17 changes: 9 additions & 8 deletions lib/temple/parser/components.ex
Expand Up @@ -2,6 +2,8 @@ defmodule Temple.Parser.Components do
@moduledoc false
@behaviour Temple.Parser

alias Temple.Parser

defstruct module: nil, assigns: [], children: [], slots: []

@impl Temple.Parser
Expand Down Expand Up @@ -88,12 +90,12 @@ defmodule Temple.Parser.Components do
end

defimpl Temple.Generator do
def to_eex(%{module: module, assigns: assigns, children: children, slots: slots}) do
def to_eex(%{module: module, assigns: assigns, children: children, slots: slots}, indent \\ 0) do
component_function = Temple.Config.mode().component_function
renderer = Temple.Config.mode().renderer.(module)

[
"<%= #{component_function} ",
"#{Parser.Utils.indent(indent)}<%= #{component_function} ",
renderer,
", ",
Macro.to_string(assigns),
Expand All @@ -102,23 +104,22 @@ defmodule Temple.Parser.Components do
" do %>\n",
if not Enum.empty?(children) do
[
"<% {:default, _} -> %>\n",
for(child <- children, do: Temple.Generator.to_eex(child)),
"\n"
"#{Parser.Utils.indent(indent + 1)}<% {:default, _} -> %>\n",
for(child <- children, do: Temple.Generator.to_eex(child, indent + 2))
]
else
""
end,
for slot <- slots do
[
"<% {:",
"#{Parser.Utils.indent(indent + 1)}<% {:",
to_string(slot.name),
", ",
"#{Macro.to_string(slot.assigns)}} -> %>\n",
for(child <- slot.content, do: Temple.Generator.to_eex(child))
for(child <- slot.content, do: Temple.Generator.to_eex(child, indent + 2))
]
end,
"<% end %>"
"\n#{Parser.Utils.indent(indent)}<% end %>"
]
else
" %>"
Expand Down
4 changes: 2 additions & 2 deletions lib/temple/parser/default.ex
Expand Up @@ -15,8 +15,8 @@ defmodule Temple.Parser.Default do
end

defimpl Temple.Generator do
def to_eex(%{elixir_ast: expression}) do
["<%= ", Macro.to_string(expression), " %>\n"]
def to_eex(%{elixir_ast: expression}, indent \\ 0) do
["#{Parser.Utils.indent(indent)}<%= ", Macro.to_string(expression), " %>"]
end
end
end
15 changes: 10 additions & 5 deletions lib/temple/parser/do_expressions.ex
Expand Up @@ -30,18 +30,23 @@ defmodule Temple.Parser.DoExpressions do
end

defimpl Temple.Generator do
def to_eex(%{elixir_ast: expression, children: [do_body, else_body]}) do
def to_eex(%{elixir_ast: expression, children: [do_body, else_body]}, indent \\ 0) do
[
"<%= ",
"#{Parser.Utils.indent(indent)}<%= ",
Macro.to_string(expression),
" do %>",
"\n",
for(child <- do_body, do: Temple.Generator.to_eex(child)),
for(child <- do_body, do: Temple.Generator.to_eex(child, indent + 1))
|> Enum.intersperse("\n"),
if(else_body != nil,
do: ["<% else %>\n", for(child <- else_body, do: Temple.Generator.to_eex(child))],
do: [
"#{Parser.Utils.indent(indent)}\n<% else %>\n",
for(child <- else_body, do: Temple.Generator.to_eex(child, indent + 1))
|> Enum.intersperse("\n")
],
else: ""
),
"<% end %>"
"\n#{Parser.Utils.indent(indent)}<% end %>"
]
end
end
Expand Down
33 changes: 33 additions & 0 deletions lib/temple/parser/element_list.ex
@@ -0,0 +1,33 @@
defmodule Temple.Parser.ElementList do
@moduledoc false

@behaviour Temple.Parser

defstruct children: [], whitespace: :loose

@impl Temple.Parser
def applicable?(asts), do: is_list(asts)

@impl Temple.Parser
def run(asts) do
children = Enum.flat_map(asts, &Temple.Parser.parse/1)

Temple.Ast.new(__MODULE__, children: children)
end

defimpl Temple.Generator do
def to_eex(%{children: children, whitespace: whitespace}, indent \\ 0) do
child_indent = if whitespace == :loose, do: indent + 1, else: 0
self_indent = if whitespace == :loose, do: indent, else: 0
whitespace = if whitespace == :tight, do: [], else: ["\n"]

[
whitespace,
for(child <- children, do: Temple.Generator.to_eex(child, child_indent))
|> Enum.intersperse("\n"),
whitespace,
Temple.Parser.Utils.indent(self_indent)
]
end
end
end
2 changes: 1 addition & 1 deletion lib/temple/parser/empty.ex
Expand Up @@ -16,7 +16,7 @@ defmodule Temple.Parser.Empty do
end

defimpl Temple.Generator do
def to_eex(_) do
def to_eex(_, _ \\ 0) do
[]
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/temple/parser/match.ex
Expand Up @@ -19,8 +19,8 @@ defmodule Temple.Parser.Match do
end

defimpl Temple.Generator do
def to_eex(%{elixir_ast: elixir_ast}) do
["<% ", Macro.to_string(elixir_ast), " %>"]
def to_eex(%{elixir_ast: elixir_ast}, indent \\ 0) do
["#{Parser.Utils.indent(indent)}<% ", Macro.to_string(elixir_ast), " %>"]
end
end
end
30 changes: 23 additions & 7 deletions lib/temple/parser/nonvoid_elements_aliases.ex
Expand Up @@ -2,7 +2,7 @@ defmodule Temple.Parser.NonvoidElementsAliases do
@moduledoc false
@behaviour Temple.Parser

defstruct name: nil, attrs: [], children: []
defstruct name: nil, attrs: [], children: [], meta: %{}

alias Temple.Parser

Expand All @@ -23,18 +23,34 @@ defmodule Temple.Parser.NonvoidElementsAliases do

children = Temple.Parser.parse(do_and_else[:do])

Temple.Ast.new(__MODULE__, name: to_string(name), attrs: args, children: children)
Temple.Ast.new(__MODULE__,
name: to_string(name) |> String.replace_suffix("!", ""),
attrs: args,
children:
Temple.Ast.new(Temple.Parser.ElementList,
children: children,
whitespace: whitespace(to_string(name))
)
)
end

defp whitespace(name) do
if String.ends_with?(name, "!") do
:tight
else
:loose
end
end

defimpl Temple.Generator do
def to_eex(%{name: name, attrs: attrs, children: children}) do
def to_eex(%{name: name, attrs: attrs, children: children}, indent \\ 0) do
[
"<",
"#{Parser.Utils.indent(indent)}<",
name,
Temple.Parser.Utils.compile_attrs(attrs),
">\n",
for(child <- children, do: Temple.Generator.to_eex(child)),
"\n</",
">",
Temple.Generator.to_eex(children, indent),
"</",
name,
">"
]
Expand Down
6 changes: 3 additions & 3 deletions lib/temple/parser/right_arrow.ex
Expand Up @@ -18,12 +18,12 @@ defmodule Temple.Parser.RightArrow do
end

defimpl Temple.Generator do
def to_eex(%{elixir_ast: elixir_ast, children: children}) do
def to_eex(%{elixir_ast: elixir_ast, children: children}, indent \\ 0) do
[
"<% ",
"#{Parser.Utils.indent(indent)}<% ",
Macro.to_string(elixir_ast),
" -> %>\n",
for(child <- children, do: Temple.Generator.to_eex(child))
for(child <- children, do: Temple.Generator.to_eex(child, indent + 1))
]
end
end
Expand Down
7 changes: 4 additions & 3 deletions lib/temple/parser/slot.ex
@@ -1,6 +1,7 @@
defmodule Temple.Parser.Slot do
@moduledoc false
@behaviour Temple.Parser
alias Temple.Parser.Utils

defstruct name: nil, args: []

Expand All @@ -26,15 +27,15 @@ defmodule Temple.Parser.Slot do
end

defimpl Temple.Generator do
def to_eex(%{name: name, args: args}) do
def to_eex(%{name: name, args: args}, indent \\ 0) do
render_block_function = Temple.Config.mode().render_block_function

[
"<%= #{render_block_function}(@inner_block, {:",
"#{Utils.indent(indent)}<%= #{render_block_function}(@inner_block, {:",
to_string(name),
", ",
Macro.to_string(quote(do: Enum.into(unquote(args), %{}))),
"}) %>"
"}) %>\n"
]
end
end
Expand Down

0 comments on commit c965048

Please sign in to comment.