use std::ffi::OsString; use std::path::PathBuf; use clap::{arg, Command}; fn cli() -> Command { Command::new("git") .about("A fictional versioning CLI") .subcommand_required(true) .arg_required_else_help(true) .allow_external_subcommands(true) .subcommand( Command::new("clone") .about("Clones repos") .arg(arg!( "The remote to clone")) .arg_required_else_help(true), ) .subcommand( Command::new("diff") .about("Compare two commits") .arg(arg!(base: [COMMIT])) .arg(arg!(head: [COMMIT])) .arg(arg!(path: [PATH]).last(true)) .arg( arg!(--color ) .value_parser(["always", "auto", "never"]) .num_args(0..=1) .require_equals(true) .default_value("auto") .default_missing_value("always"), ), ) .subcommand( Command::new("push") .about("pushes things") .arg(arg!( "The remote to target")) .arg_required_else_help(true), ) .subcommand( Command::new("add") .about("adds things") .arg_required_else_help(true) .arg(arg!( ... "Stuff to add").value_parser(clap::value_parser!(PathBuf))), ) .subcommand( Command::new("stash") .args_conflicts_with_subcommands(true) .args(push_args()) .subcommand(Command::new("push").args(push_args())) .subcommand(Command::new("pop").arg(arg!([STASH]))) .subcommand(Command::new("apply").arg(arg!([STASH]))), ) } fn push_args() -> Vec { vec![arg!(-m --message )] } fn main() { let matches = cli().get_matches(); match matches.subcommand() { Some(("clone", sub_matches)) => { println!( "Cloning {}", sub_matches.get_one::("REMOTE").expect("required") ); } Some(("diff", sub_matches)) => { let color = sub_matches .get_one::("color") .map(|s| s.as_str()) .expect("defaulted in clap"); let mut base = sub_matches.get_one::("base").map(|s| s.as_str()); let mut head = sub_matches.get_one::("head").map(|s| s.as_str()); let mut path = sub_matches.get_one::("path").map(|s| s.as_str()); if path.is_none() { path = head; head = None; if path.is_none() { path = base; base = None; } } let base = base.unwrap_or("stage"); let head = head.unwrap_or("worktree"); let path = path.unwrap_or(""); println!("Diffing {base}..{head} {path} (color={color})"); } Some(("push", sub_matches)) => { println!( "Pushing to {}", sub_matches.get_one::("REMOTE").expect("required") ); } Some(("add", sub_matches)) => { let paths = sub_matches .get_many::("PATH") .into_iter() .flatten() .collect::>(); println!("Adding {paths:?}"); } Some(("stash", sub_matches)) => { let stash_command = sub_matches.subcommand().unwrap_or(("push", sub_matches)); match stash_command { ("apply", sub_matches) => { let stash = sub_matches.get_one::("STASH"); println!("Applying {stash:?}"); } ("pop", sub_matches) => { let stash = sub_matches.get_one::("STASH"); println!("Popping {stash:?}"); } ("push", sub_matches) => { let message = sub_matches.get_one::("message"); println!("Pushing {message:?}"); } (name, _) => { unreachable!("Unsupported subcommand `{name}`") } } } Some((ext, sub_matches)) => { let args = sub_matches .get_many::("") .into_iter() .flatten() .collect::>(); println!("Calling out to {ext:?} with {args:?}"); } _ => unreachable!(), // If all subcommands are defined above, anything else is unreachable!() } // Continued program logic goes here... }