Skip to content

Commit

Permalink
feat: Mix task to convert HTML into Temple
Browse files Browse the repository at this point in the history
  • Loading branch information
mhanberg committed Sep 12, 2022
1 parent e28504a commit a00c092
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 9 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ jobs:

strategy:
matrix:
otp: [23.x, 24.x]
elixir: [1.13.x]
otp: [23.x, 24.x, 25.x]
elixir: [1.13.x, 1.14.x]

steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -86,22 +86,22 @@ jobs:

formatter:
runs-on: ubuntu-latest
name: Formatter (1.13.x.x/24.x)
name: Formatter (1.14.x.x/25.x)

steps:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1
with:
otp-version: 24.x
elixir-version: 1.13.x
otp-version: 25.x
elixir-version: 1.14.x
- uses: actions/cache@v3
with:
path: |
deps
_build
key: ${{ runner.os }}-mix-24-1.13-${{ hashFiles('**/mix.lock') }}
key: ${{ runner.os }}-mix-23-1.14-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-24-1.13-
${{ runner.os }}-mix-23-1.14-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
Expand Down
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir ref:v1.13.4
elixir 1.14.0-otp-25
erlang 25.0-rc2
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Main

### 0.9.0-rc.0
### 0.9.0

### Breaking Changes

Expand Down
20 changes: 20 additions & 0 deletions lib/mix/tasks/temple.convert.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Mix.Tasks.Temple.Convert do
use Mix.Task

@shortdoc "A task to convert vanilla HTML into Temple syntax"

def run(argv) do
case argv do
[] ->
Mix.raise(
"You need to provide the path to an HTML file you would like to convert to Temple syntax"
)

[file] ->
file
|> File.read!()
|> Temple.Converter.convert()
|> IO.puts()
end
end
end
101 changes: 101 additions & 0 deletions lib/temple/converter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
defmodule Temple.Converter do
@boolean_attributes ~w[
allowfullscreen
async
autofocus
autoplay
checked
controls
default
defer
disabled
formnovalidate
ismap
itemscope
loop
multiple
muted
nomodule
novalidate
open
playsinline
readonly
required
reversed
selected
truespeed
]

def convert(html) do
html
|> Floki.parse_fragment!()
|> to_temple()
|> :erlang.iolist_to_binary()
|> Code.format_string!()
|> :erlang.iolist_to_binary()
end

def to_temple([]) do
[]
end

def to_temple([{tag, attrs, children} | rest]) do
[
to_string(tag),
" ",
to_temple_attrs(attrs),
" do\n",
to_temple(children),
"end\n"
] ++ to_temple(rest)
end

def to_temple([{:comment, comment} | rest]) do
[
comment
|> String.split("\n")
|> Enum.map_join("\n", fn line ->
if String.trim(line) != "" do
"# #{line}"
else
""
end
end),
"\n"
] ++ to_temple(rest)
end

def to_temple([text | rest]) when is_binary(text) do
[
text
|> String.split("\n")
|> Enum.map_join("\n", fn line ->
if String.trim(line) != "" do
escaped = String.replace(line, ~s|"|, ~s|\\"|)
~s|"#{String.trim(escaped)}"|
else
""
end
end),
"\n"
] ++ to_temple(rest)
end

defp to_temple_attrs([]) do
""
end

defp to_temple_attrs(attrs) do
Enum.map_join(attrs, ", ", fn
{attr, _value} when attr in @boolean_attributes ->
to_attr_name(attr) <> ": true"

{attr, value} ->
~s|#{to_attr_name(attr)}: "#{value}"|
end)
end

defp to_attr_name(name) do
String.replace(name, "-", "_")
end
end
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ defmodule Temple.MixProject do

defp deps do
[
{:floki, ">= 0.0.0"},
{:ex_doc, "~> 0.28.3", only: :dev, runtime: false}
]
end
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
%{
"earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"},
"ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"},
"floki": {:hex, :floki, "0.33.1", "f20f1eb471e726342b45ccb68edb9486729e7df94da403936ea94a794f072781", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "461035fd125f13fdf30f243c85a0b1e50afbec876cbf1ceefe6fddd2e6d712c6"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"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"},
Expand Down
79 changes: 79 additions & 0 deletions test/temple/converter_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
defmodule Temple.ConverterTest do
use ExUnit.Case, async: true

alias Temple.Converter

describe "convert/1" do
test "converts basic html" do
# html
html = """
<div class="container" disabled aria-label="alice">
<!-- this is a comment -->
I'm some content!
</div>
"""

assert Converter.convert(html) ===
"""
div class: "container", disabled: true, aria_label: "alice" do
# this is a comment
"I'm some content!"
end
"""
|> String.trim()
end

test "multiline html comments" do
# html
html = """
<div >
<!-- this is a comment
and this is some multi
stuff -->
</div>
"""

assert Converter.convert(html) ===
"""
div do
# this is a comment
# and this is some multi
# stuff
end
"""
|> String.trim()
end

test "script and style tag" do
# html
html = """
<script>
console.log("ayy yoo");
</script>
<style>
.foo {
color: red;
}
</style>
"""

assert Converter.convert(html) |> tap(&IO.puts/1) ===
"""
script do
"console.log(\\"ayy yoo\\");"
end
style do
".foo {"
"color: red;"
"}"
end
"""
|> String.trim()
end
end
end

0 comments on commit a00c092

Please sign in to comment.