crates/cli/src/cmd/spec.rs 2.0 KiB 58 lines Raw
1 #![forbid(unsafe_code)]2 //! f0rge spec: print/write the embedded canonical spec/schema.md.3 //! Single source of truth: include_str! of the repo spec, no scrubbed copy.4 use crate::CliError;5 6 /// Embedded canonical spec: single source, no drift.7 pub const SPEC_TEXT: &str = include_str!("../../../../spec/schema.md");8 9 pub fn help() -> &'static str {10     "f0rge spec: schema\nusage: f0rge spec [--write]\nprints embedded spec/schema.md\ndocs: spec/schema.md §Spec\ncontract: f0rge --skill\n"11 }12 13 pub fn run(args: &[String], _fg: &f0rge_core::Forge) -> Result<(), CliError> {14     let mut write = false;15     for a in args {16         if a == "-h" || a == "--help" {17             crate::output::out(help());18             return Ok(());19         }20         if a == "--write" {21             write = true;22             continue;23         }24         if a.starts_with('-') {25             return Err(CliError::Usage(format!("unknown flag: {a}")));26         }27         return Err(CliError::Usage("spec [--write]".to_string()));28     }29     if write {30         let cwd = std::env::current_dir().map_err(|_| CliError::Io("cwd: io error".to_string()))?;31         write_spec(&cwd).map_err(|e| CliError::Io(e.to_string()))?;32         return Ok(());33     }34     crate::output::out(SPEC_TEXT);35     if !SPEC_TEXT.ends_with('\n') {36         crate::output::out("\n");37     }38     Ok(())39 }40 41 fn io_err(file: &str) -> std::io::Error {42     std::io::Error::other(file.to_string())43 }44 45 /// Overwrite spec/schema.md (atomic tmp+rename). Creates spec/ dir.46 fn write_spec(target: &std::path::Path) -> Result<(), std::io::Error> {47     use std::io::Write;48     let dir = target.join("spec");49     std::fs::create_dir_all(&dir)?;50     let path = dir.join("schema.md");51     let tmp = path.with_extension("tmp");52     let mut f = std::fs::File::create(&tmp).map_err(|_| io_err("schema.md"))?;53     f.write_all(SPEC_TEXT.as_bytes()).map_err(|_| io_err("schema.md"))?;54     f.sync_all().map_err(|_| io_err("schema.md"))?;55     drop(f);56     std::fs::rename(&tmp, &path).map_err(|_| io_err("schema.md"))?;57     Ok(())58 }