Skip to main content

## Module git_derive

Source
Available on crate feature `unstable-doc` only.
Expand description

## §Example: git-like CLI (Derive API)

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

    use clap::{Args, Parser, Subcommand, ValueEnum};

    /// A fictional versioning CLI
    #[derive(Debug, Parser)] // requires `derive` feature
    #[command(name = "git")]
    #[command(about = "A fictional versioning CLI", long_about = None)]
    struct Cli {
        #[command(subcommand)]
        command: Commands,
    }

    #[derive(Debug, Subcommand)]
    enum Commands {
        /// Clones repos
        #[command(arg_required_else_help = true)]
        Clone {
            /// The remote to clone
            remote: String,
        },
        /// Compare two commits
        Diff {
            #[arg(value_name = "COMMIT")]
            base: Option<OsString>,
            #[arg(value_name = "COMMIT")]
            head: Option<OsString>,
            #[arg(last = true)]
            path: Option<OsString>,
            #[arg(
                long,
                require_equals = true,
                value_name = "WHEN",
                num_args = 0..=1,
                default_value_t = ColorWhen::Auto,
                default_missing_value = "always",
                value_enum
            )]
            color: ColorWhen,
        },
        /// pushes things
        #[command(arg_required_else_help = true)]
        Push {
            /// The remote to target
            remote: String,
        },
        /// adds things
        #[command(arg_required_else_help = true)]
        Add {
            /// Stuff to add
            #[arg(required = true)]
            path: Vec<PathBuf>,
        },
        Stash(StashArgs),
        #[command(external_subcommand)]
        External(Vec<OsString>),
    }

    #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
    enum ColorWhen {
        Always,
        Auto,
        Never,
    }

    impl std::fmt::Display for ColorWhen {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            self.to_possible_value()
                .expect("no values are skipped")
                .get_name()
                .fmt(f)
        }
    }

    #[derive(Debug, Args)]
    #[command(args_conflicts_with_subcommands = true)]
    #[command(flatten_help = true)]
    struct StashArgs {
        #[command(subcommand)]
        command: Option<StashCommands>,

        #[command(flatten)]
        push: StashPushArgs,
    }

    #[derive(Debug, Subcommand)]
    enum StashCommands {
        Push(StashPushArgs),
        Pop { stash: Option<String> },
        Apply { stash: Option<String> },
    }

    #[derive(Debug, Args)]
    struct StashPushArgs {
        #[arg(short, long)]
        message: Option<String>,
    }

    fn main() {
        let args = Cli::parse();

        match args.command {
            Commands::Clone { remote } => {
                println!("Cloning {remote}");
            }
            Commands::Diff {
                mut base,
                mut head,
                mut path,
                color,
            } => {
                if path.is_none() {
                    path = head;
                    head = None;
                    if path.is_none() {
                        path = base;
                        base = None;
                    }
                }
                let base = base
                    .as_deref()
                    .map(|s| s.to_str().unwrap())
                    .unwrap_or("stage");
                let head = head
                    .as_deref()
                    .map(|s| s.to_str().unwrap())
                    .unwrap_or("worktree");
                let path = path.as_deref().unwrap_or_else(|| OsStr::new(""));
                println!(
                    "Diffing {}..{} {} (color={})",
                    base,
                    head,
                    path.to_string_lossy(),
                    color
                );
            }
            Commands::Push { remote } => {
                println!("Pushing to {remote}");
            }
            Commands::Add { path } => {
                println!("Adding {path:?}");
            }
            Commands::Stash(stash) => {
                let stash_cmd = stash.command.unwrap_or(StashCommands::Push(stash.push));
                match stash_cmd {
                    StashCommands::Push(push) => {
                        println!("Pushing {push:?}");
                    }
                    StashCommands::Pop { stash } => {
                        println!("Popping {stash:?}");
                    }
                    StashCommands::Apply { stash } => {
                        println!("Applying {stash:?}");
                    }
                }
            }
            Commands::External(args) => {
                println!("Calling out to {:?} with {:?}", args[0], &args[1..]);
            }
        }

        // Continued program logic goes here...
    }

This requires enabling the `derive` feature flag.

Git is an example of several common subcommand patterns.

Help:

    $ git-derive
    ? failed
    A fictional versioning CLI

    Usage: git-derive[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-derive help
    A fictional versioning CLI

    Usage: git-derive[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-derive help add
    adds things

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

    Arguments:
      <PATH>...  Stuff to add

    Options:
      -h, --help  Print help

A basic argument:

    $ git-derive add
    ? failed
    adds things

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

    Arguments:
      <PATH>...  Stuff to add

    Options:
      -h, --help  Print help

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

Default subcommand:

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

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

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

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

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

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

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

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

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

    Arguments:
      [STASH]

    Options:
      -h, --help  Print help

    $ git-derive stash -m "Prototype"
    Pushing StashPushArgs { message: Some("Prototype") }

    $ git-derive stash pop
    Popping None

    $ git-derive stash push -m "Prototype"
    Pushing StashPushArgs { message: Some("Prototype") }

    $ git-derive stash pop
    Popping None

External subcommands:

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

Last argument:

    $ git-derive diff --help
    Compare two commits

    Usage: git-derive[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-derive diff
    Diffing stage..worktree  (color=auto)

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

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

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

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

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