Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 51 additions & 28 deletions lib/tailwind_formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ defmodule TailwindFormatter do
end

def format(contents, _opts) do
class_and_variant_order = Order.class_and_variant_order()

contents
|> HEExTokenizer.tokenize()
|> Enum.reduce([contents], fn
{:tag, _name, attrs, _meta}, contents ->
Enum.reduce(attrs, contents, fn
{"class", class_attr, _meta}, [remainder | acc] ->
[attr, remainder] = String.split(remainder, old_classes(class_attr), parts: 2)
[remainder, sort_classes(class_attr), attr | acc]
[remainder, sort_classes(class_attr, class_and_variant_order), attr | acc]

_, contents ->
contents
Expand All @@ -37,33 +39,48 @@ defmodule TailwindFormatter do
end

defp old_classes({_type, classes, _meta}), do: classes
defp sort_classes({:string, classes, _meta}), do: sort(classes)

defp sort_classes({:expr, expr_class, _meta}) do
defp sort_classes({:string, classes, _meta}, class_and_variant_order),
do: sort(classes, class_and_variant_order)

defp sort_classes({:expr, expr_class, _meta}, class_and_variant_order) do
expr_class
|> Code.string_to_quoted!(literal_encoder: &{:ok, {:__block__, &2, [&1]}})
|> sort_expr()
|> sort_expr(class_and_variant_order)
|> Code.quoted_to_algebra()
|> Inspect.Algebra.format(:infinity)
|> IO.iodata_to_binary()
end

defp sort_expr({:<<>>, meta, children}), do: {:<<>>, meta, handle_interpolation(children)}
defp sort_expr({a, b, c}), do: {sort_expr(a), sort_expr(b), sort_expr(c)}
defp sort_expr({a, b}), do: {sort_expr(a), sort_expr(b)}
defp sort_expr(list) when is_list(list), do: Enum.map(list, &sort_expr/1)
defp sort_expr(text) when is_binary(text), do: sort(text)
defp sort_expr(node), do: node
defp sort_expr({:<<>>, meta, children}, class_and_variant_order),
do: {:<<>>, meta, handle_interpolation(children, class_and_variant_order)}

defp sort_expr({a, b, c}, class_and_variant_order),
do:
{sort_expr(a, class_and_variant_order), sort_expr(b, class_and_variant_order),
sort_expr(c, class_and_variant_order)}

defp sort_expr({a, b}, class_and_variant_order),
do: {sort_expr(a, class_and_variant_order), sort_expr(b, class_and_variant_order)}

defp sort_expr(list, class_and_variant_order) when is_list(list),
do: Enum.map(list, &sort_expr(&1, class_and_variant_order))

defp handle_interpolation(children) do
defp sort_expr(text, class_and_variant_order) when is_binary(text),
do: sort(text, class_and_variant_order)

defp sort_expr(node, _class_and_variant_order), do: node

defp handle_interpolation(children, class_and_variant_order) do
{classes_with_placeholders, {placeholder_map, _index}} =
Enum.map_reduce(children, {%{}, 0}, fn
str, acc when is_binary(str) ->
{str, acc}

node, {placeholder_map, index} ->
{"#{@placeholder}#{index}#{@placeholder}",
{Map.put(placeholder_map, "#{index}", sort_expr(node)), index + 1}}
{Map.put(placeholder_map, "#{index}", sort_expr(node, class_and_variant_order)),
index + 1}}
end)

classes_with_placeholders
Expand All @@ -72,7 +89,7 @@ defmodule TailwindFormatter do
do: acc <> class,
else: "#{acc} #{class}"
end)
|> sort()
|> sort(class_and_variant_order)
|> String.split()
|> weave_in_code(placeholder_map)
end
Expand All @@ -91,54 +108,60 @@ defmodule TailwindFormatter do
|> List.flatten()
end

defp sort_variant_chains(classes) do
defp sort_variant_chains(classes, class_and_variant_order) do
{_class_order, variant_order} = class_and_variant_order

classes
|> String.split()
|> Enum.map(fn class ->
class
|> String.split(":")
|> Enum.sort_by(&variant_position/1, :desc)
|> Enum.sort_by(&variant_position(&1, variant_order), :desc)
|> Enum.join(":")
end)
end

defp sort(classes) when is_binary(classes) do
defp sort(classes, class_and_variant_order) when is_binary(classes) do
leading_space = if classes =~ ~r/\A\s/, do: " "
trailing_space = if classes =~ ~r/\s\z/, do: " "

classes =
classes
|> sort_variant_chains()
|> sort()
|> sort_variant_chains(class_and_variant_order)
|> sort(class_and_variant_order)
|> Enum.join(" ")

Enum.join([leading_space, classes, trailing_space])
end

defp sort([]), do: []
defp sort([], _class_and_variant_order), do: []

defp sort(class_list) when is_list(class_list) do
defp sort(class_list, class_and_variant_order) when is_list(class_list) do
{class_order, _variant_order} = class_and_variant_order
{variants, base_classes} = Enum.split_with(class_list, &variant?/1)

Enum.sort_by(base_classes, &class_position/1) ++ sort_variant_classes(variants)
Enum.sort_by(base_classes, &class_position(&1, class_order)) ++
sort_variant_classes(variants, class_and_variant_order)
end

defp placeholder?(class), do: String.contains?(class, @placeholder)
defp variant?(class), do: String.contains?(class, ":")
defp prose?(class), do: String.contains?(class, "prose")

defp class_position(class),
do: if(placeholder?(class), do: -1_000_000, else: Map.get(Order.classes(), class, -1))
defp class_position(class, class_order),
do: if(placeholder?(class), do: -1_000_000, else: Map.get(class_order, class, -1))

# prose variant order matters, thus push to front
defp variant_position(variant),
do: if(prose?(variant), do: 0, else: Map.get(Order.variants(), variant, -1))
defp variant_position(variant, variant_order),
do: if(prose?(variant), do: 0, else: Map.get(variant_order, variant, -1))

defp sort_variant_classes(variants, class_and_variant_order) do
{_class_order, variant_order} = class_and_variant_order

defp sort_variant_classes(variants) do
variants
|> group_by_first_variant()
|> Enum.sort_by(fn {variant, _rest} -> variant_position(variant) end)
|> Enum.map(fn {variant, rest} -> {variant, sort(rest)} end)
|> Enum.sort_by(fn {variant, _rest} -> variant_position(variant, variant_order) end)
|> Enum.map(fn {variant, rest} -> {variant, sort(rest, class_and_variant_order)} end)
|> Enum.flat_map(fn {variant, rest} -> Enum.map(rest, &"#{variant}:#{&1}") end)
end

Expand Down
33 changes: 15 additions & 18 deletions lib/tailwind_formatter/order.ex
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
defmodule TailwindFormatter.Order do
custom_classes = Path.join(Path.dirname(Mix.Project.build_path()), "classes.txt")
custom_variants = Path.join(Path.dirname(Mix.Project.build_path()), "variants.txt")
def class_and_variant_order do
custom_classes = Path.join(Path.dirname(Mix.Project.build_path()), "classes.txt")
custom_variants = Path.join(Path.dirname(Mix.Project.build_path()), "variants.txt")

@external_resource custom_classes
@external_resource custom_variants
classes =
if File.exists?(custom_classes),
do: order_map(custom_classes),
else: order_map("priv/classes.txt")

@moduledoc false
order_map = fn src ->
Mix.shell().info([:green, "Loading in #{src}."])
variants =
if File.exists?(custom_variants),
do: order_map(custom_variants),
else: order_map("priv/variants.txt")

{classes, variants}
end

defp order_map(src) do
src
|> File.read!()
|> String.split(~r/\R/)
|> Enum.with_index()
|> Map.new()
end

@classes if File.exists?(custom_classes),
do: order_map.(custom_classes),
else: order_map.("priv/classes.txt")

@variants if File.exists?(custom_variants),
do: order_map.(custom_variants),
else: order_map.("priv/variants.txt")

def variants(), do: @variants
def classes(), do: @classes
end