Current section

Files

Jump to
al_check lib check config.ex
Raw

lib/check/config.ex

defmodule CheckEscript.Config do
@moduledoc "Config loading, parsing, initialization, and defaults."
@default_checks %{
"format" => %{"name" => "Formatting", "run" => "mix format --check-formatted"},
"compile" => %{"name" => "Compile", "run" => "mix compile --warnings-as-errors"},
"compile_test" => %{
"name" => "Compile (test)",
"run" => "MIX_ENV=test mix compile --warnings-as-errors"
},
"dialyzer" => %{"name" => "Dialyzer", "run" => "mix dialyzer"},
"credo" => %{"name" => "Credo", "run" => "mix credo --all"},
"credo_strict" => %{
"name" => "Credo Strict",
"run" => "mix credo --strict --only readability --all"
},
"modified_tests" => %{
"name" => "Modified Tests",
"run" => "builtin:modified_tests"
},
"modified_test_modules" => %{
"name" => "Modified Test Modules",
"run" => "builtin:modified_test_modules"
}
}
@default_run ["format", "compile", "compile_test", "dialyzer", "credo", "credo_strict", "test"]
@default_fast ["format", "compile", "compile_test", "credo", "credo_strict"]
@default_config Jason.encode!(
%{
"run" => @default_run,
"fast" => @default_fast,
"partitions" => 3,
"max_concurrency" => 10,
"test_args" => "--warnings-as-errors",
"default_repeat" => 100,
"fix" => [
%{"run" => "mix format"},
%{"run" => "mix recode", "files" => ".check/credo*.txt"}
],
"coverage" => %{"mod" => "native", "limit" => 80, "html" => false},
"checks" => @default_checks
},
pretty: true
)
def default_checks, do: @default_checks
def default_run, do: @default_run
def default_fast, do: @default_fast
def load do
default_path = Path.join(File.cwd!(), ".check.json")
config_path =
case System.get_env("CHECK_CONFIG") do
path when is_binary(path) and path != "" -> path
_ -> default_path
end
if File.exists?(config_path) do
case config_path |> File.read!() |> Jason.decode() do
{:ok, config} when is_map(config) ->
warn_unknown_keys(config)
{:ok, config}
{:ok, _} ->
{:error, "#{config_path} must contain a JSON object"}
{:error, reason} ->
{:error, "Failed to parse #{config_path}: #{inspect(reason)}"}
end
else
{:ok, %{}}
end
end
def init do
config_path = Path.join(File.cwd!(), ".check.json")
if File.exists?(config_path) do
IO.puts([IO.ANSI.format([:yellow, "#{config_path} already exists"])])
else
File.write!(config_path, @default_config)
IO.puts([IO.ANSI.format([:green, "Created #{config_path}"])])
end
end
def parse_coverage(%{"mod" => mod} = config) do
%{
mod: parse_coverage_mod(mod),
limit: config["limit"],
html: config["html"] || false,
baseline_cmd: config["baseline_cmd"]
}
end
def parse_coverage(_), do: %{mod: false, limit: nil, html: false, baseline_cmd: nil}
def parse_check_config(key, %{"run" => "builtin:" <> builtin} = config) do
name = config["name"] || humanize_key(key)
{name, :builtin, [builtin]}
end
def parse_check_config(key, %{"run" => run} = config) do
name = config["name"] || humanize_key(key)
{name, "sh", ["-c", run]}
end
def base_branch(config) do
config["base_branch"] || detect_base_branch()
end
@base_branch_candidates ["main", "master", "origin/main", "origin/master"]
defp detect_base_branch do
case Enum.find(@base_branch_candidates, &branch_exists?/1) do
nil ->
IO.puts(
:stderr,
"Could not detect base branch (tried #{Enum.join(@base_branch_candidates, ", ")}). Set \"base_branch\" in .check.json"
)
System.halt(1)
branch ->
IO.puts("Detected base git branch: #{branch}")
branch
end
end
defp branch_exists?(name) do
match?(
{_, 0},
System.cmd("git", ["rev-parse", "--verify", "--quiet", name], stderr_to_stdout: true)
)
end
def humanize_key(key) do
key
|> String.replace("_", " ")
|> String.split()
|> Enum.map_join(" ", &String.capitalize/1)
end
@known_keys ~w(run fast partitions max_concurrency test_args default_repeat coverage checks base_branch fix)
@known_coverage_keys ~w(mod limit html baseline_cmd)
@known_check_keys ~w(name run)
defp warn_unknown_keys(config) do
warn_keys(config, @known_keys, "")
warn_coverage_keys(config["coverage"])
warn_check_entries(config["checks"])
end
defp warn_coverage_keys(coverage) when is_map(coverage),
do: warn_keys(coverage, @known_coverage_keys, "coverage.")
defp warn_coverage_keys(_), do: :ok
defp warn_check_entries(checks) when is_map(checks) do
Enum.each(checks, fn
{name, config} when is_map(config) ->
warn_keys(config, @known_check_keys, "checks.#{name}.")
_ ->
:ok
end)
end
defp warn_check_entries(_), do: :ok
defp warn_keys(map, known, prefix) do
unknown = Map.keys(map) -- known
Enum.each(unknown, fn key ->
IO.puts(:stderr, "Warning: Unknown config key \"#{prefix}#{key}\" in .check.json")
end)
end
defp parse_coverage_mod("native"), do: :native
defp parse_coverage_mod("coveralls"), do: :coveralls
defp parse_coverage_mod(_), do: false
end