use std::path::{Path, PathBuf};
use dbmd_core::parser::{read_file, write_file};
use dbmd_core::validate::codes;
use dbmd_core::Store;
use crate::cli::FormatArgs;
use crate::context::Context;
use crate::error::{CliError, CliResult, ExitCode};
pub fn run(ctx: &Context, args: &FormatArgs) -> CliResult {
let file = Path::new(&args.file);
let store = locate_store(file)?;
let rel = store_relative(&store, file);
if store.config.is_frozen(&rel) {
return Err(CliError::new(
ExitCode::Policy,
codes::POLICY_FROZEN_PAGE,
format!("`{}` is a frozen page; refusing to format", rel.display()),
)
.with_hint("remove it from DB.md ## Policies → ### Frozen pages to allow writes"));
}
let original = std::fs::read_to_string(file).map_err(CliError::from)?;
let (frontmatter, body) = read_file(file).map_err(|e| {
CliError::from(dbmd_core::Error::from(e))
.with_hint(format!("could not read `{}`", args.file))
})?;
write_file(file, &frontmatter, &body).map_err(|e| {
CliError::from(dbmd_core::Error::from(e))
.with_hint(format!("could not write `{}`", args.file))
})?;
let reformatted = std::fs::read_to_string(file).map_err(CliError::from)?;
let changed = reformatted != original;
if ctx.json {
let obj = serde_json::json!({
"file": rel.to_string_lossy(),
"changed": changed,
});
let mut s = serde_json::to_string_pretty(&obj).unwrap_or_else(|_| "{}".to_string());
s.push('\n');
print!("{s}");
} else if changed {
println!("formatted {}", rel.display());
} else {
println!("{} already canonical", rel.display());
}
Ok(())
}
fn locate_store(file: &Path) -> Result<Store, CliError> {
let start = file.parent().unwrap_or(Path::new("."));
let start = std::fs::canonicalize(start).unwrap_or_else(|_| start.to_path_buf());
let mut dir: Option<&Path> = Some(start.as_path());
while let Some(d) = dir {
if Store::is_db_md_store(d) {
return Store::open(d).map_err(|e| CliError::from(dbmd_core::Error::from(e)));
}
dir = d.parent();
}
Store::open(&start).map_err(|e| CliError::from(dbmd_core::Error::from(e)))
}
fn store_relative(store: &Store, file: &Path) -> PathBuf {
let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
let canonical_root = std::fs::canonicalize(&store.root).unwrap_or_else(|_| store.root.clone());
canonical_file
.strip_prefix(&canonical_root)
.map(|p| p.to_path_buf())
.unwrap_or_else(|_| file.to_path_buf())
}