Skip to content

Commit

Permalink
SVG (#181)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mhanberg authored Sep 20, 2022
1 parent f9ccfbc commit 99cbb42
Show file tree
Hide file tree
Showing 22 changed files with 346 additions and 94 deletions.
116 changes: 104 additions & 12 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

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

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)
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]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 13 additions & 0 deletions lib/temple/ast.ex
Original file line number Diff line number Diff line change
@@ -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
Expand Down
153 changes: 123 additions & 30 deletions lib/temple/parser.ex
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -38,9 +27,113 @@ defmodule Temple.Parser do
Should return Temple.AST.
"""
@callback run(ast :: Macro.t()) :: ast()

@aliases Application.get_env(:temple, :aliases, [])
@callback run(ast :: Macro.t()) :: Temple.Ast.t()

@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
Expand All @@ -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
Expand All @@ -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
[
Expand Down
11 changes: 7 additions & 4 deletions lib/temple/parser/anonymous_functions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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)

Expand Down
Loading

0 comments on commit 99cbb42

Please sign in to comment.