|
| 1 | +// Working with subcommands is simple. There are a few key points to remember when working with |
| 2 | +// subcommands in clap. First, SubCommands are really just Apps. This means they can have their own |
| 3 | +// settings, version, authors, args, and even their own subcommands. The next thing to remember is |
| 4 | +// that subcommands are set up in a tree like heirachy. |
| 5 | +// |
| 6 | +// An ASCII art depiction may help explain this better. Using a fictional version of git as the demo |
| 7 | +// subject. Imagine the following are all subcommands of git (note, the author is aware these aren't |
| 8 | +// actually all subcommands in the real git interface, but it makes explanation easier) |
| 9 | +// |
| 10 | +// Top Level App (git) TOP |
| 11 | +// | |
| 12 | +// ----------------------------------------- |
| 13 | +// / | \ \ |
| 14 | +// clone push add commit LEVEL 1 |
| 15 | +// | / \ / \ | |
| 16 | +// url origin remote ref name message LEVEL 2 |
| 17 | +// / /\ |
| 18 | +// path remote local LEVEL 3 |
| 19 | +// |
| 20 | +// Given the above fictional subcommand hierarchy, valid runtime uses would be (not an all inclusive |
| 21 | +// list): |
| 22 | +// |
| 23 | +// $ git clone url |
| 24 | +// $ git push origin path |
| 25 | +// $ git add ref local |
| 26 | +// $ git commit message |
| 27 | +// |
| 28 | +// Notice only one command per "level" may be used. You could not, for example, do: |
| 29 | +// |
| 30 | +// $ git clone url push origin path |
| 31 | +// |
| 32 | +// It's also important to know that subcommands each have their own set of matches and may have args |
| 33 | +// with the same name as other subcommands in a different part of the tree heirachy (i.e. the arg |
| 34 | +// names aren't in a flat namespace). |
| 35 | +// |
| 36 | +// In order to use subcommands in clap, you only need to know which subcommand you're at in your |
| 37 | +// tree, and which args are defined on that subcommand. |
| 38 | +// |
| 39 | +// Let's make a quick program to illustrate. We'll be using the same example as above but for |
| 40 | +// brevity sake we won't implement all of the subcommands, only a few. |
| 41 | + |
| 42 | +extern crate clap; |
| 43 | + |
| 44 | +use clap::{App, Arg, SubCommand, AppSettings}; |
| 45 | + |
| 46 | +fn main() { |
| 47 | + |
| 48 | + let matches = App::new("git") |
| 49 | + .about("A fictional versioning CLI") |
| 50 | + .version("1.0") |
| 51 | + .author("Me") |
| 52 | + .subcommand(SubCommand::with_name("clone") |
| 53 | + .about("clones repos") |
| 54 | + .arg(Arg::with_name("repo") |
| 55 | + .help("The repo to clone") |
| 56 | + .required(true))) |
| 57 | + .subcommand(SubCommand::with_name("push") |
| 58 | + .about("pushes things") |
| 59 | + .setting(AppSettings::SubcommandRequiredElseHelp) |
| 60 | + .subcommand(SubCommand::with_name("remote") // Subcommands can have thier own subcommands, |
| 61 | + // which in turn have their own subcommands |
| 62 | + .about("pushes remote things") |
| 63 | + .arg(Arg::with_name("repo") |
| 64 | + .required(true) |
| 65 | + .help("The remote repo to push things to"))) |
| 66 | + .subcommand(SubCommand::with_name("local") |
| 67 | + .about("pushes local things"))) |
| 68 | + .subcommand(SubCommand::with_name("add") |
| 69 | + .about("adds things") |
| 70 | + .author("Someone Else") // Subcommands can list different authors |
| 71 | + .version("v2.0 (I'm versioned differently") // or different version from their parents |
| 72 | + .setting(AppSettings::ArgRequiredElseHelp) // They can even have different settings |
| 73 | + .arg(Arg::with_name("stuff") |
| 74 | + .long("stuff") |
| 75 | + .help("Stuff to add") |
| 76 | + .takes_value(true) |
| 77 | + .multiple(true))) |
| 78 | + .get_matches(); |
| 79 | + |
| 80 | + // At this point, the matches we have point to git. Keep this in mind... |
| 81 | + |
| 82 | + // You can check if one of git's subcommands was used |
| 83 | + if matches.is_present("clone") { |
| 84 | + println!("'git clone' was run."); |
| 85 | + } |
| 86 | + |
| 87 | + // You can see which subcommand was used |
| 88 | + if let Some(subcommand) = matches.subcommand_name() { |
| 89 | + println!("'git {}' was used", subcommand); |
| 90 | + |
| 91 | + // It's important to note, this *only* check's git's DIRECT children, **NOT** it's |
| 92 | + // grandchildren, great grandchildren, etc. |
| 93 | + // |
| 94 | + // i.e. if the command `git push remove --stuff foo` was run, the above will only print out, |
| 95 | + // `git push` was used. We'd need to get push's matches to see futher into the tree |
| 96 | + } |
| 97 | + |
| 98 | + // An alternative to checking the name is matching on known names. Again notice that only the |
| 99 | + // direct children are matched here. |
| 100 | + match matches.subcommand_name() { |
| 101 | + Some("clone") => println!("'git clone' was used"), |
| 102 | + Some("push") => println!("'git push' was used"), |
| 103 | + Some("add") => println!("'git add' was used"), |
| 104 | + None => println!("No subcommand was used"), |
| 105 | + _ => unreachable!(), // Assuming you've listed all direct children above, this is unreachable |
| 106 | + } |
| 107 | + |
| 108 | + // You could get the independent subcommand matches, although this is less common |
| 109 | + if let Some(clone_matches) = matches.subcommand_matches("clone") { |
| 110 | + // Now we have a reference to clone's matches |
| 111 | + println!("Cloning repo: {}", clone_matches.value_of("repo").unwrap()); |
| 112 | + } |
| 113 | + |
| 114 | + // The most common way to handle subcommands is via a combined approach using |
| 115 | + // `ArgMatches::subcommand` which returns a tuple of both the name and matches |
| 116 | + match matches.subcommand() { |
| 117 | + ("clone", Some(clone_matches)) =>{ |
| 118 | + // Now we have a reference to clone's matches |
| 119 | + println!("Cloning {}", clone_matches.value_of("repo").unwrap()); |
| 120 | + }, |
| 121 | + ("push", Some(push_matches)) =>{ |
| 122 | + // Now we have a reference to push's matches |
| 123 | + match push_matches.subcommand() { |
| 124 | + ("remote", Some(remote_matches)) =>{ |
| 125 | + // Now we have a reference to remote's matches |
| 126 | + println!("Pushing to {}", remote_matches.value_of("repo").unwrap()); |
| 127 | + }, |
| 128 | + ("local", Some(local_matches)) =>{ |
| 129 | + // Now we have a reference to push's matches |
| 130 | + println!("'git push local' was used"); |
| 131 | + }, |
| 132 | + _ => unreachable!(), |
| 133 | + } |
| 134 | + }, |
| 135 | + ("add", Some(add_matches)) =>{ |
| 136 | + // Now we have a reference to add's matches |
| 137 | + println!("Adding {}", add_matches.values_of("stuff").unwrap().collect::<Vec<_>>().join(", ")); |
| 138 | + }, |
| 139 | + ("", None) => println!("No subcommand was used"), // If no subcommand was usd it'll match the tuple ("", None) |
| 140 | + _ => unreachable!(), // If all subcommands are defined above, anything else is unreachabe!() |
| 141 | + } |
| 142 | + |
| 143 | + // Continued program logic goes here... |
| 144 | +} |
0 commit comments