diff --git a/.formatter.exs b/.formatter.exs index 2d7172e..eb039f1 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -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] diff --git a/README.md b/README.md index 520f586..2936982 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/temple/ast.ex b/lib/temple/ast.ex index e7d6995..8c27a78 100644 --- a/lib/temple/ast.ex +++ b/lib/temple/ast.ex @@ -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 diff --git a/lib/temple/parser.ex b/lib/temple/parser.ex index 7ef078a..0046824 100644 --- a/lib/temple/parser.ex +++ b/lib/temple/parser.ex @@ -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 [ diff --git a/lib/temple/parser/anonymous_functions.ex b/lib/temple/parser/anonymous_functions.ex index f0a41db..03f059c 100644 --- a/lib/temple/parser/anonymous_functions.ex +++ b/lib/temple/parser/anonymous_functions.ex @@ -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) diff --git a/lib/temple/parser/components.ex b/lib/temple/parser/components.ex index 81a0fc0..8bf8203 100644 --- a/lib/temple/parser/components.ex +++ b/lib/temple/parser/components.ex @@ -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 diff --git a/lib/temple/parser/default.ex b/lib/temple/parser/default.ex index e48bf38..39f481a 100644 --- a/lib/temple/parser/default.ex +++ b/lib/temple/parser/default.ex @@ -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 diff --git a/lib/temple/parser/do_expressions.ex b/lib/temple/parser/do_expressions.ex index a842cb6..ebad04f 100644 --- a/lib/temple/parser/do_expressions.ex +++ b/lib/temple/parser/do_expressions.ex @@ -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) diff --git a/lib/temple/parser/element_list.ex b/lib/temple/parser/element_list.ex index 55da997..ecdcbf1 100644 --- a/lib/temple/parser/element_list.ex +++ b/lib/temple/parser/element_list.ex @@ -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) diff --git a/lib/temple/parser/empty.ex b/lib/temple/parser/empty.ex index a8f7bc2..25dd8d3 100644 --- a/lib/temple/parser/empty.ex +++ b/lib/temple/parser/empty.ex @@ -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 diff --git a/lib/temple/parser/match.ex b/lib/temple/parser/match.ex index b1a794e..1f6e0d5 100644 --- a/lib/temple/parser/match.ex +++ b/lib/temple/parser/match.ex @@ -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 diff --git a/lib/temple/parser/nonvoid_elements_aliases.ex b/lib/temple/parser/nonvoid_elements_aliases.ex index faee2b6..76a1a98 100644 --- a/lib/temple/parser/nonvoid_elements_aliases.ex +++ b/lib/temple/parser/nonvoid_elements_aliases.ex @@ -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] diff --git a/lib/temple/parser/right_arrow.ex b/lib/temple/parser/right_arrow.ex index 9af20d1..1e7e8e9 100644 --- a/lib/temple/parser/right_arrow.ex +++ b/lib/temple/parser/right_arrow.ex @@ -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 diff --git a/lib/temple/parser/slot.ex b/lib/temple/parser/slot.ex index 426899a..f513fdc 100644 --- a/lib/temple/parser/slot.ex +++ b/lib/temple/parser/slot.ex @@ -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 diff --git a/lib/temple/parser/slottable.ex b/lib/temple/parser/slottable.ex index c047a1f..3c32485 100644 --- a/lib/temple/parser/slottable.ex +++ b/lib/temple/parser/slottable.ex @@ -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 diff --git a/lib/temple/parser/temple_namespace_nonvoid.ex b/lib/temple/parser/temple_namespace_nonvoid.ex index 696fab2..1bb2bcc 100644 --- a/lib/temple/parser/temple_namespace_nonvoid.ex +++ b/lib/temple/parser/temple_namespace_nonvoid.ex @@ -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}) diff --git a/lib/temple/parser/temple_namespace_void.ex b/lib/temple/parser/temple_namespace_void.ex index e86778a..2545f40 100644 --- a/lib/temple/parser/temple_namespace_void.ex +++ b/lib/temple/parser/temple_namespace_void.ex @@ -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 diff --git a/lib/temple/parser/text.ex b/lib/temple/parser/text.ex index cfc50da..0d7cc65 100644 --- a/lib/temple/parser/text.ex +++ b/lib/temple/parser/text.ex @@ -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 diff --git a/lib/temple/parser/void_elements_aliases.ex b/lib/temple/parser/void_elements_aliases.ex index 22ccc74..7ba4d96 100644 --- a/lib/temple/parser/void_elements_aliases.ex +++ b/lib/temple/parser/void_elements_aliases.ex @@ -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 diff --git a/mix.exs b/mix.exs index e692367..2b11d67 100644 --- a/mix.exs +++ b/mix.exs @@ -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} ] diff --git a/mix.lock b/mix.lock index cebe483..24c6723 100644 --- a/mix.lock +++ b/mix.lock @@ -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"}, } diff --git a/test/temple/converter_test.exs b/test/temple/converter_test.exs index 01562dc..7120c29 100644 --- a/test/temple/converter_test.exs +++ b/test/temple/converter_test.exs @@ -61,7 +61,7 @@ defmodule Temple.ConverterTest do """ - assert Converter.convert(html) |> tap(&IO.puts/1) === + assert Converter.convert(html) === """ script do "console.log(\\"ayy yoo\\");"