Current section
Files
Jump to
Current section
Files
lib/mix/tasks/frontier.spec.ex
defmodule Mix.Tasks.Frontier.Spec do
@shortdoc "Prints the Frontier boundary spec for the project."
@moduledoc """
Prints a human-readable summary of all declared Frontier boundaries.
$ mix frontier.spec
Shows each context with its exports, internals, reaches, and externals configuration.
"""
use Mix.Task
alias Frontier.Classifier
alias Frontier.Store
@impl Mix.Task
def run(_argv) do
Mix.Task.run("compile")
Frontier.Store.ensure_loaded()
print_root()
print_contexts()
end
defp print_root do
case Store.root() do
nil ->
:ok
{root_module, config} ->
Mix.shell().info("#{inspect(root_module)} (root)")
if config.globals != [] do
globals = Enum.map_join(config.globals, ", ", &inspect/1)
Mix.shell().info(" globals: #{globals}")
end
if config.ignore != [] do
ignored = Enum.map_join(config.ignore, ", ", &inspect/1)
Mix.shell().info(" ignore: #{ignored}")
end
Mix.shell().info("")
end
end
defp print_contexts do
contexts = Store.contexts()
if contexts == %{} do
Mix.shell().info("No contexts declared.")
else
contexts
|> Enum.sort_by(fn {mod, _} -> inspect(mod) end)
|> Enum.each(fn {module, config} ->
Mix.shell().info(inspect(module))
print_exports(module, config)
print_internals(module)
print_reaches(config)
print_externals(config)
print_enforcement(config)
print_skip_violations(config)
Mix.shell().info("")
end)
end
end
defp print_exports(module, config) do
schemas = find_schemas(module)
manual_exports = if is_list(config.exports), do: config.exports, else: []
reclassified =
Store.all_reclassified()
|> Enum.filter(fn {_mod, target} -> target == module end)
|> Enum.map(fn {mod, _} -> mod end)
all_exports =
Enum.map(schemas, &{&1, "schema"}) ++
Enum.map(manual_exports, &{&1, "manual"}) ++
Enum.map(reclassified, &{&1, "reclassified"})
case {config.exports, all_exports} do
{:all, _} ->
Mix.shell().info(" exports: :all")
{_, []} ->
:ok
{_, exports} ->
formatted =
Enum.map_join(exports, ", ", fn {mod, type} -> "#{inspect(mod)} (#{type})" end)
Mix.shell().info(" exports: #{formatted}")
end
end
defp print_internals(module) do
module_prefix = Atom.to_string(module) <> "."
internals =
:code.all_loaded()
|> Enum.map(fn {mod, _} -> mod end)
|> Enum.filter(fn mod ->
String.starts_with?(Atom.to_string(mod), module_prefix) &&
Classifier.classify(mod) == :internal
end)
|> Enum.sort()
if internals != [] do
formatted = Enum.map_join(internals, ", ", &inspect/1)
Mix.shell().info(" internals: #{formatted}")
end
end
defp print_reaches(%{reaches: nil}), do: Mix.shell().info(" reaches: (unrestricted)")
defp print_reaches(%{reaches: reaches}) do
formatted = Enum.map_join(reaches, ", ", &inspect/1)
Mix.shell().info(" reaches: #{formatted}")
end
defp print_externals(%{externals: nil}), do: Mix.shell().info(" externals: (unrestricted)")
defp print_externals(%{externals: externals}) do
formatted = Enum.map_join(externals, ", ", &inspect/1)
Mix.shell().info(" externals: #{formatted}")
end
defp print_enforcement(%{enforce: %{callers: true, calls: true}}), do: :ok
defp print_enforcement(%{enforce: enforce}) do
flags =
[]
|> then(&if(enforce.callers, do: &1, else: ["callers disabled" | &1]))
|> then(&if(enforce.calls, do: &1, else: ["calls disabled" | &1]))
|> Enum.join(", ")
Mix.shell().info(" enforcement: #{flags}")
end
defp print_skip_violations(%{skip_violations: []}), do: :ok
defp print_skip_violations(%{skip_violations: skips}) do
formatted = Enum.map_join(skips, ", ", &inspect/1)
Mix.shell().info(" skip_violations: #{formatted}")
end
defp find_schemas(context_module) do
prefix = Atom.to_string(context_module) <> "."
:code.all_loaded()
|> Enum.map(fn {mod, _} -> mod end)
|> Enum.filter(fn mod ->
String.starts_with?(Atom.to_string(mod), prefix) &&
Classifier.classify(mod) == :schema
end)
|> Enum.sort()
end
end