///|
fn trim_quotes(text : String) -> String {
let value = text.trim().to_owned()
if value.length() >= 2 && value.has_prefix("\"") && value.has_suffix("\"") {
value[1:value.length() - 1].to_owned()
} else {
value
}
}
///|
fn parse_bool(text : String) -> Bool? {
let value = text.trim().to_owned().to_lower()
if value == "true" {
Some(true)
} else if value == "false" {
Some(false)
} else {
None
}
}
///|
fn parse_int_opt(text : String) -> Int? {
let value = text.trim().to_owned()
if value.is_empty() {
None
} else {
let mut result = 0
for ch in value {
if ch >= '0' && ch <= '9' {
result = result * 10 + (ch.to_int() - '0'.to_int())
} else {
return None
}
}
Some(result)
}
}
///|
fn parse_string_array_value(value : String) -> Array[String] {
let text = value.trim().to_owned()
if text.has_prefix("[") && text.has_suffix("]") {
let inner = text[1:text.length() - 1].to_owned()
if inner.trim().is_empty() {
[]
} else {
inner
.split(",")
.map(part => trim_quotes(part.to_owned()))
.filter(item => item != "")
.collect()
}
} else {
[]
}
}
///|
fn split_key_value(line : String) -> (String, String)? {
match line.find("=") {
Some(index) =>
Some(
(
line[:index].to_owned().trim().to_owned(),
line[index + 1:].to_owned().trim().to_owned(),
),
)
None => None
}
}
///|
fn parse_config_lines(lines : Array[String]) -> ScanConfig {
let mut config = default_scan_config()
let mut section = ""
let mut collecting_exclude = false
let exclude_parts : Array[String] = []
for raw in lines {
let line = raw.trim().to_owned()
if collecting_exclude {
exclude_parts.push(line)
if line.contains("]") {
collecting_exclude = false
config = {
..config,
exclusions: parse_string_array_value(exclude_parts.join(" ")),
}
}
} else if line.is_empty() || line.has_prefix("#") {
continue
} else if line.has_prefix("[") && line.has_suffix("]") {
section = line[1:line.length() - 1].to_owned().trim().to_owned()
} else {
match split_key_value(line) {
Some((key, value)) =>
if section == "" && key == "exclude" {
if value.contains("[") && !value.contains("]") {
collecting_exclude = true
exclude_parts.clear()
exclude_parts.push(value)
} else {
config = { ..config, exclusions: parse_string_array_value(value) }
}
} else if section == "rules" {
match parse_bool(value) {
Some(flag) =>
if key == "check_readme" {
config = {
..config,
rules: { ..config.rules, check_readme: flag },
}
} else if key == "check_moon_mod" {
config = {
..config,
rules: { ..config.rules, check_moon_mod: flag },
}
} else if key == "check_ci" {
config = {
..config,
rules: { ..config.rules, check_ci: flag },
}
} else if key == "check_generated_mbti" {
config = {
..config,
rules: { ..config.rules, check_generated_mbti: flag },
}
}
None => ()
}
} else if section == "coverage" && key == "min_public_api_coverage" {
config = {
..config,
coverage: { min_public_api_coverage: parse_int_opt(value) },
}
}
None => ()
}
}
}
config
}
///|
fn merge_exclusions(
left : Array[String],
right : Array[String],
) -> Array[String] {
let merged : Array[String] = []
for item in left {
if item != "" {
merged.push(item)
}
}
for item in right {
if item != "" {
merged.push(item)
}
}
merged
}
///|
/// Build default scan configuration.
pub fn default_scan_config() -> ScanConfig {
{
exclusions: [],
rules: {
check_readme: true,
check_moon_mod: true,
check_ci: true,
check_generated_mbti: true,
},
coverage: { min_public_api_coverage: None },
}
}
///|
/// Parse the supported `moondoccheck.toml` configuration subset.
pub fn parse_scan_config(source : String) -> ScanConfig {
parse_config_lines(source.split("\n").map(line => line.to_owned()).collect())
}
///|
/// Merge configuration-file exclusions with command-line scan options.
pub fn apply_scan_config(
config : ScanConfig,
options : ScanOptions,
) -> ScanOptions {
{ exclusions: merge_exclusions(config.exclusions, options.exclusions) }
}