defmodule Mix.Tasks.Qs.Gen.AddTailwind do
use Mix.Task
@tailwind_config """
config :tailwind,
version: "3.0.23",
default: [
args: ~w(
--config=tailwind.config.js
--input=css/app.css
--output=../priv/static/assets/app.css
),
cd: Path.expand("../assets", __DIR__)
]
"""
@tailwind_dep "\t\t\t{:tailwind, \"~> 0.1\", runtime: Mix.env() == :dev},"
@tailwind_watcher "\t\ttailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}"
@impl Mix.Task
def run(_args) do
add_into(
"mix.exs",
fn
{:defp, _, [{:deps, _, nil}, _]} -> true
_ -> false
end,
@tailwind_dep
)
add_after(
"config/config.exs",
fn
{:config, _, [:esbuild, _]} -> true
_ -> false
end,
@tailwind_config
)
add_into(
"config/dev.exs",
fn node ->
Keyword.keyword?(node) and Keyword.has_key?(node, :watchers)
end,
@tailwind_watcher
)
end
defp add_into(file_name, is_match, addition), do: add_to_file(file_name, is_match, 0, addition)
defp add_after(file_name, is_match, addition), do: add_to_file(file_name, is_match, 2, addition)
defp add_to_file(file_name, is_match, line_offset, addition) do
contents = File.read!(file_name)
ast = Code.string_to_quoted!(contents)
{_start, max_line} = find_span(ast, is_match)
if not is_nil(max_line) do
Mix.shell().info("Patching #{file_name}")
contents =
contents
|> String.split("\n")
|> List.insert_at(max_line + line_offset, addition)
|> Enum.join("\n")
File.write!(file_name, contents)
end
end
defp find_span_end(node) do
{_, max_line} =
Macro.postwalk(node, 0, fn item, acc ->
case item do
{_, [line: line], _} -> {item, max(acc, line)}
node -> {node, acc}
end
end)
max_line
end
defp find_span(ast, is_match) do
{_, span} =
Macro.postwalk(ast, nil, fn node, acc ->
if is_match.(node) do
case node do
{_, [line: line], _} ->
{node, {line, find_span_end(node)}}
_ ->
{node, {0, find_span_end(node)}}
end
else
{node, acc}
end
end)
span
end
end