///|
/// Profiles express the intended publication target without using network APIs.
pub(all) enum DoctorProfile {
Default
Github
Mooncakes
Release
} derive(Debug, Eq)
///|
pub fn DoctorProfile::label(self : DoctorProfile) -> String {
match self {
Default => "default"
Github => "github"
Mooncakes => "mooncakes"
Release => "release"
}
}
///|
fn parse_profile(value : String) -> DoctorProfile? {
match value {
"default" => Some(Default)
"github" => Some(Github)
"mooncakes" => Some(Mooncakes)
"release" => Some(Release)
_ => None
}
}
///|
fn parse_severity(value : String) -> Severity? {
match value {
"error" => Some(Error)
"warn" | "warning" => Some(Warn)
"info" => Some(Info)
_ => None
}
}
///|
/// Project-local policy loaded from moon.doctor.json when present.
pub(all) struct DoctorConfig {
version : Int
profile : DoctorProfile
disabled_rules : Array[String]
severity_overrides : Map[String, Severity]
ignore_paths : Array[String]
required_commands : Array[String]
release_metadata : Map[String, String]
source : String
} derive(Debug, Eq)
///|
pub fn DoctorConfig::default(
profile? : DoctorProfile = Default,
) -> DoctorConfig {
let required_commands = match profile {
Default => ["moon check", "moon test"]
Github => ["moon check", "moon test", "git status --short --branch"]
Mooncakes => ["moon check", "moon test", "moon package"]
Release =>
[
"moon check --target all --deny-warn", "moon build --target all --deny-warn",
"moon test --target all --deny-warn", "moon package",
]
}
{
version: 1,
profile,
disabled_rules: [],
severity_overrides: {},
ignore_paths: ["_build", ".mooncakes", ".git", "target"],
required_commands,
release_metadata: {},
source: "builtin:" + profile.label(),
}
}
///|
pub fn DoctorConfig::is_rule_disabled(
self : DoctorConfig,
rule_id : String,
) -> Bool {
self.disabled_rules.contains(rule_id)
}
///|
pub fn DoctorConfig::severity_for(
self : DoctorConfig,
rule_id : String,
default_severity : Severity,
) -> Severity {
match self.severity_overrides.get(rule_id) {
Some(value) => value
None => default_severity
}
}
///|
pub fn DoctorConfig::release_checklist(self : DoctorConfig) -> Array[String] {
let checklist = default_release_checklist()
for command in self.required_commands {
let entry = "Run " + command
if !checklist.contains(entry) {
checklist.push(entry)
}
}
checklist
}
///|
pub fn DoctorConfig::ignores_path(self : DoctorConfig, path : String) -> Bool {
self.ignore_paths.any(ignore => {
path == ignore ||
path.has_prefix(ignore + "/") ||
path.has_prefix(ignore + "\\")
})
}
///|
fn config_path(root : String) -> String {
if root.has_suffix("/") || root.has_suffix("\\") {
root + "moon.doctor.json"
} else {
root + "/moon.doctor.json"
}
}
///|
fn string_value(
object : Map[String, Json],
key : String,
fallback : String,
) -> String raise {
match object.get(key) {
Some(value) => @json.from_json(value)
None => fallback
}
}
///|
fn string_array_value(
object : Map[String, Json],
key : String,
) -> Array[String] raise {
match object.get(key) {
Some(value) => @json.from_json(value)
None => []
}
}
///|
fn string_map_value(
object : Map[String, Json],
key : String,
) -> Map[String, String] raise {
match object.get(key) {
Some(value) => @json.from_json(value)
None => {}
}
}
///|
fn severity_map_value(
object : Map[String, Json],
) -> Map[String, Severity] raise {
let raw = string_map_value(object, "severity_overrides")
let parsed : Map[String, Severity] = {}
for rule_id, value in raw {
match parse_severity(value) {
Some(severity) => parsed[rule_id] = severity
None =>
fail(
"moon.doctor.json has invalid severity for " + rule_id + ": " + value,
)
}
}
parsed
}
///|
fn validate_known_fields(object : Map[String, Json]) -> Unit raise {
let known = [
"version", "profile", "disabled_rules", "severity_overrides", "ignore_paths",
"required_commands", "release_metadata",
]
for key in object.keys() {
if !known.contains(key) {
fail("moon.doctor.json has unknown field: " + key)
}
}
}
///|
fn validate_unique(label : String, values : Array[String]) -> Unit raise {
let seen : Map[String, Unit] = {}
for value in values {
if seen.contains(value) {
fail("moon.doctor.json has duplicate " + label + ": " + value)
}
seen[value] = ()
}
}
///|
fn parse_config(content : String, source : String) -> DoctorConfig raise {
let json = @json.parse(content)
let object : Map[String, Json] = @json.from_json(json)
validate_known_fields(object)
let version = match object.get("version") {
Some(value) => {
let parsed : Int = @json.from_json(value)
parsed
}
None => 1
}
if version != 1 {
fail("moon.doctor.json supports only version 1")
}
let profile_name = string_value(object, "profile", "default")
let profile = match parse_profile(profile_name) {
Some(value) => value
None => fail("moon.doctor.json has unknown profile: " + profile_name)
}
let defaults = DoctorConfig::default(profile~)
let disabled_rules = string_array_value(object, "disabled_rules")
let ignore_paths = match object.get("ignore_paths") {
Some(_) => string_array_value(object, "ignore_paths")
None => defaults.ignore_paths
}
let required_commands = match object.get("required_commands") {
Some(_) => string_array_value(object, "required_commands")
None => defaults.required_commands
}
validate_unique("disabled rule", disabled_rules)
validate_unique("ignored path", ignore_paths)
validate_unique("required command", required_commands)
{
version,
profile,
disabled_rules,
severity_overrides: severity_map_value(object),
ignore_paths,
required_commands,
release_metadata: string_map_value(object, "release_metadata"),
source,
}
}
///|
/// Load a project policy or return the selected built-in profile when absent.
pub fn load_config(
root : String,
path? : String,
profile? : String,
) -> DoctorConfig raise {
let requested_profile = match profile {
Some(value) =>
match parse_profile(value) {
Some(parsed) => parsed
None => fail("unknown profile: " + value)
}
None => Default
}
let path = match path {
Some(value) => value
None => config_path(root)
}
if @fs.path_exists(path) {
let config = parse_config(@fs.read_file_to_string(path), path)
match profile {
Some(_) =>
DoctorConfig::{
..config,
profile: requested_profile,
source: path + " + CLI profile",
}
None => config
}
} else {
DoctorConfig::default(profile=requested_profile)
}
}