use std::path::Path;
use super::assert_check::evaluate_assert;
use super::load::load;
use super::model::AuditOptions;
use super::render::{render_json, render_text};
use super::score::score;
pub(crate) fn cmd_audit(cwd: &Path, args: &[String]) -> (i32, String) {
const USAGE: &str = "usage: devscout audit --semantic <refs.jsonl> [--units F] [--defs F] [--json] [--assert F] [--fp-sites F]";
let mut semantic: Option<String> = None;
let mut units: Option<String> = None;
let mut defs: Option<String> = None;
let mut assert_path: Option<String> = None;
let mut fp_sites_path: Option<String> = None;
let mut json = false;
let mut i = 0;
while i < args.len() {
let flag = args[i].as_str();
if matches!(
flag,
"--semantic" | "--units" | "--defs" | "--assert" | "--fp-sites"
) {
let Some(val) = args.get(i + 1) else {
return (1, format!("error: missing value for '{flag}'\n{USAGE}"));
};
match flag {
"--semantic" => semantic = Some(val.clone()),
"--units" => units = Some(val.clone()),
"--defs" => defs = Some(val.clone()),
"--assert" => assert_path = Some(val.clone()),
"--fp-sites" => fp_sites_path = Some(val.clone()),
_ => unreachable!(),
}
i += 2;
} else if flag == "--json" {
json = true;
i += 1;
} else {
return (1, format!("error: unrecognized argument '{flag}'\n{USAGE}"));
}
}
let Some(semantic) = semantic else {
return (
1,
format!("error: --semantic <refs.jsonl> is required\n{USAGE}"),
);
};
let root = match crate::cli::require_repo(cwd) {
Ok(r) => r,
Err(e) => return (1, format!("error: {e}")),
};
let semantic_path = crate::repo::resolve_from(cwd, Path::new(&semantic));
let units_path = units.map(|u| crate::repo::resolve_from(cwd, Path::new(&u)));
let defs_path = defs.map(|d| crate::repo::resolve_from(cwd, Path::new(&d)));
let opts = AuditOptions {
semantic: &semantic_path,
units: units_path.as_deref(),
defs: defs_path.as_deref(),
};
let mut inputs = match load(&root, &opts) {
Ok(i) => i,
Err(e) => return (1, format!("error: {e}")),
};
inputs.collect_fp_sites = fp_sites_path.is_some();
let report = score(inputs);
if let Some(fp_path) = fp_sites_path {
let abs = crate::repo::resolve_from(cwd, Path::new(&fp_path));
if let Err(e) = std::fs::write(&abs, super::fp_sites::render(&report.fp_sites)) {
return (1, format!("error: failed to write {}: {e}", abs.display()));
}
}
let json_string = render_json(&report);
let mut out = if json {
json_string.clone()
} else {
render_text(&report)
};
if let Some(assert_path) = assert_path {
let abs = crate::repo::resolve_from(cwd, Path::new(&assert_path));
let text = match std::fs::read_to_string(&abs) {
Ok(t) => t,
Err(e) => return (1, format!("error: failed to read {}: {e}", abs.display())),
};
let violations = match evaluate_assert(&json_string, &text) {
Ok(v) => v,
Err(e) => return (1, format!("error: {e}")),
};
if !violations.is_empty() {
if json {
eprintln!("{}", violations.join("\n"));
} else {
out.push('\n');
out.push_str(&violations.join("\n"));
}
return (1, out);
}
}
(0, out)
}