Adding subcommands

This commit is contained in:
Kevin K 2015-03-05 22:44:11 -05:00
parent 379d2192b6
commit 5ca006cbb5
3 changed files with 99 additions and 17 deletions

View file

@ -4,12 +4,14 @@ use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::vec::IntoIter;
use argmatches::ArgMatches; use argmatches::ArgMatches;
use Arg; use Arg;
use args::OptArg; use args::OptArg;
use args::FlagArg; use args::FlagArg;
use args::PosArg; use args::PosArg;
use subcommand::SubCommand;
/// Used to create a representation of the program and all possible command line arguments /// Used to create a representation of the program and all possible command line arguments
/// for parsing at runtime. /// for parsing at runtime.
@ -46,16 +48,19 @@ pub struct App {
flags: HashMap<&'static str, FlagArg>, flags: HashMap<&'static str, FlagArg>,
opts: HashMap<&'static str, OptArg>, opts: HashMap<&'static str, OptArg>,
positionals_idx: BTreeMap<u8, PosArg>, positionals_idx: BTreeMap<u8, PosArg>,
subcommands: HashMap<&'static str, Box<App>>,
// positionals_name: HashMap<&'static str, PosArg>, // positionals_name: HashMap<&'static str, PosArg>,
needs_long_help: bool, needs_long_help: bool,
needs_long_version: bool, needs_long_version: bool,
needs_short_help: bool, needs_short_help: bool,
needs_short_version: bool, needs_short_version: bool,
needs_subcmd_help: bool,
required: HashSet<&'static str>, required: HashSet<&'static str>,
arg_list: HashSet<&'static str>, arg_list: HashSet<&'static str>,
short_list: HashSet<char>, short_list: HashSet<char>,
long_list: HashSet<&'static str>, long_list: HashSet<&'static str>,
blacklist: HashSet<&'static str>, blacklist: HashSet<&'static str>,
} }
impl App { impl App {
@ -78,10 +83,12 @@ impl App {
flags: HashMap::new(), flags: HashMap::new(),
opts: HashMap::new(), opts: HashMap::new(),
positionals_idx: BTreeMap::new(), positionals_idx: BTreeMap::new(),
subcommands: HashMap::new(),
// positionals_name: HashMap::new(), // positionals_name: HashMap::new(),
needs_long_version: true, needs_long_version: true,
needs_long_help: true, needs_long_help: true,
needs_short_help: true, needs_short_help: true,
needs_subcmd_help: true,
needs_short_version: true, needs_short_version: true,
required: HashSet::new(), required: HashSet::new(),
arg_list: HashSet::new(), arg_list: HashSet::new(),
@ -259,6 +266,20 @@ impl App {
self self
} }
pub fn subcommand(mut self, subcmd: App) -> App {
if subcmd.name == "help" { self.needs_subcmd_help = false; }
self.subcommands.insert(subcmd.name, Box::new(subcmd));
self
}
pub fn subcommands(mut self, subcmds: Vec<App>) -> App {
for subcmd in subcmds.into_iter() {
self = self.subcommand(subcmd);
}
self
}
fn exit(&self) { fn exit(&self) {
unsafe { libc::exit(0); } unsafe { libc::exit(0); }
} }
@ -274,6 +295,7 @@ impl App {
let mut flags = false; let mut flags = false;
let mut pos = false; let mut pos = false;
let mut opts = false; let mut opts = false;
let mut subcmds = false;
if let Some(author) = self.author { if let Some(author) = self.author {
println!("{}", author); println!("{}", author);
@ -283,11 +305,12 @@ impl App {
} }
println!(""); println!("");
println!("USAGE:"); println!("USAGE:");
print!("\t{} {} {} {}", self.name, print!("\t{} {} {} {} {}", self.name,
if ! self.subcommands.is_empty() {subcmds = true; "[SUBCOMMANDS]"} else {""},
if ! self.flags.is_empty() {flags = true; "[FLAGS]"} else {""}, if ! self.flags.is_empty() {flags = true; "[FLAGS]"} else {""},
if ! self.opts.is_empty() {opts = true; "[OPTIONS]"} else {""}, if ! self.opts.is_empty() {opts = true; "[OPTIONS]"} else {""},
if ! self.positionals_idx.is_empty() {pos = true; "[POSITIONAL]"} else {""}); if ! self.positionals_idx.is_empty() {pos = true; "[POSITIONAL]"} else {""});
if flags || opts || pos { if flags || opts || pos || subcmds {
println!(""); println!("");
} }
if flags { if flags {
@ -319,6 +342,14 @@ impl App {
if let Some(h) = v.help {h} else {" "} ); if let Some(h) = v.help {h} else {" "} );
} }
} }
if subcmds {
println!("");
println!("SUBCOMMANDS:");
for sc in self.subcommands.values() {
println!("\t{}\t\t{}", sc.name,
if let Some(a) = sc.about {a} else {" "} );
}
}
self.exit(); self.exit();
} }
@ -585,17 +616,19 @@ impl App {
occurrences: 1 occurrences: 1
}); });
} }
if self.needs_subcmd_help {
self.subcommands.insert("help", Box::new(App::new("help").about("Prints this message")));
}
} }
pub fn get_matches(mut self) -> ArgMatches { fn get_matches_from(&mut self, it: &mut IntoIter<String>, matches: &mut ArgMatches) -> Option<&'static str> {
let mut matches = ArgMatches::new(&self);
self.create_help_and_version(); self.create_help_and_version();
// let mut needs_val = false; // let mut needs_val = false;
let mut subcmd_name: Option<&'static str> = None;
let mut needs_val_of: Option<&'static str> = None; let mut needs_val_of: Option<&'static str> = None;
let mut pos_counter = 1; let mut pos_counter = 1;
for arg in env::args().collect::<Vec<_>>().tail() { while let Some(arg) = it.next() {
let arg_slice = arg.as_slice(); let arg_slice = arg.as_slice();
let mut skip = false; let mut skip = false;
if let Some(nvo) = needs_val_of { if let Some(nvo) = needs_val_of {
@ -648,12 +681,16 @@ impl App {
} }
if arg_slice.starts_with("--") { if arg_slice.starts_with("--") {
// Single flag, or option long version // Single flag, or option long version
needs_val_of = self.parse_long_arg(&mut matches, &arg); needs_val_of = self.parse_long_arg(matches, &arg);
} else if arg_slice.starts_with("-") { } else if arg_slice.starts_with("-") {
needs_val_of = self.parse_short_arg(&mut matches, &arg); needs_val_of = self.parse_short_arg(matches, &arg);
} else { } else {
// Positional // Positional or Subcommand
if let Some(sca) = self.subcommands.get(arg_slice) {
subcmd_name = Some(sca.name);
break;
}
if self.positionals_idx.is_empty() { // || self.positionals_name.is_empty() { if self.positionals_idx.is_empty() { // || self.positionals_name.is_empty() {
self.report_error( self.report_error(
@ -716,6 +753,32 @@ impl App {
self.validate_blacklist(&matches); self.validate_blacklist(&matches);
subcmd_name
}
pub fn get_matches(mut self) -> ArgMatches {
let mut matches = ArgMatches::new(self.name);
let args = env::args().collect::<Vec<_>>();
let mut it = args.into_iter();
let mut subcmd = self.get_matches_from(&mut it, &mut matches);
while let Some(sc) = subcmd {
if let Some(sca) = self.subcommands.get_mut(sc) {
let mut new_matches = SubCommand {
name: sc,
matches: ArgMatches::new(sc)
};
subcmd = sca.get_matches_from(&mut it, &mut new_matches.matches);
matches.subcommand.insert(sc, new_matches);
// prev_matches = prev_matches.unwrap().subcommand.get_mut(sc).unwrap().matches;
} else {
panic!("Found subcommand \"{}\" but wasn't able to find a valid representation of it to match against", sc);
}
matches = &mut matches.subcommand.get_mut(sc).unwrap().matches;
}
matches matches
} }
} }

View file

@ -1,8 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
// use std::collections::HashSet; // use std::collections::HashSet;
use app::App; // use app::App;
use args::{ FlagArg, OptArg, PosArg }; use args::{ FlagArg, OptArg, PosArg };
use subcommand::SubCommand;
/// Used to get information about the arguments that /// Used to get information about the arguments that
/// where supplied to the program at runtime. /// where supplied to the program at runtime.
@ -47,7 +48,7 @@ use args::{ FlagArg, OptArg, PosArg };
/// } /// }
/// } /// }
pub struct ArgMatches { pub struct ArgMatches {
pub name: &'static str, pub matches_of: &'static str,
// pub author: Option<&'static str>, // pub author: Option<&'static str>,
// pub about: Option<&'static str>, // pub about: Option<&'static str>,
// pub version: Option<&'static str>, // pub version: Option<&'static str>,
@ -56,6 +57,7 @@ pub struct ArgMatches {
pub flags: HashMap<&'static str, FlagArg>, pub flags: HashMap<&'static str, FlagArg>,
pub opts: HashMap<&'static str, OptArg>, pub opts: HashMap<&'static str, OptArg>,
pub positionals: HashMap<&'static str, PosArg>, pub positionals: HashMap<&'static str, PosArg>,
pub subcommand: HashMap<&'static str, SubCommand>
} }
impl ArgMatches { impl ArgMatches {
@ -68,12 +70,13 @@ impl ArgMatches {
/// # use clap::{App, Arg}; /// # use clap::{App, Arg};
/// let matches = App::new("myprog").get_matches(); /// let matches = App::new("myprog").get_matches();
/// ``` /// ```
pub fn new(app: &App) -> ArgMatches { pub fn new(name: &'static str) -> ArgMatches {
ArgMatches { ArgMatches {
name: app.name, matches_of: name,
flags: HashMap::new(), flags: HashMap::new(),
opts: HashMap::new(), opts: HashMap::new(),
positionals: HashMap::new(), positionals: HashMap::new(),
subcommand: HashMap::new()
// required: vec![], // required: vec![],
// blacklist: HashSet::new(), // blacklist: HashSet::new(),
// about: app.about, // about: app.about,
@ -123,7 +126,10 @@ impl ArgMatches {
/// } /// }
/// ``` /// ```
pub fn is_present(&self, name: &'static str) -> bool { pub fn is_present(&self, name: &'static str) -> bool {
if let Some(_) = self.flags.get(name) { if self.subcommand.contains_key(name) ||
self.flags.contains_key(name) ||
self.opts.contains_key(name) ||
self.positionals.contains_key(name) {
return true; return true;
} }
false false
@ -155,4 +161,16 @@ impl ArgMatches {
} }
0 0
} }
pub fn subcommand_matches(&self, name: &'static str) -> Option<&ArgMatches> {
if let Some(ref sc) = self.subcommand.get(name) {
return Some(&sc.matches);
}
None
}
pub fn subcommand_name(&self) -> Option<&'static str> {
if self.subcommand.is_empty() { return None; }
return Some(self.subcommand.keys().collect::<Vec<_>>()[0]);
}
} }

View file

@ -1,6 +1,6 @@
#![crate_type= "lib"] #![crate_type= "lib"]
#![feature(collections, core, libc)] #![feature(collections, core, libc, exit_status)]
//! A simply library for parsing command line arguments when writing //! A simply library for parsing command line arguments when writing
//! command line and console applications. //! command line and console applications.
@ -88,6 +88,7 @@ mod app;
mod argmatches; mod argmatches;
mod arg; mod arg;
mod args; mod args;
mod subcommand;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {