diff --git a/lib/mix/tasks/convert_to_heex.ex b/lib/mix/tasks/convert_to_heex.ex deleted file mode 100644 index ad811f2c..00000000 --- a/lib/mix/tasks/convert_to_heex.ex +++ /dev/null @@ -1,247 +0,0 @@ -defmodule Mix.Tasks.ConvertToHeex do - @moduledoc """ - Converts all slime files in the repository to HEEx. - - This file is substantially based on Slime's compiler, which can be found here: - https://github.com/slime-lang/slime/blob/master/lib/slime/compiler.ex - """ - - use Mix.Task - - alias Slime.Parser.Nodes.{ - DoctypeNode, - EExNode, - EExCommentNode, - HTMLCommentNode, - HTMLNode, - InlineHTMLNode, - VerbatimTextNode - } - - alias Slime.Doctype - - @void_elements ~w( - area br col doctype embed hr img input link meta base param - keygen source menuitem track wbr - ) - - @indent " " - - def run(_) do - Path.wildcard("lib/**/*.html.slime") - |> Enum.sort() - |> Enum.each(&format_file/1) - - :ok - end - - defp format_file(filename) do - Mix.shell().info(filename) - - formatted_content = - filename - |> File.read!() - |> format_string() - - heex_filename = String.replace(filename, ".html.slime", ".html.heex") - - File.write!(heex_filename, [formatted_content]) - File.rm!(filename) - end - - defp format_string(source) do - tree = Slime.Parser.parse(source) - compile(tree, "") - end - - defp compile(tags, indent) when is_list(tags) do - Enum.map(tags, &compile(&1, indent)) - end - - defp compile(%DoctypeNode{name: name}, indent), do: [indent, Doctype.for(name), "\n"] - - defp compile(%VerbatimTextNode{content: content}, indent) do - [indent, String.trim(IO.iodata_to_binary(content)), "\n"] - end - - defp compile(%HTMLNode{name: name} = tag, indent) do - attrs = Enum.map(tag.attributes, &render_attribute/1) - tag_head = Enum.join([name | attrs]) - - body = - cond do - tag.closed -> - ["<", tag_head, "/>\n"] - - name in @void_elements -> - ["<", tag_head, " />\n"] - - true -> - children = compile(tag.children, indent <> @indent) - inner = if(tag.children == [], do: [], else: ["\n", children, indent]) - - [ - "<", - tag_head, - ">", - inner, - "\n" - ] - end - - [indent, body] - end - - defp compile(%EExNode{content: code, output: output, safe?: safe?} = eex, indent) do - if safe? do - raise "== operator used to include safe content in template; mark as raw in view instead" - end - - tag_indent = - if(String.trim(code) == "else", do: unindent(indent), else: indent) - - code = reformat_code(code, eex.children) - - opening = [ - if(output, do: "<%= ", else: "<% "), - convert_multiline(code, tag_indent <> @indent), - "%>\n" - ] - - closing = - if Regex.match?(~r/(fn.*->| do)\s*$/, code) do - [indent, "<% end %>\n"] - else - "" - end - - [tag_indent, opening, compile(eex.children, tag_indent <> @indent), closing] - end - - defp compile(%InlineHTMLNode{}, _indent) do - raise "Inline HTML not supported" - end - - defp compile(%HTMLCommentNode{content: content}, indent) do - [indent, "\n"] - end - - defp compile(%EExCommentNode{content: content}, indent) do - content - |> raw() - |> IO.iodata_to_binary() - |> String.split("\n") - |> Enum.map(&[indent, "<% # ", &1, " %>\n"]) - end - - defp compile({:eex, eex}, indent), do: [indent, "<%= ", eex, " %>"] - defp compile({:safe_eex, _eex}, _indent), do: raise("Safe EEx not supported") - defp compile(raw, indent), do: [indent, raw] - - defp render_attribute({name, {safe_eex, content}}) do - if safe_eex != :eex do - raise "Unsupported attribute type '#{safe_eex}'" - end - - case content do - "true" -> - " #{name}" - - "false" -> - "" - - "nil" -> - "" - - _ -> - quoted_content = Code.string_to_quoted!(content) - render_attribute_code(name, content, quoted_content) - end - end - - defp render_attribute({name, value}) do - if value == true do - " #{name}" - else - value = - cond do - is_binary(value) -> value - is_list(value) -> Enum.join(value, " ") - true -> value - end - - ~s( #{name}="#{value}") - end - end - - defp render_attribute_code(name, _content, quoted) - when is_number(quoted) or is_atom(quoted) do - ~s[ #{name}="#{quoted}"] - end - - defp render_attribute_code(name, _content, quoted) when is_list(quoted) do - quoted |> Enum.map_join(" ", &Kernel.to_string/1) |> (&~s[ #{name}="#{&1}"]).() - end - - defp render_attribute_code(name, _content, quoted) when is_binary(quoted), - do: ~s[ #{name}="#{quoted}"] - - # String with interpolation - defp render_attribute_code( - name, - _content, - {:<<>>, _, [{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [line]}, _]}]} - ) do - ~s[ #{name}={#{Macro.to_string(line)}}] - end - - defp render_attribute_code(name, content, _) do - ~s[ #{name}={#{content}}] - end - - defp raw(value) when is_list(value) do - Enum.map(value, &raw/1) - end - - defp raw({:eex, value}), do: "\#{" <> value <> "}" - defp raw(value), do: value - - defp convert_multiline(code, indent) do - case String.split(code, "\n") do - [_line] -> - [code, " "] - - lines -> - lines = - Enum.map(lines, fn line -> - if String.trim(line) == "" do - ["\n"] - else - [indent, line, "\n"] - end - end) - - ["\n", lines, unindent(indent)] - end - end - - defp unindent(indent), do: String.slice(indent, 0..-3//1) - - defp reformat_code(code, []) do - case Code.string_to_quoted(code) do - {:ok, ast} -> - ast - |> Code.quoted_to_algebra() - |> Inspect.Algebra.format(300) - |> IO.iodata_to_binary() - - _ -> - # Stab or do block - code - end - end - - defp reformat_code(code, _), do: code -end