diff --git a/src/app.rs b/src/app.rs index 77806b7d..aac3771e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,12 +4,14 @@ use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::env; +use std::vec::IntoIter; use argmatches::ArgMatches; use Arg; use args::OptArg; use args::FlagArg; use args::PosArg; +use subcommand::SubCommand; /// Used to create a representation of the program and all possible command line arguments /// for parsing at runtime. @@ -46,16 +48,19 @@ pub struct App { flags: HashMap<&'static str, FlagArg>, opts: HashMap<&'static str, OptArg>, positionals_idx: BTreeMap, + subcommands: HashMap<&'static str, Box>, // positionals_name: HashMap<&'static str, PosArg>, needs_long_help: bool, needs_long_version: bool, needs_short_help: bool, needs_short_version: bool, + needs_subcmd_help: bool, required: HashSet<&'static str>, arg_list: HashSet<&'static str>, short_list: HashSet, long_list: HashSet<&'static str>, blacklist: HashSet<&'static str>, + } impl App { @@ -78,10 +83,12 @@ impl App { flags: HashMap::new(), opts: HashMap::new(), positionals_idx: BTreeMap::new(), + subcommands: HashMap::new(), // positionals_name: HashMap::new(), needs_long_version: true, needs_long_help: true, needs_short_help: true, + needs_subcmd_help: true, needs_short_version: true, required: HashSet::new(), arg_list: HashSet::new(), @@ -259,6 +266,20 @@ impl App { 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 { + for subcmd in subcmds.into_iter() { + self = self.subcommand(subcmd); + } + self + } + + fn exit(&self) { unsafe { libc::exit(0); } } @@ -274,6 +295,7 @@ impl App { let mut flags = false; let mut pos = false; let mut opts = false; + let mut subcmds = false; if let Some(author) = self.author { println!("{}", author); @@ -283,11 +305,12 @@ impl App { } println!(""); 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.opts.is_empty() {opts = true; "[OPTIONS]"} else {""}, if ! self.positionals_idx.is_empty() {pos = true; "[POSITIONAL]"} else {""}); - if flags || opts || pos { + if flags || opts || pos || subcmds { println!(""); } if flags { @@ -319,6 +342,14 @@ impl App { 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(); } @@ -585,17 +616,19 @@ impl App { 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 { - let mut matches = ArgMatches::new(&self); - + fn get_matches_from(&mut self, it: &mut IntoIter, matches: &mut ArgMatches) -> Option<&'static str> { self.create_help_and_version(); // let mut needs_val = false; + let mut subcmd_name: Option<&'static str> = None; let mut needs_val_of: Option<&'static str> = None; let mut pos_counter = 1; - for arg in env::args().collect::>().tail() { + while let Some(arg) = it.next() { let arg_slice = arg.as_slice(); let mut skip = false; if let Some(nvo) = needs_val_of { @@ -648,12 +681,16 @@ impl App { } if arg_slice.starts_with("--") { // 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("-") { - needs_val_of = self.parse_short_arg(&mut matches, &arg); + needs_val_of = self.parse_short_arg(matches, &arg); } 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() { self.report_error( @@ -716,6 +753,32 @@ impl App { 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::>(); + + 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 } } \ No newline at end of file diff --git a/src/argmatches.rs b/src/argmatches.rs index 6e26cf85..a81c3a7c 100644 --- a/src/argmatches.rs +++ b/src/argmatches.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; // use std::collections::HashSet; -use app::App; +// use app::App; use args::{ FlagArg, OptArg, PosArg }; +use subcommand::SubCommand; /// Used to get information about the arguments that /// where supplied to the program at runtime. @@ -47,7 +48,7 @@ use args::{ FlagArg, OptArg, PosArg }; /// } /// } pub struct ArgMatches { - pub name: &'static str, + pub matches_of: &'static str, // pub author: Option<&'static str>, // pub about: Option<&'static str>, // pub version: Option<&'static str>, @@ -56,6 +57,7 @@ pub struct ArgMatches { pub flags: HashMap<&'static str, FlagArg>, pub opts: HashMap<&'static str, OptArg>, pub positionals: HashMap<&'static str, PosArg>, + pub subcommand: HashMap<&'static str, SubCommand> } impl ArgMatches { @@ -68,12 +70,13 @@ impl ArgMatches { /// # use clap::{App, Arg}; /// let matches = App::new("myprog").get_matches(); /// ``` - pub fn new(app: &App) -> ArgMatches { + pub fn new(name: &'static str) -> ArgMatches { ArgMatches { - name: app.name, + matches_of: name, flags: HashMap::new(), opts: HashMap::new(), positionals: HashMap::new(), + subcommand: HashMap::new() // required: vec![], // blacklist: HashSet::new(), // about: app.about, @@ -123,9 +126,12 @@ impl ArgMatches { /// } /// ``` pub fn is_present(&self, name: &'static str) -> bool { - if let Some(_) = self.flags.get(name) { - return true; - } + if self.subcommand.contains_key(name) || + self.flags.contains_key(name) || + self.opts.contains_key(name) || + self.positionals.contains_key(name) { + return true; + } false } @@ -155,4 +161,16 @@ impl ArgMatches { } 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::>()[0]); + } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index be2db3c1..0e0940b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![crate_type= "lib"] -#![feature(collections, core, libc)] +#![feature(collections, core, libc, exit_status)] //! A simply library for parsing command line arguments when writing //! command line and console applications. @@ -88,6 +88,7 @@ mod app; mod argmatches; mod arg; mod args; +mod subcommand; #[cfg(test)] mod tests {