This basically just adds svg elements as void and nonvoid element
aliases and it works, will test on a real proejct before releasing the
next release.

Also, fixed the weird behaviour problem by defining types for each of the ast
nodes and then referencing those types when defining the ast type.

Unclear why this works, but I imagine it has to do with the types not
being a big part of the compilation process or something.

This also uses the typed_struct library to do so. Seems pretty slick and
does what it claims it does.
This commit is contained in:
Mitchell Hanberg 2022-09-19 20:35:45 -04:00 committed by GitHub
parent f9ccfbc718
commit 99cbb42962
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 345 additions and 93 deletions

View File

@ -1,5 +1,6 @@
locals_without_parens = ~w[
temple c slot
temple = ~w[temple c slot]a
html = ~w[
html head title style script
noscript template
body section nav article aside h1 h2 h3 h4 h5 h6
@ -12,22 +13,113 @@ locals_without_parens = ~w[
map svg math
table caption colgroup tbody thead tfoot tr td th
form fieldset legend label button select datalist optgroup
option text_area output progress meter
option textarea output progress meter
details summary menuitem menu
meta link base
area br col embed hr img input keygen param source track wbr
]a
animate animateMotion animateTransform circle clipPath
color-profile defs desc discard ellipse feBlend
feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow
feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset
fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter foreignObject g hatch hatchpath image line linearGradient
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
]a |> Enum.map(fn e -> {e, :*} end)
svg = ~w[
circle
ellipse
line
path
polygon
polyline
rect
stop
use
a
altGlyph
altGlyphDef
altGlyphItem
animate
animateColor
animateMotion
animateTransform
animation
audio
canvas
clipPath
cursor
defs
desc
discard
feBlend
feColorMatrix
feComponentTransfer
feComposite
feConvolveMatrix
feDiffuseLighting
feDisplacementMap
feDistantLight
feDropShadow
feFlood
feFuncA
feFuncB
feFuncG
feFuncR
feGaussianBlur
feImage
feMerge
feMergeNode
feMorphology
feOffset
fePointLight
feSpecularLighting
feSpotLight
feTile
feTurbulence
filter
font
foreignObject
g
glyph
glyphRef
handler
hatch
hatchpath
hkern
iframe
image
linearGradient
listener
marker
mask
mesh
meshgradient
meshpatch
meshrow
metadata
mpath
pattern
prefetch
radialGradient
script
set
solidColor
solidcolor
style
svg
switch
symbol
tbreak
text
textArea
textPath
title
tref
tspan
unknown
video
view
vkern
]a
locals_without_parens = Enum.map(temple ++ html ++ svg, &{&1, :*})
[
import_deps: [:typed_struct],
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"],
locals_without_parens: locals_without_parens,
export: [locals_without_parens: locals_without_parens]

View File

@ -5,7 +5,7 @@
> You are looking at the README for the main branch. The README for the latest stable release is located [here](https://github.com/mhanberg/temple/tree/v0.9.0).
Temple is an Elixir DSL for writing HTML.
Temple is an Elixir DSL for writing HTML and SVG.
## Installation

View File

@ -1,6 +1,19 @@
defmodule Temple.Ast do
@moduledoc false
@type t ::
Temple.Parser.Empty.t()
| Temple.Parser.Text.t()
| Temple.Parser.Components.t()
| Temple.Parser.Slot.t()
| Temple.Parser.NonvoidElementsAliases.t()
| Temple.Parser.VoidElementsAliases.t()
| Temple.Parser.AnonymousFunctions.t()
| Temple.Parser.RightArrow.t()
| Temple.Parser.DoExpressions.t()
| Temple.Parser.Match.t()
| Temple.Parser.Default.t()
def new(module, opts \\ []) do
struct(module, opts)
end

View File

@ -1,32 +1,21 @@
defmodule Temple.Parser do
@moduledoc false
alias Temple.Parser.AnonymousFunctions
alias Temple.Parser.Components
alias Temple.Parser.Default
alias Temple.Parser.DoExpressions
alias Temple.Parser.Empty
alias Temple.Parser.Text
alias Temple.Parser.Match
alias Temple.Parser.NonvoidElementsAliases
alias Temple.Parser.RightArrow
alias Temple.Parser.Slot
alias Temple.Parser.TempleNamespaceNonvoid
alias Temple.Parser.TempleNamespaceVoid
alias Temple.Parser.Components
alias Temple.Parser.Slot
alias Temple.Parser.NonvoidElementsAliases
alias Temple.Parser.Text
alias Temple.Parser.VoidElementsAliases
alias Temple.Parser.AnonymousFunctions
alias Temple.Parser.RightArrow
alias Temple.Parser.DoExpressions
alias Temple.Parser.Match
alias Temple.Parser.Default
@type ast ::
%Empty{}
| %Text{}
| %Components{}
| %Slot{}
| %NonvoidElementsAliases{}
| %VoidElementsAliases{}
| %AnonymousFunctions{}
| %RightArrow{}
| %DoExpressions{}
| %Match{}
| %Default{}
@aliases Application.compile_env(:temple, :aliases, [])
@doc """
Should return true if the parser should apply for the given AST.
@ -38,9 +27,113 @@ defmodule Temple.Parser do
Should return Temple.AST.
"""
@callback run(ast :: Macro.t()) :: ast()
@callback run(ast :: Macro.t()) :: Temple.Ast.t()
@aliases Application.get_env(:temple, :aliases, [])
@void_svg_lookup [
circle: "circle",
ellipse: "ellipse",
line: "line",
path: "path",
polygon: "polygon",
polyline: "polyline",
rect: "rect",
stop: "stop",
use: "use"
]
@void_svg_aliases Keyword.keys(@void_svg_lookup)
@nonvoid_svg_lookup [
a: "a",
altGlyph: "altGlyph",
altGlyphDef: "altGlyphDef",
altGlyphItem: "altGlyphItem",
animate: "animate",
animateColor: "animateColor",
animateMotion: "animateMotion",
animateTransform: "animateTransform",
animation: "animation",
audio: "audio",
canvas: "canvas",
clipPath: "clipPath",
cursor: "cursor",
defs: "defs",
desc: "desc",
discard: "discard",
feBlend: "feBlend",
feColorMatrix: "feColorMatrix",
feComponentTransfer: "feComponentTransfer",
feComposite: "feComposite",
feConvolveMatrix: "feConvolveMatrix",
feDiffuseLighting: "feDiffuseLighting",
feDisplacementMap: "feDisplacementMap",
feDistantLight: "feDistantLight",
feDropShadow: "feDropShadow",
feFlood: "feFlood",
feFuncA: "feFuncA",
feFuncB: "feFuncB",
feFuncG: "feFuncG",
feFuncR: "feFuncR",
feGaussianBlur: "feGaussianBlur",
feImage: "feImage",
feMerge: "feMerge",
feMergeNode: "feMergeNode",
feMorphology: "feMorphology",
feOffset: "feOffset",
fePointLight: "fePointLight",
feSpecularLighting: "feSpecularLighting",
feSpotLight: "feSpotLight",
feTile: "feTile",
feTurbulence: "feTurbulence",
filter: "filter",
font: "font",
foreignObject: "foreignObject",
g: "g",
glyph: "glyph",
glyphRef: "glyphRef",
handler: "handler",
hatch: "hatch",
hatchpath: "hatchpath",
hkern: "hkern",
iframe: "iframe",
image: "image",
linearGradient: "linearGradient",
listener: "listener",
marker: "marker",
mask: "mask",
mesh: "mesh",
meshgradient: "meshgradient",
meshpatch: "meshpatch",
meshrow: "meshrow",
metadata: "metadata",
mpath: "mpath",
pattern: "pattern",
prefetch: "prefetch",
radialGradient: "radialGradient",
script: "script",
set: "set",
solidColor: "solidColor",
solidcolor: "solidcolor",
style: "style",
svg: "svg",
switch: "switch",
symbol: "symbol",
tbreak: "tbreak",
text: "text",
textArea: "textArea",
textPath: "textPath",
title: "title",
tref: "tref",
tspan: "tspan",
unknown: "unknown",
video: "video",
view: "view",
vkern: "vkern"
]
@nonvoid_svg_aliases Keyword.keys(@nonvoid_svg_lookup)
# nonvoid tags
@nonvoid_elements ~w[
head title style script
@ -67,9 +160,9 @@ defmodule Temple.Parser do
{Keyword.get(@aliases, el, el), el}
end)
def nonvoid_elements, do: @nonvoid_elements
def nonvoid_elements_aliases, do: @nonvoid_elements_aliases
def nonvoid_elements_lookup, do: @nonvoid_elements_lookup
def nonvoid_elements, do: @nonvoid_elements ++ Keyword.values(@nonvoid_svg_aliases)
def nonvoid_elements_aliases, do: @nonvoid_elements_aliases ++ @nonvoid_svg_aliases
def nonvoid_elements_lookup, do: @nonvoid_elements_lookup ++ @nonvoid_svg_lookup
@void_elements ~w[
meta link base
@ -81,9 +174,9 @@ defmodule Temple.Parser do
{Keyword.get(@aliases, el, el), el}
end)
def void_elements, do: @void_elements
def void_elements_aliases, do: @void_elements_aliases
def void_elements_lookup, do: @void_elements_lookup
def void_elements, do: @void_elements ++ Keyword.values(@void_svg_aliases)
def void_elements_aliases, do: @void_elements_aliases ++ @void_svg_aliases
def void_elements_lookup, do: @void_elements_lookup ++ @void_svg_lookup
def parsers() do
[

View File

@ -2,11 +2,14 @@ defmodule Temple.Parser.AnonymousFunctions do
@moduledoc false
@behaviour Temple.Parser
defstruct elixir_ast: nil, children: []
use TypedStruct
alias Temple.Parser
typedstruct do
field :elixir_ast, Macro.t()
field :children, [map()]
end
@impl Parser
@impl true
def applicable?({_, _, args}) do
import Temple.Parser.Utils, only: [split_args: 1]
@ -18,7 +21,7 @@ defmodule Temple.Parser.AnonymousFunctions do
def applicable?(_), do: false
@impl Parser
@impl true
def run({_name, _, args} = expression) do
{_do_and_else, args} = Temple.Parser.Utils.split_args(args)

View File

@ -2,16 +2,23 @@ defmodule Temple.Parser.Components do
@moduledoc false
@behaviour Temple.Parser
defstruct function: nil, assigns: [], children: [], slots: []
use TypedStruct
@impl Temple.Parser
typedstruct do
field :function, function()
field :assigns, map()
field :children, [map()]
field :slots, [function()]
end
@impl true
def applicable?({:c, _, _}) do
true
end
def applicable?(_), do: false
@impl Temple.Parser
@impl true
def run({:c, _meta, [component_function | args]}) do
{do_and_else, args} =
args

View File

@ -2,14 +2,16 @@ defmodule Temple.Parser.Default do
@moduledoc false
@behaviour Temple.Parser
defstruct elixir_ast: nil
use TypedStruct
alias Temple.Parser
typedstruct do
field :elixir_ast, Macro.t()
end
@impl Parser
@impl true
def applicable?(_ast), do: true
@impl Parser
@impl true
def run(ast) do
Temple.Ast.new(__MODULE__, elixir_ast: ast)
end

View File

@ -1,19 +1,22 @@
defmodule Temple.Parser.DoExpressions do
@moduledoc false
alias Temple.Parser
@behaviour Temple.Parser
@behaviour Parser
use TypedStruct
defstruct elixir_ast: nil, children: []
typedstruct do
field :elixir_ast, Macro.t()
field :children, [map()]
end
@impl Parser
@impl true
def applicable?({_, _, args}) when is_list(args) do
Enum.any?(args, fn arg -> match?([{:do, _} | _], arg) end)
end
def applicable?(_), do: false
@impl Parser
@impl true
def run({name, meta, args}) do
{do_and_else, args} = Temple.Parser.Utils.split_args(args)

View File

@ -3,12 +3,17 @@ defmodule Temple.Parser.ElementList do
@behaviour Temple.Parser
defstruct children: [], whitespace: :loose
use TypedStruct
@impl Temple.Parser
typedstruct do
field :children, list()
field :whitespace, :loose | :tight
end
@impl true
def applicable?(asts), do: is_list(asts)
@impl Temple.Parser
@impl true
def run(asts) do
children = Enum.flat_map(asts, &Temple.Parser.parse/1)

View File

@ -1,16 +1,18 @@
defmodule Temple.Parser.Empty do
@moduledoc false
use TypedStruct
@behaviour Temple.Parser
defstruct []
typedstruct do
end
alias Temple.Parser
@impl Parser
@impl true
def applicable?(ast) when ast in [nil, []], do: true
def applicable?(_), do: false
@impl Parser
@impl true
def run(_ast) do
Temple.Ast.new(__MODULE__)
end

View File

@ -2,18 +2,20 @@ defmodule Temple.Parser.Match do
@moduledoc false
@behaviour Temple.Parser
defstruct elixir_ast: nil
use TypedStruct
alias Temple.Parser
typedstruct do
field :elixir_ast, Macro.t()
end
@impl Parser
@impl true
def applicable?({name, _, _}) do
name in [:=]
end
def applicable?(_), do: false
@impl Parser
@impl true
def run(macro) do
Temple.Ast.new(__MODULE__, elixir_ast: macro)
end

View File

@ -2,18 +2,25 @@ defmodule Temple.Parser.NonvoidElementsAliases do
@moduledoc false
@behaviour Temple.Parser
defstruct name: nil, attrs: [], children: [], meta: %{}
use TypedStruct
typedstruct do
field :name, atom()
field :attrs, list()
field :children, list()
field :meta, map()
end
alias Temple.Parser
@impl Parser
@impl true
def applicable?({name, _, _}) do
name in Parser.nonvoid_elements_aliases()
end
def applicable?(_), do: false
@impl Parser
@impl true
def run({name, meta, args}) do
name = Parser.nonvoid_elements_lookup()[name]

View File

@ -1,18 +1,22 @@
defmodule Temple.Parser.RightArrow do
@moduledoc false
alias Temple.Parser
@behaviour Parser
@behaviour Temple.Parser
defstruct elixir_ast: nil, children: []
use TypedStruct
@impl Parser
typedstruct do
field :elixir_ast, Macro.t()
field :children, [map()]
end
@impl true
def applicable?({:->, _, _}), do: true
def applicable?(_), do: false
@impl Parser
@impl true
def run({func, meta, [pattern, args]}) do
children = Parser.parse(args)
children = Temple.Parser.parse(args)
Temple.Ast.new(__MODULE__, elixir_ast: {func, meta, [pattern]}, children: children)
end

View File

@ -1,8 +1,12 @@
defmodule Temple.Parser.Slot do
@moduledoc false
@behaviour Temple.Parser
use TypedStruct
defstruct name: nil, args: []
typedstruct do
field :name, atom()
field :args, list(), default: []
end
@impl true
def applicable?({:slot, _, _}) do

View File

@ -1,5 +1,11 @@
defmodule Temple.Parser.Slottable do
@moduledoc false
defstruct content: nil, assigns: Macro.escape(%{}), name: nil
use TypedStruct
typedstruct do
field :content, any()
field :assigns, map(), default: Macro.escape(%{})
field :name, atom()
end
end

View File

@ -4,14 +4,14 @@ defmodule Temple.Parser.TempleNamespaceNonvoid do
alias Temple.Parser
@impl Parser
@impl true
def applicable?({{:., _, [{:__aliases__, _, [:Temple]}, name]}, _meta, _args}) do
name in Parser.nonvoid_elements_aliases()
end
def applicable?(_), do: false
@impl Parser
@impl true
def run({name, meta, args}) do
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
Temple.Parser.NonvoidElementsAliases.run({name, meta, args})

View File

@ -2,14 +2,14 @@ defmodule Temple.Parser.TempleNamespaceVoid do
@moduledoc false
@behaviour Temple.Parser
@impl Temple.Parser
@impl true
def applicable?({{:., _, [{:__aliases__, _, [:Temple]}, name]}, _meta, _args}) do
name in Temple.Parser.void_elements_aliases()
end
def applicable?(_), do: false
@impl Temple.Parser
@impl true
def run({name, meta, args}) do
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name

View File

@ -2,15 +2,17 @@ defmodule Temple.Parser.Text do
@moduledoc false
@behaviour Temple.Parser
defstruct text: nil
use TypedStruct
alias Temple.Parser
typedstruct do
field :text, String.t()
end
@impl Parser
@impl true
def applicable?(text) when is_binary(text), do: true
def applicable?(_), do: false
@impl Parser
@impl true
def run(text) do
Temple.Ast.new(__MODULE__, text: text)
end

View File

@ -2,16 +2,21 @@ defmodule Temple.Parser.VoidElementsAliases do
@moduledoc false
@behaviour Temple.Parser
defstruct name: nil, attrs: []
use TypedStruct
@impl Temple.Parser
typedstruct do
field :name, atom()
field :attrs, list(), default: []
end
@impl true
def applicable?({name, _, _}) do
name in Temple.Parser.void_elements_aliases()
end
def applicable?(_), do: false
@impl Temple.Parser
@impl true
def run({name, _, args}) do
args =
case Temple.Parser.Utils.split_args(args) do

View File

@ -61,6 +61,7 @@ defmodule Temple.MixProject do
defp deps do
[
{:typed_struct, "~> 0.3"},
{:floki, ">= 0.0.0"},
{:ex_doc, "~> 0.28.3", only: :dev, runtime: false}
]

View File

@ -7,4 +7,5 @@
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"},
}

View File

@ -61,7 +61,7 @@ defmodule Temple.ConverterTest do
</style>
"""
assert Converter.convert(html) |> tap(&IO.puts/1) ===
assert Converter.convert(html) ===
"""
script do
"console.log(\\"ayy yoo\\");"