git_derive/
git-derive.rs

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)]
79#[command(flatten_help = true)]
80struct StashArgs {
81    #[command(subcommand)]
82    command: Option<StashCommands>,
83
84    #[command(flatten)]
85    push: StashPushArgs,
86}
87
88#[derive(Debug, Subcommand)]
89enum StashCommands {
90    Push(StashPushArgs),
91    Pop { stash: Option<String> },
92    Apply { stash: Option<String> },
93}
94
95#[derive(Debug, Args)]
96struct StashPushArgs {
97    #[arg(short, long)]
98    message: Option<String>,
99}
100
101fn 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}