crates/cli/src/cmd/mod.rs 3.7 KiB 118 lines Raw
1 #![forbid(unsafe_code)]2 //! Dispatch: one match arm + one module per subcommand.3 //! Feature agents: replace the stub body in their file, add nothing here4 //! except the single registration line (already present).5 6 pub mod ci;7 pub mod init;8 pub mod issue;9 pub mod label;10 pub mod merge;11 pub mod milestone;12 pub mod pr;13 pub mod release;14 pub mod skill;15 pub mod spec;16 pub mod task;17 pub mod wiki;18 19 use crate::args::Global;20 use crate::CliError;21 22 /// Root help: <=12 lines, <=300 bytes. Whole surface from here.23 /// Command map only; contract lives in `f0rge --skill`.24 pub const ROOT_HELP: &str = "f0rge: local forge\n\25 usage: f0rge [--skill] <cmd> [--json|--jq E] [args]\n\26 issue pr task label milestone ci wiki\n\27 release merge init skill spec\n\28 'cmd -h' for help\n\29 contract: f0rge --skill\n";30 31 /// Dispatch over subcommand names (mirror of folder tree).32 pub fn dispatch(33     sub: &str,34     args: &[String],35     fg: &f0rge_core::Forge,36     _g: &Global,37 ) -> Result<(), CliError> {38     // -h anywhere in sub args prints help: per-verb const when the39     // first bare token names a verb (#36), else the domain block.40     if args.iter().any(|a| a == "-h" || a == "--help") {41         if let Some(h) = verb_help_for(sub, args) {42             crate::output::out(h);43             return Ok(());44         }45         crate::output::out(help_for(sub).unwrap_or(ROOT_HELP));46         return Ok(());47     }48     match sub {49         "issue" => issue::run(args, fg),50         "pr" => pr::run(args, fg),51         "task" => task::run(args, fg),52         "label" => label::run(args, fg),53         "milestone" => milestone::run(args, fg),54         "ci" => ci::run(args, fg),55         "wiki" => wiki::run(args, fg),56         "release" => release::run(args, fg),57         "merge" => merge::run(args, fg),58         "init" => init::run(args, fg),59         "skill" => skill::run(args, fg),60         "spec" => spec::run(args, fg),61         _ => Err(CliError::Usage(format!("unknown command: {sub}"))),62     }63 }64 65 /// Two-arg help (#36): first bare token names the verb. None when the66 /// subcommand has no verbs (merge/skill) or the verb is unknown, so the67 /// domain block prints. Only fix-help verbs route here; other commands68 /// keep domain help until their owners add verb_help().69 fn verb_help_for(sub: &str, args: &[String]) -> Option<&'static str> {70     let verb = args.first().map(|s| s.as_str()).filter(|s| !s.starts_with('-'))?;71     Some(match sub {72         "issue" => issue::verb_help(verb)?,73         "pr" => pr::verb_help(verb)?,74         "ci" => ci::verb_help(verb)?,75         "wiki" => wiki::verb_help(verb)?,76         "release" => release::verb_help(verb)?,77         "merge" => merge::verb_help(verb)?,78         "skill" => skill::verb_help(verb)?,79         _ => return None,80     })81 }82 83 fn help_for(sub: &str) -> Option<&'static str> {84     Some(match sub {85         "issue" => issue::help(),86         "pr" => pr::help(),87         "task" => task::help(),88         "label" => label::help(),89         "milestone" => milestone::help(),90         "ci" => ci::help(),91         "wiki" => wiki::help(),92         "release" => release::help(),93         "merge" => merge::help(),94         "init" => init::help(),95         "skill" => skill::help(),96         "spec" => spec::help(),97         _ => return None,98     })99 }100 101 /// All sub helps for budget tests (exactly 12).102 #[allow(dead_code)]103 pub fn all_helps() -> Vec<(&'static str, &'static str)> {104     vec![105         ("issue", issue::help()),106         ("pr", pr::help()),107         ("task", task::help()),108         ("label", label::help()),109         ("milestone", milestone::help()),110         ("ci", ci::help()),111         ("wiki", wiki::help()),112         ("release", release::help()),113         ("merge", merge::help()),114         ("init", init::help()),115         ("skill", skill::help()),116         ("spec", spec::help()),117     ]118 }