feat: Mix task to convert HTML into Temple (#180)
This commit is contained in:
parent
e28504a037
commit
2f042506b6
9 changed files with 258 additions and 9 deletions
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
|
@ -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'
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
elixir ref:v1.13.4
|
||||
elixir 1.14.0-otp-25
|
||||
erlang 25.0-rc2
|
||||
|
|
|
@ -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
25
guides/converting-html.md
Normal 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!
|
34
lib/mix/tasks/temple.convert.ex
Normal file
34
lib/mix/tasks/temple.convert.ex
Normal 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
103
lib/temple/converter.ex
Normal 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
|
2
mix.exs
2
mix.exs
|
@ -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
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -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"},
|
||||
|
|
79
test/temple/converter_test.exs
Normal file
79
test/temple/converter_test.exs
Normal 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
|
Reference in a new issue