defmodule Temple.Ast.Utils do @moduledoc false @attributes Application.compile_env( :temple, :attributes, {Phoenix.HTML, :attributes_escape} ) def snake_to_kebab(stringable), do: stringable |> to_string() |> String.replace_trailing("_", "") |> String.replace("_", "-") def kebab_to_snake(stringable), do: stringable |> to_string() |> String.replace("-", "_") def compile_attrs([]), do: [] def compile_attrs([attrs]) when is_list(attrs) do compile_attrs(attrs) end def compile_attrs(attrs) when is_list(attrs) do if Keyword.keyword?(attrs) do for {name, value} <- attrs, reduce: [] do acc -> name = snake_to_kebab(name) with false <- not is_binary(value) && Macro.quoted_literal?(value), false <- match?({_, _, _}, value), false <- is_list(value) do [{:text, " " <> name <> "=\"" <> to_string(value) <> "\""} | acc] else true -> nodes = Temple.Ast.Utils.build_attr(name, value) Enum.reverse(nodes) ++ acc end end |> Enum.reverse() else [ {:expr, quote do unquote(__MODULE__).__attributes__(unquote(List.first(attrs))) end} ] end end def build_attr(name, true) do [{:text, " " <> name}] end def build_attr(_name, false) do [] end def build_attr("rest!", values) when is_list(values) do Enum.flat_map(values, fn {name, value} -> build_attr(snake_to_kebab(name), value) end) end def build_attr("rest!", {_, _, _} = value) do expr = quote do unquote(__MODULE__).__attributes__(unquote(value)) end [{:expr, expr}] end def build_attr(name, {_, _, _} = value) do expr = quote do unquote(__MODULE__).__attributes__([{unquote(name), unquote(value)}]) end [{:expr, expr}] end def build_attr("class", classes) when is_list(classes) do value = quote do String.trim_leading(for {class, true} <- unquote(classes), into: "", do: " #{class}") end [{:text, ~s' class="'}, {:expr, value}, {:text, ~s'"'}] end def build_attr(name, value) do [{:text, ~s' #{name}="' <> to_string(value) <> ~s'"'}] end def split_args(not_what_i_want) when is_nil(not_what_i_want) or is_atom(not_what_i_want), do: {[], []} def split_args(args) do {do_and_else, args} = args |> Enum.split_with(fn arg -> if Keyword.keyword?(arg) do arg |> Keyword.drop([:do, :else]) |> Enum.empty?() else false end end) {List.flatten(do_and_else), args} end def consolidate_blocks(blocks, args) do case args do [args] when is_list(args) -> {do_value, args} = Keyword.pop(args, :do) {Keyword.put_new(blocks, :do, do_value), args} _ -> {blocks, args} end end def split_on_fn([{:fn, _, _} = func | rest], {args_before, _, args_after}) do split_on_fn(rest, {args_before, func, args_after}) end def split_on_fn([arg | rest], {args_before, nil, args_after}) do split_on_fn(rest, {[arg | args_before], nil, args_after}) end def split_on_fn([arg | rest], {args_before, func, args_after}) do split_on_fn(rest, {args_before, func, [arg | args_after]}) end def split_on_fn([], {args_before, func, args_after}) do {Enum.reverse(args_before), func, Enum.reverse(args_after)} end def pop_compact?([]), do: {false, []} def pop_compact?([args]) when is_list(args), do: pop_compact?(args) def pop_compact?(args) do Keyword.pop(args, :compact, false) end def indent(nil) do "" end def indent(level) do String.duplicate(" ", level * 2) end def inspect_ast(ast) do ast |> Macro.to_string() |> IO.puts() ast end def __attributes__(attributes) do {mod, func} = @attributes apply(mod, func, [attributes]) end end