| 1 | use std::ffi::OsString; |
| 2 | use std::path::PathBuf; |
| 3 | |
| 4 | use clap::{arg, Command}; |
| 5 | |
| 6 | fn cli() -> Command { |
| 7 | Command::new("git" ) |
| 8 | .about("A fictional versioning CLI" ) |
| 9 | .subcommand_required(true) |
| 10 | .arg_required_else_help(true) |
| 11 | .allow_external_subcommands(true) |
| 12 | .subcommand( |
| 13 | Command::new("clone" ) |
| 14 | .about("Clones repos" ) |
| 15 | .arg(arg!(<REMOTE> "The remote to clone" )) |
| 16 | .arg_required_else_help(true), |
| 17 | ) |
| 18 | .subcommand( |
| 19 | Command::new("diff" ) |
| 20 | .about("Compare two commits" ) |
| 21 | .arg(arg!(base: [COMMIT])) |
| 22 | .arg(arg!(head: [COMMIT])) |
| 23 | .arg(arg!(path: [PATH]).last(true)) |
| 24 | .arg( |
| 25 | arg!(--color <WHEN>) |
| 26 | .value_parser(["always" , "auto" , "never" ]) |
| 27 | .num_args(0..=1) |
| 28 | .require_equals(true) |
| 29 | .default_value("auto" ) |
| 30 | .default_missing_value("always" ), |
| 31 | ), |
| 32 | ) |
| 33 | .subcommand( |
| 34 | Command::new("push" ) |
| 35 | .about("pushes things" ) |
| 36 | .arg(arg!(<REMOTE> "The remote to target" )) |
| 37 | .arg_required_else_help(true), |
| 38 | ) |
| 39 | .subcommand( |
| 40 | Command::new("add" ) |
| 41 | .about("adds things" ) |
| 42 | .arg_required_else_help(true) |
| 43 | .arg(arg!(<PATH> ... "Stuff to add" ).value_parser(clap::value_parser!(PathBuf))), |
| 44 | ) |
| 45 | .subcommand( |
| 46 | Command::new("stash" ) |
| 47 | .args_conflicts_with_subcommands(true) |
| 48 | .flatten_help(true) |
| 49 | .args(push_args()) |
| 50 | .subcommand(Command::new("push" ).args(push_args())) |
| 51 | .subcommand(Command::new("pop" ).arg(arg!([STASH]))) |
| 52 | .subcommand(Command::new("apply" ).arg(arg!([STASH]))), |
| 53 | ) |
| 54 | } |
| 55 | |
| 56 | fn push_args() -> Vec<clap::Arg> { |
| 57 | vec![arg!(-m --message <MESSAGE>)] |
| 58 | } |
| 59 | |
| 60 | fn main() { |
| 61 | let matches = cli().get_matches(); |
| 62 | |
| 63 | match matches.subcommand() { |
| 64 | Some(("clone" , sub_matches)) => { |
| 65 | println!( |
| 66 | "Cloning {}" , |
| 67 | sub_matches.get_one::<String>("REMOTE" ).expect("required" ) |
| 68 | ); |
| 69 | } |
| 70 | Some(("diff" , sub_matches)) => { |
| 71 | let color = sub_matches |
| 72 | .get_one::<String>("color" ) |
| 73 | .map(|s| s.as_str()) |
| 74 | .expect("defaulted in clap" ); |
| 75 | |
| 76 | let mut base = sub_matches.get_one::<String>("base" ).map(|s| s.as_str()); |
| 77 | let mut head = sub_matches.get_one::<String>("head" ).map(|s| s.as_str()); |
| 78 | let mut path = sub_matches.get_one::<String>("path" ).map(|s| s.as_str()); |
| 79 | if path.is_none() { |
| 80 | path = head; |
| 81 | head = None; |
| 82 | if path.is_none() { |
| 83 | path = base; |
| 84 | base = None; |
| 85 | } |
| 86 | } |
| 87 | let base = base.unwrap_or("stage" ); |
| 88 | let head = head.unwrap_or("worktree" ); |
| 89 | let path = path.unwrap_or("" ); |
| 90 | println!("Diffing {base}..{head} {path} (color={color})" ); |
| 91 | } |
| 92 | Some(("push" , sub_matches)) => { |
| 93 | println!( |
| 94 | "Pushing to {}" , |
| 95 | sub_matches.get_one::<String>("REMOTE" ).expect("required" ) |
| 96 | ); |
| 97 | } |
| 98 | Some(("add" , sub_matches)) => { |
| 99 | let paths = sub_matches |
| 100 | .get_many::<PathBuf>("PATH" ) |
| 101 | .into_iter() |
| 102 | .flatten() |
| 103 | .collect::<Vec<_>>(); |
| 104 | println!("Adding {paths:?}" ); |
| 105 | } |
| 106 | Some(("stash" , sub_matches)) => { |
| 107 | let stash_command = sub_matches.subcommand().unwrap_or(("push" , sub_matches)); |
| 108 | match stash_command { |
| 109 | ("apply" , sub_matches) => { |
| 110 | let stash = sub_matches.get_one::<String>("STASH" ); |
| 111 | println!("Applying {stash:?}" ); |
| 112 | } |
| 113 | ("pop" , sub_matches) => { |
| 114 | let stash = sub_matches.get_one::<String>("STASH" ); |
| 115 | println!("Popping {stash:?}" ); |
| 116 | } |
| 117 | ("push" , sub_matches) => { |
| 118 | let message = sub_matches.get_one::<String>("message" ); |
| 119 | println!("Pushing {message:?}" ); |
| 120 | } |
| 121 | (name, _) => { |
| 122 | unreachable!("Unsupported subcommand `{name}`" ) |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | Some((ext, sub_matches)) => { |
| 127 | let args = sub_matches |
| 128 | .get_many::<OsString>("" ) |
| 129 | .into_iter() |
| 130 | .flatten() |
| 131 | .collect::<Vec<_>>(); |
| 132 | println!("Calling out to {ext:?} with {args:?}" ); |
| 133 | } |
| 134 | _ => unreachable!(), // If all subcommands are defined above, anything else is unreachable!() |
| 135 | } |
| 136 | |
| 137 | // Continued program logic goes here... |
| 138 | } |
| 139 | |