Module clap::_derive::_cookbook::git

source ·
Available on crate feature unstable-doc only.
Expand description

§Example: git-like CLI (Builder API)

use std::ffi::OsString;
use std::path::PathBuf;

use clap::{arg, Command};

fn cli() -> Command {
    Command::new("git")
        .about("A fictional versioning CLI")
        .subcommand_required(true)
        .arg_required_else_help(true)
        .allow_external_subcommands(true)
        .subcommand(
            Command::new("clone")
                .about("Clones repos")
                .arg(arg!(<REMOTE> "The remote to clone"))
                .arg_required_else_help(true),
        )
        .subcommand(
            Command::new("diff")
                .about("Compare two commits")
                .arg(arg!(base: [COMMIT]))
                .arg(arg!(head: [COMMIT]))
                .arg(arg!(path: [PATH]).last(true))
                .arg(
                    arg!(--color <WHEN>)
                        .value_parser(["always", "auto", "never"])
                        .num_args(0..=1)
                        .require_equals(true)
                        .default_value("auto")
                        .default_missing_value("always"),
                ),
        )
        .subcommand(
            Command::new("push")
                .about("pushes things")
                .arg(arg!(<REMOTE> "The remote to target"))
                .arg_required_else_help(true),
        )
        .subcommand(
            Command::new("add")
                .about("adds things")
                .arg_required_else_help(true)
                .arg(arg!(<PATH> ... "Stuff to add").value_parser(clap::value_parser!(PathBuf))),
        )
        .subcommand(
            Command::new("stash")
                .args_conflicts_with_subcommands(true)
                .flatten_help(true)
                .args(push_args())
                .subcommand(Command::new("push").args(push_args()))
                .subcommand(Command::new("pop").arg(arg!([STASH])))
                .subcommand(Command::new("apply").arg(arg!([STASH]))),
        )
}

fn push_args() -> Vec<clap::Arg> {
    vec![arg!(-m --message <MESSAGE>)]
}

fn main() {
    let matches = cli().get_matches();

    match matches.subcommand() {
        Some(("clone", sub_matches)) => {
            println!(
                "Cloning {}",
                sub_matches.get_one::<String>("REMOTE").expect("required")
            );
        }
        Some(("diff", sub_matches)) => {
            let color = sub_matches
                .get_one::<String>("color")
                .map(|s| s.as_str())
                .expect("defaulted in clap");

            let mut base = sub_matches.get_one::<String>("base").map(|s| s.as_str());
            let mut head = sub_matches.get_one::<String>("head").map(|s| s.as_str());
            let mut path = sub_matches.get_one::<String>("path").map(|s| s.as_str());
            if path.is_none() {
                path = head;
                head = None;
                if path.is_none() {
                    path = base;
                    base = None;
                }
            }
            let base = base.unwrap_or("stage");
            let head = head.unwrap_or("worktree");
            let path = path.unwrap_or("");
            println!("Diffing {base}..{head} {path} (color={color})");
        }
        Some(("push", sub_matches)) => {
            println!(
                "Pushing to {}",
                sub_matches.get_one::<String>("REMOTE").expect("required")
            );
        }
        Some(("add", sub_matches)) => {
            let paths = sub_matches
                .get_many::<PathBuf>("PATH")
                .into_iter()
                .flatten()
                .collect::<Vec<_>>();
            println!("Adding {paths:?}");
        }
        Some(("stash", sub_matches)) => {
            let stash_command = sub_matches.subcommand().unwrap_or(("push", sub_matches));
            match stash_command {
                ("apply", sub_matches) => {
                    let stash = sub_matches.get_one::<String>("STASH");
                    println!("Applying {stash:?}");
                }
                ("pop", sub_matches) => {
                    let stash = sub_matches.get_one::<String>("STASH");
                    println!("Popping {stash:?}");
                }
                ("push", sub_matches) => {
                    let message = sub_matches.get_one::<String>("message");
                    println!("Pushing {message:?}");
                }
                (name, _) => {
                    unreachable!("Unsupported subcommand `{name}`")
                }
            }
        }
        Some((ext, sub_matches)) => {
            let args = sub_matches
                .get_many::<OsString>("")
                .into_iter()
                .flatten()
                .collect::<Vec<_>>();
            println!("Calling out to {ext:?} with {args:?}");
        }
        _ => unreachable!(), // If all subcommands are defined above, anything else is unreachable!()
    }

    // Continued program logic goes here...
}

Git is an example of several common subcommand patterns.

Help:

$ git
? failed
A fictional versioning CLI

Usage: git[EXE] <COMMAND>

Commands:
  clone  Clones repos
  diff   Compare two commits
  push   pushes things
  add    adds things
  stash  
  help   Print this message or the help of the given subcommand(s)

Options:
  -h, --help  Print help

$ git help
A fictional versioning CLI

Usage: git[EXE] <COMMAND>

Commands:
  clone  Clones repos
  diff   Compare two commits
  push   pushes things
  add    adds things
  stash  
  help   Print this message or the help of the given subcommand(s)

Options:
  -h, --help  Print help

$ git help add
adds things

Usage: git[EXE] add <PATH>...

Arguments:
  <PATH>...  Stuff to add

Options:
  -h, --help  Print help

A basic argument:

$ git add
? failed
adds things

Usage: git[EXE] add <PATH>...

Arguments:
  <PATH>...  Stuff to add

Options:
  -h, --help  Print help

$ git add Cargo.toml Cargo.lock
Adding ["Cargo.toml", "Cargo.lock"]

Default subcommand:

$ git stash -h
Usage: git[EXE] stash [OPTIONS]
       git[EXE] stash push [OPTIONS]
       git[EXE] stash pop [STASH]
       git[EXE] stash apply [STASH]
       git[EXE] stash help [COMMAND]...

Options:
  -m, --message <MESSAGE>  
  -h, --help               Print help

git[EXE] stash push:
  -m, --message <MESSAGE>  
  -h, --help               Print help

git[EXE] stash pop:
  -h, --help   Print help
  [STASH]  

git[EXE] stash apply:
  -h, --help   Print help
  [STASH]  

git[EXE] stash help:
Print this message or the help of the given subcommand(s)
  [COMMAND]...  Print help for the subcommand(s)

$ git stash push -h
Usage: git[EXE] stash push [OPTIONS]

Options:
  -m, --message <MESSAGE>  
  -h, --help               Print help

$ git stash pop -h
Usage: git[EXE] stash pop [STASH]

Arguments:
  [STASH]  

Options:
  -h, --help  Print help

$ git stash -m "Prototype"
Pushing Some("Prototype")

$ git stash pop
Popping None

$ git stash push -m "Prototype"
Pushing Some("Prototype")

$ git stash pop
Popping None

External subcommands:

$ git custom-tool arg1 --foo bar
Calling out to "custom-tool" with ["arg1", "--foo", "bar"]

Last argument:

$ git diff --help
Compare two commits

Usage: git[EXE] diff [OPTIONS] [COMMIT] [COMMIT] [-- <PATH>]

Arguments:
  [COMMIT]  
  [COMMIT]  
  [PATH]    

Options:
      --color[=<WHEN>]  [default: auto] [possible values: always, auto, never]
  -h, --help            Print help

$ git diff
Diffing stage..worktree  (color=auto)

$ git diff ./src
Diffing stage..worktree ./src (color=auto)

$ git diff HEAD ./src
Diffing HEAD..worktree ./src (color=auto)

$ git diff HEAD~~ -- HEAD
Diffing HEAD~~..worktree HEAD (color=auto)

$ git diff --color
Diffing stage..worktree  (color=always)

$ git diff --color=never
Diffing stage..worktree  (color=never)