From bc4c5953772490f5822f0f7f1ab816a4df8909d9 Mon Sep 17 00:00:00 2001 From: Aaron Tinio Date: Thu, 7 Dec 2023 07:17:52 +0800 Subject: [PATCH] Generate class and variant order at runtime --- lib/tailwind_formatter.ex | 79 +++++++++++++++++++++------------ lib/tailwind_formatter/order.ex | 33 +++++++------- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/lib/tailwind_formatter.ex b/lib/tailwind_formatter.ex index 7c9b2ed..682c52b 100644 --- a/lib/tailwind_formatter.ex +++ b/lib/tailwind_formatter.ex @@ -16,6 +16,8 @@ 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 @@ -23,7 +25,7 @@ defmodule TailwindFormatter do 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 @@ -37,25 +39,39 @@ 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) -> @@ -63,7 +79,8 @@ defmodule TailwindFormatter do 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 @@ -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 @@ -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 diff --git a/lib/tailwind_formatter/order.ex b/lib/tailwind_formatter/order.ex index 23e7c9f..29e4af4 100644 --- a/lib/tailwind_formatter/order.ex +++ b/lib/tailwind_formatter/order.ex @@ -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