mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 23:04:23 +00:00
feat(parser): accept boolean literal with env vars, take 1
This commit is contained in:
parent
6527fdeec4
commit
f2f9b665ed
4 changed files with 134 additions and 31 deletions
|
@ -1772,40 +1772,91 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
matcher: &mut ArgMatcher,
|
matcher: &mut ArgMatcher,
|
||||||
trailing_values: bool,
|
trailing_values: bool,
|
||||||
) -> ClapResult<()> {
|
) -> ClapResult<()> {
|
||||||
|
use crate::util::{str_to_bool, FALSE_LITERALS, TRUE_LITERALS};
|
||||||
|
|
||||||
if self.app.is_set(AS::DisableEnv) {
|
if self.app.is_set(AS::DisableEnv) {
|
||||||
|
debug!("Parser::add_env: Env vars disabled, quitting");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
for a in self.app.args.args() {
|
self.app.args.args().try_for_each(|a| {
|
||||||
// Use env only if the arg was not present among command line args
|
// Use env only if the arg was absent among command line args,
|
||||||
if matcher.get(&a.id).map_or(true, |a| a.occurs == 0) {
|
// early return if this is not the case.
|
||||||
if let Some((_, Some(ref val))) = a.env {
|
if matcher.get(&a.id).map_or(false, |a| a.occurs != 0) {
|
||||||
let val = ArgStr::new(val);
|
debug!("Parser::add_env: Skipping existing arg `{}`", a);
|
||||||
if a.is_set(ArgSettings::TakesValue) {
|
return Ok(());
|
||||||
self.add_val_to_arg(
|
}
|
||||||
a,
|
|
||||||
val,
|
debug!("Parser::add_env: Checking arg `{}`", a);
|
||||||
matcher,
|
if let Some((_, Some(ref val))) = a.env {
|
||||||
ValueType::EnvVariable,
|
let val = ArgStr::new(val);
|
||||||
false,
|
|
||||||
trailing_values,
|
if a.is_set(ArgSettings::TakesValue) {
|
||||||
);
|
debug!(
|
||||||
} else {
|
"Parser::add_env: Found an opt with value={:?}, trailing={:?}",
|
||||||
match self.check_for_help_and_version_str(&val) {
|
val, trailing_values
|
||||||
Some(ParseResult::HelpFlag) => {
|
);
|
||||||
return Err(self.help_err(true));
|
self.add_val_to_arg(
|
||||||
}
|
a,
|
||||||
Some(ParseResult::VersionFlag) => {
|
val,
|
||||||
return Err(self.version_err(true));
|
matcher,
|
||||||
}
|
ValueType::EnvVariable,
|
||||||
_ => (),
|
false,
|
||||||
|
trailing_values,
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Parser::add_env: Checking for help and version");
|
||||||
|
// Early return on `HelpFlag` or `VersionFlag`.
|
||||||
|
match self.check_for_help_and_version_str(&val) {
|
||||||
|
Some(ParseResult::HelpFlag) => {
|
||||||
|
return Err(self.help_err(true));
|
||||||
|
}
|
||||||
|
Some(ParseResult::VersionFlag) => {
|
||||||
|
return Err(self.version_err(true));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Parser::add_env: Found a flag with value `{:?}`", val);
|
||||||
|
match str_to_bool(val.to_string_lossy()) {
|
||||||
|
Ok(predicate) => {
|
||||||
|
debug!("Parser::add_env: Found boolean literal `{}`", predicate);
|
||||||
|
if predicate {
|
||||||
|
matcher.add_index_to(&a.id, self.cur_idx.get(), ValueType::EnvVariable);
|
||||||
}
|
}
|
||||||
matcher.add_index_to(&a.id, self.cur_idx.get(), ValueType::EnvVariable);
|
}
|
||||||
|
Err(rest) => {
|
||||||
|
debug!("Parser::parse_long_arg: Got invalid literal `{}`", rest);
|
||||||
|
let used: Vec<Id> = matcher
|
||||||
|
.arg_names()
|
||||||
|
.filter(|&n| {
|
||||||
|
self.app.find(n).map_or(true, |a| {
|
||||||
|
!(a.is_set(ArgSettings::Hidden)
|
||||||
|
|| self.required.contains(&a.id))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
let good_vals: Vec<&str> = TRUE_LITERALS
|
||||||
|
.iter()
|
||||||
|
.chain(FALSE_LITERALS.iter())
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
return Err(ClapError::invalid_value(
|
||||||
|
rest.into(),
|
||||||
|
&good_vals,
|
||||||
|
a,
|
||||||
|
Usage::new(self).create_usage_no_title(&used),
|
||||||
|
self.app.color(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increase occurrence of specific argument and the grouped arg it's in.
|
/// Increase occurrence of specific argument and the grouped arg it's in.
|
||||||
|
|
|
@ -4,10 +4,16 @@ mod argstr;
|
||||||
mod fnv;
|
mod fnv;
|
||||||
mod graph;
|
mod graph;
|
||||||
mod id;
|
mod id;
|
||||||
|
mod str_to_bool;
|
||||||
|
|
||||||
pub use self::fnv::Key;
|
pub use self::fnv::Key;
|
||||||
|
|
||||||
pub(crate) use self::{argstr::ArgStr, graph::ChildGraph, id::Id};
|
pub(crate) use self::{
|
||||||
|
argstr::ArgStr,
|
||||||
|
graph::ChildGraph,
|
||||||
|
id::Id,
|
||||||
|
str_to_bool::{str_to_bool, FALSE_LITERALS, TRUE_LITERALS},
|
||||||
|
};
|
||||||
pub(crate) use vec_map::VecMap;
|
pub(crate) use vec_map::VecMap;
|
||||||
|
|
||||||
#[cfg(feature = "color")]
|
#[cfg(feature = "color")]
|
||||||
|
|
24
src/util/str_to_bool.rs
Normal file
24
src/util/str_to_bool.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/// True values are `y`, `yes`, `t`, `true`, `on`, and `1`.
|
||||||
|
pub(crate) const TRUE_LITERALS: [&str; 6] = ["y", "yes", "t", "true", "on", "1"];
|
||||||
|
|
||||||
|
/// False values are `n`, `no`, `f`, `false`, `off`, and `0`.
|
||||||
|
pub(crate) const FALSE_LITERALS: [&str; 6] = ["n", "no", "f", "false", "off", "0"];
|
||||||
|
|
||||||
|
/// Converts a string literal representation of truth to true or false.
|
||||||
|
///
|
||||||
|
/// Translated from the Python function [`strtobool`].
|
||||||
|
///
|
||||||
|
/// [`strtobool`]: https://docs.python.org/3/distutils/apiref.html#distutils.util.strtobool
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns Err(val) if `val` is anything else.
|
||||||
|
pub(crate) fn str_to_bool<S: AsRef<str>>(val: S) -> Result<bool, S> {
|
||||||
|
let pat: &str = &val.as_ref().to_lowercase();
|
||||||
|
if TRUE_LITERALS.contains(&pat) {
|
||||||
|
Ok(true)
|
||||||
|
} else if FALSE_LITERALS.contains(&pat) {
|
||||||
|
Ok(false)
|
||||||
|
} else {
|
||||||
|
Err(val)
|
||||||
|
}
|
||||||
|
}
|
30
tests/env.rs
30
tests/env.rs
|
@ -1,7 +1,7 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
use clap::{App, AppSettings, Arg};
|
use clap::{App, AppSettings, Arg, Error as ClapError};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn env() {
|
fn env() {
|
||||||
|
@ -30,11 +30,33 @@ fn env_no_takes_value() {
|
||||||
.arg(Arg::from("[arg] 'some opt'").env("CLP_TEST_ENV"))
|
.arg(Arg::from("[arg] 'some opt'").env("CLP_TEST_ENV"))
|
||||||
.try_get_matches_from(vec![""]);
|
.try_get_matches_from(vec![""]);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
dbg!(r),
|
||||||
|
Err(ClapError {
|
||||||
|
kind: clap::ErrorKind::InvalidValue,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn env_bool_literal() {
|
||||||
|
env::set_var("CLP_TEST_FLAG_TRUE", "On");
|
||||||
|
env::set_var("CLP_TEST_FLAG_FALSE", "nO");
|
||||||
|
|
||||||
|
let r = App::new("df")
|
||||||
|
.arg(Arg::from("[present] 'some opt'").env("CLP_TEST_FLAG_TRUE"))
|
||||||
|
.arg(Arg::from("[negated] 'some another opt'").env("CLP_TEST_FLAG_FALSE"))
|
||||||
|
.arg(Arg::from("[absent] 'some third opt'").env("CLP_TEST_FLAG_ABSENT"))
|
||||||
|
.try_get_matches_from(vec![""]);
|
||||||
|
|
||||||
assert!(r.is_ok());
|
assert!(r.is_ok());
|
||||||
let m = r.unwrap();
|
let m = r.unwrap();
|
||||||
assert!(m.is_present("arg"));
|
assert!(m.is_present("present"));
|
||||||
assert_eq!(m.occurrences_of("arg"), 0);
|
assert_eq!(m.occurrences_of("present"), 0);
|
||||||
assert_eq!(m.value_of("arg"), None);
|
assert_eq!(m.value_of("present"), None);
|
||||||
|
assert!(!m.is_present("negated"));
|
||||||
|
assert!(!m.is_present("absent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue