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