use camino::{Utf8Path, Utf8PathBuf};
use crate::cli::policy::{PolicyArgs, PolicyVerb, ReconcileArgs};
use crate::context::AppContext;
use crate::error::AppError;
use crate::output;
use crate::services::policy::{Action, Plan};
fn resolve_target(ctx: &AppContext, target: &Utf8Path) -> Result<Utf8PathBuf, AppError> {
if target.is_absolute() {
Ok(target.to_path_buf())
} else if target == "." {
Ok(ctx.cwd.clone())
} else {
Err(AppError::Usage("target must be absolute or .".to_string()))
}
}
fn print_plan(plan: &Plan) {
match &plan.action {
Action::Seed { destination, .. } => {
output::line(format!(
"{destination}: absent; write the seed, which defines `{}`",
plan.reconciliation.sentinel.rule
));
}
Action::Append {
destination, block, ..
} => {
output::line(format!(
"{destination}: append `{}` to its Requirements section:",
plan.reconciliation.sentinel.rule
));
for line in block.lines() {
output::line(format!(" {line}"));
}
}
Action::Checklist { destination, block } => {
output::line(format!(
"{destination}: not in a shape this command rewrites; add `{}` by hand:",
plan.reconciliation.sentinel.rule
));
for line in block.lines() {
output::line(format!(" {line}"));
}
}
}
}
fn reconcile(ctx: &AppContext, args: &ReconcileArgs) -> Result<(), AppError> {
let target = resolve_target(ctx, &args.target)?;
let manifest = crate::services::verifier::read_manifest(&target)?;
let plans = crate::services::policy::plan(&target, manifest.docs_root)?;
if plans.is_empty() {
output::line("OK every active declaration is authorized by a local specification");
return Ok(());
}
for plan in &plans {
print_plan(plan);
}
let checklist: Vec<String> = plans
.iter()
.filter_map(|plan| match &plan.action {
Action::Checklist { destination, .. } => Some(destination.to_string()),
Action::Seed { .. } | Action::Append { .. } => None,
})
.collect();
if !args.apply {
output::line("DRY RUN: no files written");
return Ok(());
}
if !checklist.is_empty() {
return Err(AppError::Refused(format!(
"nothing written; add the rule by hand to: {}",
checklist.join(", ")
)));
}
for written in crate::services::policy::apply_all(&target, &plans)? {
output::line(format!("OK wrote {written}"));
}
Ok(())
}
pub fn run(ctx: &AppContext, args: PolicyArgs) -> Result<(), AppError> {
match args.verb {
PolicyVerb::Reconcile(args) => reconcile(ctx, &args),
}
}