feat: Mix task to convert HTML into Temple (#180)

This commit is contained in:
Mitchell Hanberg 2022-09-11 22:39:31 -04:00 committed by GitHub
parent e28504a037
commit 2f042506b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 258 additions and 9 deletions

View File

@ -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
@ -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'

View File

@ -1,2 +1,2 @@
elixir ref:v1.13.4
elixir 1.14.0-otp-25
erlang 25.0-rc2

View File

@ -2,7 +2,11 @@
## Main
### 0.9.0-rc.0
### Enhancements
- mix temple.convert task to convert HTML into Temple syntax.
### 0.9.0
### Breaking Changes

25
guides/converting-html.md Normal file
View File

@ -0,0 +1,25 @@
# Converting HTML
If you want to use something like [TailwindUI](https://tailwindui.com) with Temple, you're going to have to convert a ton of vanilla HTML into Temple syntax.
Luckily, Temple provides a mix task for converting an HTML file into Temple syntax and writes it to stdout.
## Usage
First, we would want to create a temporary HTML file with the HTML we'd like to convert.
> #### Hint {: .tip}
>
> The following examples use the `pbpaste` and `pbcopy` utilities found on macOS. These are used to send your clipboard contents into stdout and put stdout into your clipboard.
```shell
$ pbpaste > temp.html
```
Then, we can convert that file and copy the output into our clipboard.
```shell
$ mix temple.convert temp.html | pbcopy
```
Now, you are free to paste the new temple syntax into your project!

View File

@ -0,0 +1,34 @@
defmodule Mix.Tasks.Temple.Convert do
use Mix.Task
@shortdoc "A task to convert vanilla HTML into Temple syntax"
@moduledoc """
This task is useful for converting a ton of HTML into Temple syntax.
> #### Note about EEx and HEEx {: .tip}
>
> In the future, this should be able to convert EEx and HEEx as well, but that would involve invoking or forking their parsers. That is certainly doable, but is out of scope for what I needed right now. Contributions are welcome!
## Usage
```shell
$ mix temple.convert some_file.html
```
"""
@doc false
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

103
lib/temple/converter.ex Normal file
View File

@ -0,0 +1,103 @@
defmodule Temple.Converter do
@moduledoc false
@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

View File

@ -36,6 +36,7 @@ defmodule Temple.MixProject do
"guides/getting-started.md",
"guides/your-first-template.md",
"guides/components.md",
"guides/converting-html.md",
"guides/migrating/0.8-to-0.9.md"
],
groups_for_extras: groups_for_extras()
@ -60,6 +61,7 @@ defmodule Temple.MixProject do
defp deps do
[
{:floki, ">= 0.0.0"},
{:ex_doc, "~> 0.28.3", only: :dev, runtime: false}
]
end

View File

@ -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"},

View File

@ -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