diff --git a/src/app/settings.rs b/src/app/settings.rs index 1c08c7df3071..09206631a0f9 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -322,6 +322,7 @@ pub enum AppSettings { /// // All trailing arguments will be stored under the subcommand's sub-matches using a value /// // of the runtime subcommand name (in this case "subcmd") /// match m.subcommand() { + /// ("do-stuff", Some(sub_m)) => { /* do-stuff was used, internal subcommand */ }, /// (external, Some(ext_m)) => { /// let ext_args: Vec<&str> = ext_m.values_of(external).unwrap().collect(); /// assert_eq!(ext_args, ["--option", "value", "-fff", "--flag"]); diff --git a/src/args/arg_matches.rs b/src/args/arg_matches.rs index 0e11f88779a4..f2be6df2e80b 100644 --- a/src/args/arg_matches.rs +++ b/src/args/arg_matches.rs @@ -3,11 +3,13 @@ use std::collections::HashMap; use std::iter::Map; use std::slice; use std::borrow::Cow; +use std::str::FromStr; use vec_map; use args::SubCommand; use args::MatchedArg; +use args::SubCommandKey; use INVALID_UTF8; /// Used to get information about the arguments that where supplied to the program at runtime by @@ -281,7 +283,7 @@ impl<'a> ArgMatches<'a> { /// ``` pub fn is_present>(&self, name: S) -> bool { if let Some(ref sc) = self.subcommand { - if sc.name == name.as_ref() { + if &sc.name[..] == name.as_ref() { return true; } } @@ -360,7 +362,7 @@ impl<'a> ArgMatches<'a> { /// ``` pub fn subcommand_matches>(&self, name: S) -> Option<&ArgMatches<'a>> { if let Some(ref s) = self.subcommand { - if s.name == name.as_ref() { return Some(&s.matches) } + if &s.name[..] == name.as_ref() { return Some(&s.matches) } } None } @@ -400,11 +402,21 @@ impl<'a> ArgMatches<'a> { /// $ git commit message /// ``` /// - /// Notice only one command per "level" may be used. You could not, for example, do `$ git + /// Notice only one command per "level" may be used. You could *not*, for example, do `$ git /// clone url push origin path` /// /// # Examples /// + /// Subcommands can use either strings or enum variants as names and accessors. Using strings + /// is convienient, but can lead to simple typing errors and other such issues. This is + /// sometimes referred to as being, "stringly typed" and generally avoided if possible. + /// + /// This is why subcommands also have the option to use enum variants as their accessors, which + /// allows `rustc` to do some compile time checking for, to avoid all the common "stringly + /// typed" issues. + /// + /// This first example shows a "stringly" typed version. + /// /// ```no_run /// # use clap::{App, Arg, SubCommand}; /// let app_m = App::new("git") @@ -420,15 +432,48 @@ impl<'a> ArgMatches<'a> { /// _ => {}, // Either no subcommand or one not tested for... /// } /// ``` - pub fn subcommand_name(&self) -> Option<&str> { - self.subcommand.as_ref().map(|sc| &sc.name[..]) + /// This next example shows a functionally equivolent strong typed version. + /// + /// ```no_run + /// # #[macro_use] + /// # extern crate clap; + /// # use clap::{App, Arg, SubCommand}; + /// subcommands!{ + /// enum Git { + /// clone, + /// push, + /// commit + /// } + /// } + /// + /// fn main() { + /// let app_m = App::new("git") + /// .subcommand(SubCommand::with_name(Git::clone)) + /// .subcommand(SubCommand::with_name(Git::push)) + /// .subcommand(SubCommand::with_name(Git::commit)) + /// .get_matches(); + /// + /// match app_m.subcommand_name() { + /// Some(Git::clone) => {}, // clone was used + /// Some(Git::push) => {}, // push was used + /// Some(Git::commit) => {}, // commit was used + /// _ => {}, // No subcommand was used + /// } + /// } + /// ``` + pub fn subcommand_name<'s, S>(&'s self) -> Option where S: SubCommandKey<'s> { + self.subcommand.as_ref().map(|sc| S::from_str(&sc.name[..])) } /// This brings together `ArgMatches::subcommand_matches` and `ArgMatches::subcommand_name` by /// returning a tuple with both pieces of information. /// + /// Like the other methods, can either be stringly typed, or use enum variants. + /// /// # Examples /// + /// An example using strings. + /// /// ```no_run /// # use clap::{App, Arg, SubCommand}; /// let app_m = App::new("git") @@ -445,6 +490,37 @@ impl<'a> ArgMatches<'a> { /// } /// ``` /// + /// This next example shows a functionally equivolent strong typed version. + /// + /// ```no_run + /// # #[macro_use] + /// # extern crate clap; + /// # use clap::{App, Arg, SubCommand}; + /// subcommands!{ + /// enum Git { + /// clone, + /// push, + /// commit + /// } + /// } + /// + /// fn main() { + /// let app_m = App::new("git") + /// .subcommand(SubCommand::with_name(Git::clone)) + /// .subcommand(SubCommand::with_name(Git::push)) + /// .subcommand(SubCommand::with_name(Git::commit)) + /// .get_matches(); + /// + /// match app_m.subcommand() { + /// (Git::clone, Some(sub_m)) => {}, // clone was used + /// (Git::push, Some(sub_m)) => {}, // push was used + /// (Git::commit, Some(sub_m)) => {}, // commit was used + /// (Git::None, _) => {}, // No subcommand was used + /// (_, None) => {}, // Unreachable + /// } + /// } + /// ``` + /// /// Another useful scenario is when you want to support third party, or external, subcommands. /// In these cases you can't know the subcommand name ahead of time, so use a variable instead /// with pattern matching! @@ -461,6 +537,7 @@ impl<'a> ArgMatches<'a> { /// // All trailing arguments will be stored under the subcommand's sub-matches using a value /// // of the runtime subcommand name (in this case "subcmd") /// match app_m.subcommand() { + /// ("do-stuff", Some(sub_m)) => { /* do-stuff was used, internal subcommand */ }, /// (external, Some(sub_m)) => { /// let ext_args: Vec<&str> = sub_m.values_of(external).unwrap().collect(); /// assert_eq!(ext_args, ["--option", "value", "-fff", "--flag"]); @@ -468,8 +545,8 @@ impl<'a> ArgMatches<'a> { /// _ => {}, /// } /// ``` - pub fn subcommand(&self) -> (&str, Option<&ArgMatches<'a>>) { - self.subcommand.as_ref().map_or(("", None), |sc| (&sc.name[..], Some(&sc.matches))) + pub fn subcommand<'s, S>(&'s self) -> (S, Option<&ArgMatches<'a>>) where S: SubCommandKey<'s> { + self.subcommand.as_ref().map_or((S::none(), None), |sc| (S::from_str(&sc.name[..]), Some(&sc.matches))) } /// Returns a string slice of the usage statement for the `App` (or `SubCommand`) @@ -575,3 +652,4 @@ impl<'a> Iterator for OsValues<'a> { impl<'a> DoubleEndedIterator for OsValues<'a> { fn next_back(&mut self) -> Option<&'a OsStr> { self.iter.next_back() } } + diff --git a/src/args/mod.rs b/src/args/mod.rs index 365eaf915992..0a4ba3e34df7 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -8,6 +8,7 @@ pub use self::group::ArgGroup; pub use self::any_arg::AnyArg; pub use self::settings::ArgSettings; pub use self::help_writer::HelpWriter; +pub use self::subcommand::SubCommandKey; mod arg; pub mod any_arg; diff --git a/src/args/subcommand.rs b/src/args/subcommand.rs index 77abe981da0f..96362d176c2b 100644 --- a/src/args/subcommand.rs +++ b/src/args/subcommand.rs @@ -44,8 +44,8 @@ impl<'a> SubCommand<'a> { /// SubCommand::with_name("config")) /// # ; /// ``` - pub fn with_name<'b>(name: &str) -> App<'a, 'b> { - App::new(name) + pub fn with_name<'b, S: AsRef>(name: S) -> App<'a, 'b> { + App::new(name.as_ref()) } /// Creates a new instance of a subcommand from a YAML (.yml) document @@ -62,3 +62,17 @@ impl<'a> SubCommand<'a> { App::from_yaml(yaml) } } + +pub trait SubCommandKey<'a> { + fn from_str(s: &'a str) -> Self; + fn none() -> Self; +} + +impl<'a> SubCommandKey<'a> for &'a str { + fn from_str(s: &'a str) -> Self { + s + } + fn none() -> Self { + "" + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 73907c1f895d..8839ea5b725a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,7 +385,7 @@ #![cfg_attr(feature = "lints", deny(warnings))] #![cfg_attr(not(any(feature = "lints", feature = "nightly")), deny(unstable_features))] #![deny( - missing_docs, + // missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, @@ -420,6 +420,7 @@ pub use args::{Arg, ArgGroup, ArgMatches, SubCommand, ArgSettings}; pub use app::{App, AppSettings}; pub use fmt::Format; pub use errors::{Error, ErrorKind, Result}; +pub use args::SubCommandKey; #[macro_use] mod macros; diff --git a/tests/subcommands.rs b/tests/subcommands.rs index 2423a9d0f022..07f6e729a8cb 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -14,7 +14,7 @@ fn subcommand() { .arg(Arg::with_name("other").long("other")) .get_matches_from(vec!["myprog", "some", "--test", "testing"]); - assert_eq!(m.subcommand_name().unwrap(), "some"); + assert_eq!(m.subcommand_name::<&str>().unwrap(), "some"); let sub_m = m.subcommand_matches("some").unwrap(); assert!(sub_m.is_present("test")); assert_eq!(sub_m.value_of("test").unwrap(), "testing"); @@ -32,7 +32,7 @@ fn subcommand_none_given() { .arg(Arg::with_name("other").long("other")) .get_matches_from(vec![""]); - assert!(m.subcommand_name().is_none()); + assert!(m.subcommand_name::<&str>().is_none()); } #[test] @@ -53,7 +53,7 @@ fn subcommand_multiple() { assert!(m.subcommand_matches("some").is_some()); assert!(m.subcommand_matches("add").is_none()); - assert_eq!(m.subcommand_name().unwrap(), "some"); + assert_eq!(m.subcommand_name::<&str>().unwrap(), "some"); let sub_m = m.subcommand_matches("some").unwrap(); assert!(sub_m.is_present("test")); assert_eq!(sub_m.value_of("test").unwrap(), "testing");