Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 459 - Use enums in place of strings #465

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
Expand Down
92 changes: 85 additions & 7 deletions src/args/arg_matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -281,7 +283,7 @@ impl<'a> ArgMatches<'a> {
/// ```
pub fn is_present<S: AsRef<str>>(&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;
}
}
Expand Down Expand Up @@ -360,7 +362,7 @@ impl<'a> ArgMatches<'a> {
/// ```
pub fn subcommand_matches<S: AsRef<str>>(&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
}
Expand Down Expand Up @@ -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")
Expand All @@ -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<S> 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")
Expand All @@ -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!
Expand All @@ -461,15 +537,16 @@ 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"]);
/// },
/// _ => {},
/// }
/// ```
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`)
Expand Down Expand Up @@ -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() }
}

1 change: 1 addition & 0 deletions src/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 16 additions & 2 deletions src/args/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<str>>(name: S) -> App<'a, 'b> {
App::new(name.as_ref())
}

/// Creates a new instance of a subcommand from a YAML (.yml) document
Expand All @@ -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 {
""
}
}
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
Loading