use std::path::PathBuf;
use camino::Utf8Path;
use crate::{
error::{Error, Result},
load,
model::{normalize, targets::TargetId},
render,
util::{diff, fs::read_if_exists},
};
enum FileCheck {
Ok,
Missing { path: String },
Stale { path: String, diff: String },
}
pub fn run(config: &Utf8Path, targets: Option<&[String]>) -> Result<()> {
let raw = load::load_file(config)?;
let (mut policy, warnings) = normalize(raw)?;
for w in &warnings {
eprintln!("warning: {w}");
}
if let Some(target_strs) = targets {
let mut explicit_targets = Vec::new();
for t in target_strs {
let id = TargetId::from_id(t).ok_or_else(|| Error::UnknownTarget { id: t.clone() })?;
explicit_targets.push(id);
}
policy.outputs = crate::model::targets::OutputTargets::from_targets(&explicit_targets);
}
let outputs = render::render_all(&policy)?;
let mut checks: Vec<FileCheck> = Vec::new();
let base_dir = config.parent().unwrap_or(Utf8Path::new(""));
for output in &outputs {
let path = base_dir.join(&output.path);
let generated = diff::normalize_line_endings(&output.content);
let committed = read_if_exists(path.as_std_path())?;
match committed {
None => {
checks.push(FileCheck::Missing {
path: path.to_string(),
});
}
Some(committed_raw) => {
let committed_norm = diff::normalize_line_endings(&committed_raw);
if committed_norm == generated {
checks.push(FileCheck::Ok);
} else {
let d = diff::unified_diff(path.as_str(), &committed_norm, &generated);
checks.push(FileCheck::Stale {
path: path.to_string(),
diff: d,
});
}
}
}
}
let failures: Vec<&FileCheck> = checks
.iter()
.filter(|c| !matches!(c, FileCheck::Ok))
.collect();
if failures.is_empty() {
let count = checks.len();
println!("\u{2713} All {count} generated file(s) are up to date.");
return Ok(());
}
eprintln!("Generated files are out of date:\n");
for check in &failures {
match check {
FileCheck::Missing { path } => {
eprintln!(" missing {path}");
eprintln!(" \u{2192} Run: agent-policy generate\n");
}
FileCheck::Stale { path, diff: d } => {
eprintln!(" stale {path}");
eprintln!("{d}");
}
FileCheck::Ok => unreachable!(),
}
}
eprintln!("Run `agent-policy generate` to update.");
Err(Error::CheckFailed {
path: failures
.first()
.map(|c| match c {
FileCheck::Missing { path } | FileCheck::Stale { path, .. } => PathBuf::from(path),
FileCheck::Ok => unreachable!(),
})
.unwrap_or_default(),
})
}