1use std::ffi::OsStr; 2use std::ffi::OsString; 3use std::path::PathBuf; 4 5use clap::{Args, Parser, Subcommand, ValueEnum}; 6 7/// A fictional versioning CLI 8#[derive(Debug, Parser)] // requires `derive` feature 9#[command(name = "git")] 10#[command(about = "A fictional versioning CLI", long_about = None)] 11struct Cli { 12 #[command(subcommand)] 13 command: Commands, 14} 15 16#[derive(Debug, Subcommand)] 17enum Commands { 18 /// Clones repos 19 #[command(arg_required_else_help = true)] 20 Clone { 21 /// The remote to clone 22 remote: String, 23 }, 24 /// Compare two commits 25 Diff { 26 #[arg(value_name = "COMMIT")] 27 base: Option<OsString>, 28 #[arg(value_name = "COMMIT")] 29 head: Option<OsString>, 30 #[arg(last = true)] 31 path: Option<OsString>, 32 #[arg( 33 long, 34 require_equals = true, 35 value_name = "WHEN", 36 num_args = 0..=1, 37 default_value_t = ColorWhen::Auto, 38 default_missing_value = "always", 39 value_enum 40 )] 41 color: ColorWhen, 42 }, 43 /// pushes things 44 #[command(arg_required_else_help = true)] 45 Push { 46 /// The remote to target 47 remote: String, 48 }, 49 /// adds things 50 #[command(arg_required_else_help = true)] 51 Add { 52 /// Stuff to add 53 #[arg(required = true)] 54 path: Vec<PathBuf>, 55 }, 56 Stash(StashArgs), 57 #[command(external_subcommand)] 58 External(Vec<OsString>), 59} 60 61#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)] 62enum ColorWhen { 63 Always, 64 Auto, 65 Never, 66} 67 68impl std::fmt::Display for ColorWhen { 69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 70 self.to_possible_value() 71 .expect("no values are skipped") 72 .get_name() 73 .fmt(f) 74 } 75} 76 77#[derive(Debug, Args)] 78#[command(args_conflicts_with_subcommands = true)] 79struct StashArgs { 80 #[command(subcommand)] 81 command: Option<StashCommands>, 82 83 #[command(flatten)] 84 push: StashPushArgs, 85} 86 87#[derive(Debug, Subcommand)] 88enum StashCommands { 89 Push(StashPushArgs), 90 Pop { stash: Option<String> }, 91 Apply { stash: Option<String> }, 92} 93 94#[derive(Debug, Args)] 95struct StashPushArgs { 96 #[arg(short, long)] 97 message: Option<String>, 98} 99 100fn main() { 101 let args = Cli::parse(); 102 103 match args.command { 104 Commands::Clone { remote } => { 105 println!("Cloning {remote}"); 106 } 107 Commands::Diff { 108 mut base, 109 mut head, 110 mut path, 111 color, 112 } => { 113 if path.is_none() { 114 path = head; 115 head = None; 116 if path.is_none() { 117 path = base; 118 base = None; 119 } 120 } 121 let base = base 122 .as_deref() 123 .map(|s| s.to_str().unwrap()) 124 .unwrap_or("stage"); 125 let head = head 126 .as_deref() 127 .map(|s| s.to_str().unwrap()) 128 .unwrap_or("worktree"); 129 let path = path.as_deref().unwrap_or_else(|| OsStr::new("")); 130 println!( 131 "Diffing {}..{} {} (color={})", 132 base, 133 head, 134 path.to_string_lossy(), 135 color 136 ); 137 } 138 Commands::Push { remote } => { 139 println!("Pushing to {remote}"); 140 } 141 Commands::Add { path } => { 142 println!("Adding {path:?}"); 143 } 144 Commands::Stash(stash) => { 145 let stash_cmd = stash.command.unwrap_or(StashCommands::Push(stash.push)); 146 match stash_cmd { 147 StashCommands::Push(push) => { 148 println!("Pushing {push:?}"); 149 } 150 StashCommands::Pop { stash } => { 151 println!("Popping {stash:?}"); 152 } 153 StashCommands::Apply { stash } => { 154 println!("Applying {stash:?}"); 155 } 156 } 157 } 158 Commands::External(args) => { 159 println!("Calling out to {:?} with {:?}", &args[0], &args[1..]); 160 } 161 } 162 163 // Continued program logic goes here... 164} 165