use std::path::Path;
use loftspec::fmt::format;
use loftspec_store::FileStore;
use serde_json::json;
use crate::finish::Finished;
use crate::json;
use crate::out::{errln, outln};
use crate::stop::Stop;
const SCHEMA: &str = "loft-fmt/1";
struct Outcome {
changed: Vec<String>,
minted: usize,
written: bool,
}
impl Outcome {
const fn name(&self) -> &'static str {
if self.written {
"formatted"
} else if self.changed.is_empty() {
"canonical"
} else {
"drift"
}
}
}
pub fn run(root: &Path, check: bool, as_json: bool) -> Finished {
match attempt(root, check) {
Ok(outcome) => {
let name = outcome.name();
if as_json {
let fields = json!({
"changed": outcome.changed,
"minted": outcome.minted,
"written": outcome.written,
});
json::print(&json::result(SCHEMA, name, fields, Vec::new()));
} else {
for path in &outcome.changed {
outln!("{path}");
}
}
if name == "drift" {
Finished::failure(name)
} else {
Finished::success(name)
}
}
Err(stop) => stop.exit(SCHEMA, as_json),
}
}
fn attempt(root: &Path, check: bool) -> Result<Outcome, Stop> {
let store = FileStore::open(root)?;
let formatted = format(&store.read_files()?)?;
for (label, key) in &formatted.dropped {
errln!(
"spec/labels.yaml: the binding of {label} ({key}) names no node or decision, so \
`loft fmt` drops it"
);
}
let changed = formatted.changed.keys().map(ToString::to_string).collect();
let written = !check && !formatted.changed.is_empty();
if written {
store.write_files(formatted.changed)?;
}
Ok(Outcome {
changed,
minted: formatted.minted,
written,
})
}