ash/lib/mix/tasks/ash.formatter.ex

124 lines
3.3 KiB
Elixir
Raw Normal View History

defmodule Mix.Tasks.Ash.Formatter do
@moduledoc "Generates a .formatter.exs from a list of extensions, and writes it."
use Mix.Task
@formatter_exs_template """
# THIS FILE IS AUTOGENERATED USING `mix ash.formatter`
# DONT MODIFY IT BY HAND
locals_without_parens = __replace_me__
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
locals_without_parens: locals_without_parens,
export: [
locals_without_parens: locals_without_parens
]
]
"""
@shortdoc @moduledoc
2020-08-25 16:52:27 +12:00
@spec run(term) :: no_return
def run(opts) do
Mix.Task.run("compile")
{opts, []} = OptionParser.parse!(opts, strict: [check: :boolean, extensions: :string])
unless opts[:extensions] do
raise "Must supply a comma separated list of extensions to generate a .formatter.exs for"
end
locals_without_parens =
opts[:extensions]
|> String.split(",")
|> Enum.flat_map(fn extension ->
extension_mod = Module.concat([extension])
2020-07-01 01:53:37 +12:00
case Code.ensure_compiled(extension_mod) do
{:module, _module} -> :ok
other -> raise "Error ensuring extension compiled #{inspect(other)}"
end
all_entity_builders(extension_mod.sections())
end)
|> Enum.uniq()
|> Enum.sort()
contents =
@formatter_exs_template
2020-08-09 05:55:36 +12:00
|> String.replace("__replace_me__", inspect(locals_without_parens, limit: :infinity))
|> Code.format_string!()
contents_with_newline = [contents, "\n"]
if opts[:check] do
if File.read!(".formatter.exs") != IO.iodata_to_binary(contents_with_newline) do
raise """
.formatter.exs is not up to date!
Run the following command and commit the result:
mix ash.formatter --extensions #{opts[:extensions]}
"""
else
IO.puts("The current .formatter.exs is correct")
end
else
File.write!(".formatter.exs", contents_with_newline)
end
end
defp all_entity_builders(sections) do
Enum.flat_map(sections, fn section ->
Enum.concat([
all_entity_option_builders(section),
section_option_builders(section),
section_entity_builders(section)
])
end)
end
defp section_entity_builders(section) do
Enum.flat_map(section.entities, fn entity ->
entity_builders(entity)
end) ++ all_entity_builders(section.sections())
end
defp entity_builders(entity) do
arg_count = Enum.count(entity.args)
[{entity.name, arg_count}, {entity.name, arg_count + 1}] ++
flat_map_nested_entities(entity, &entity_builders/1)
end
defp all_entity_option_builders(section) do
Enum.flat_map(section.entities, fn entity ->
entity_option_builders(entity)
end)
end
defp entity_option_builders(entity) do
entity.schema
|> Keyword.drop(entity.args)
|> Enum.map(fn {key, _schema} ->
{key, 1}
end)
|> Kernel.++(flat_map_nested_entities(entity, &entity_option_builders/1))
end
defp section_option_builders(section) do
Enum.map(section.schema, fn {key, _} ->
{key, 1}
end)
end
defp flat_map_nested_entities(entity, mapper) do
Enum.flat_map(entity.entities, fn {_, nested_entities} ->
nested_entities
|> List.wrap()
|> Enum.flat_map(fn nested_entity ->
mapper.(nested_entity)
end)
end)
end
end