| 1 | use std::ffi::OsStr; |
| 2 | use std::ffi::OsString; |
| 3 | use std::path::PathBuf; |
| 4 | |
| 5 | use 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)] |
| 11 | struct Cli { |
| 12 | #[command(subcommand)] |
| 13 | command: Commands, |
| 14 | } |
| 15 | |
| 16 | #[derive(Debug, Subcommand)] |
| 17 | enum 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)] |
| 62 | enum ColorWhen { |
| 63 | Always, |
| 64 | Auto, |
| 65 | Never, |
| 66 | } |
| 67 | |
| 68 | impl 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)] |
| 79 | #[command(flatten_help = true)] |
| 80 | struct StashArgs { |
| 81 | #[command(subcommand)] |
| 82 | command: Option<StashCommands>, |
| 83 | |
| 84 | #[command(flatten)] |
| 85 | push: StashPushArgs, |
| 86 | } |
| 87 | |
| 88 | #[derive(Debug, Subcommand)] |
| 89 | enum StashCommands { |
| 90 | Push(StashPushArgs), |
| 91 | Pop { stash: Option<String> }, |
| 92 | Apply { stash: Option<String> }, |
| 93 | } |
| 94 | |
| 95 | #[derive(Debug, Args)] |
| 96 | struct StashPushArgs { |
| 97 | #[arg(short, long)] |
| 98 | message: Option<String>, |
| 99 | } |
| 100 | |
| 101 | fn main() { |
| 102 | let args = Cli::parse(); |
| 103 | |
| 104 | match args.command { |
| 105 | Commands::Clone { remote } => { |
| 106 | println!("Cloning {remote}" ); |
| 107 | } |
| 108 | Commands::Diff { |
| 109 | mut base, |
| 110 | mut head, |
| 111 | mut path, |
| 112 | color, |
| 113 | } => { |
| 114 | if path.is_none() { |
| 115 | path = head; |
| 116 | head = None; |
| 117 | if path.is_none() { |
| 118 | path = base; |
| 119 | base = None; |
| 120 | } |
| 121 | } |
| 122 | let base = base |
| 123 | .as_deref() |
| 124 | .map(|s| s.to_str().unwrap()) |
| 125 | .unwrap_or("stage" ); |
| 126 | let head = head |
| 127 | .as_deref() |
| 128 | .map(|s| s.to_str().unwrap()) |
| 129 | .unwrap_or("worktree" ); |
| 130 | let path = path.as_deref().unwrap_or_else(|| OsStr::new("" )); |
| 131 | println!( |
| 132 | "Diffing {}..{} {} (color={})" , |
| 133 | base, |
| 134 | head, |
| 135 | path.to_string_lossy(), |
| 136 | color |
| 137 | ); |
| 138 | } |
| 139 | Commands::Push { remote } => { |
| 140 | println!("Pushing to {remote}" ); |
| 141 | } |
| 142 | Commands::Add { path } => { |
| 143 | println!("Adding {path:?}" ); |
| 144 | } |
| 145 | Commands::Stash(stash) => { |
| 146 | let stash_cmd = stash.command.unwrap_or(StashCommands::Push(stash.push)); |
| 147 | match stash_cmd { |
| 148 | StashCommands::Push(push) => { |
| 149 | println!("Pushing {push:?}" ); |
| 150 | } |
| 151 | StashCommands::Pop { stash } => { |
| 152 | println!("Popping {stash:?}" ); |
| 153 | } |
| 154 | StashCommands::Apply { stash } => { |
| 155 | println!("Applying {stash:?}" ); |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | Commands::External(args) => { |
| 160 | println!("Calling out to {:?} with {:?}" , &args[0], &args[1..]); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | // Continued program logic goes here... |
| 165 | } |
| 166 | |