• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result69     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 struct StashArgs {
80     #[command(subcommand)]
81     command: Option<StashCommands>,
82 
83     #[command(flatten)]
84     push: StashPushArgs,
85 }
86 
87 #[derive(Debug, Subcommand)]
88 enum StashCommands {
89     Push(StashPushArgs),
90     Pop { stash: Option<String> },
91     Apply { stash: Option<String> },
92 }
93 
94 #[derive(Debug, Args)]
95 struct StashPushArgs {
96     #[arg(short, long)]
97     message: Option<String>,
98 }
99 
main()100 fn 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