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

api(Arg): add from_env #1057

Closed
wants to merge 8 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
302 changes: 178 additions & 124 deletions src/app/help.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1796,6 +1796,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> {
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
None
}
fn env<'s>(&'s self) -> Option<(&'n OsStr, &'s OsString)> { None }
fn longest_filter(&self) -> bool { true }
fn aliases(&self) -> Option<Vec<&'e str>> {
if let Some(ref aliases) = self.p.meta.aliases {
Expand Down
32 changes: 32 additions & 0 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,38 @@ impl<'a, 'b> Parser<'a, 'b>
}
Ok(())
}

pub fn add_env(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> {
macro_rules! add_val {
($_self:ident, $a:ident, $m:ident) => {
if let Some(ref val) = $a.v.env {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up not trying to dedup this with default. I found it was going to be annoying. If it's required for commit, I can, but otherwise I'd prefer to leave as is.

if $m.get($a.b.name).map(|ma| ma.vals.len()).map(|len| len == 0).unwrap_or(false) {
$_self.add_val_to_arg($a, OsStr::new(&val.1), $m)?;

if $_self.cache.map_or(true, |name| name != $a.name()) {
arg_post_processing!($_self, $a, $m);
$_self.cache = Some($a.name());
}
} else {
$_self.add_val_to_arg($a, OsStr::new(&val.1), $m)?;

if $_self.cache.map_or(true, |name| name != $a.name()) {
arg_post_processing!($_self, $a, $m);
$_self.cache = Some($a.name());
}
}
}
};
}

for o in &self.opts {
add_val!(self, o, matcher);
}
for p in self.positionals.values() {
add_val!(self, p, matcher);
}
Ok(())
}

pub fn flags(&self) -> Iter<FlagBuilder<'a, 'b>> { self.flags.iter() }

Expand Down
1 change: 1 addition & 0 deletions src/app/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
-> ClapResult<()> {
debugln!("Validator::validate;");
let mut reqs_validated = false;
self.0.add_env(matcher)?;
self.0.add_defaults(matcher)?;
if let ParseResult::Opt(a) = needs_val_of {
debugln!("Validator::validate: needs_val_of={:?}", a);
Expand Down
1 change: 1 addition & 0 deletions src/args/any_arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display {
fn long_help(&self) -> Option<&'e str>;
fn default_val(&self) -> Option<&'e OsStr>;
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>>;
fn env<'s>(&'s self) -> Option<(&'n OsStr, &'s OsString)>;
fn longest_filter(&self) -> bool;
fn val_terminator(&self) -> Option<&'e str>;
}
Expand Down
118 changes: 116 additions & 2 deletions src/args/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::ffi::{OsString, OsStr};
use osstringext::OsStrExt3;
#[cfg(not(target_os="windows"))]
use std::os::unix::ffi::OsStrExt;

use std::env;

#[cfg(feature = "yaml")]
use yaml_rust::Yaml;
Expand Down Expand Up @@ -128,6 +128,7 @@ impl<'a, 'b> Arg<'a, 'b> {
"default_value" => yaml_to_str!(a, v, default_value),
"default_value_if" => yaml_tuple3!(a, v, default_value_if),
"default_value_ifs" => yaml_tuple3!(a, v, default_value_if),
"env" => yaml_to_str!(a, v, env),
"value_names" => yaml_vec_or_str!(v, a, value_name),
"groups" => yaml_vec_or_str!(v, a, group),
"requires" => yaml_vec_or_str!(v, a, requires),
Expand Down Expand Up @@ -3014,7 +3015,7 @@ impl<'a, 'b> Arg<'a, 'b> {
/// **NOTE:** If the user *does not* use this argument at runtime [`ArgMatches::is_present`] will
/// still return `true`. If you wish to determine whether the argument was used at runtime or
/// not, consider [`ArgMatches::occurrences_of`] which will return `0` if the argument was *not*
/// used at runtmie.
/// used at runtime.
///
/// **NOTE:** This setting is perfectly compatible with [`Arg::default_value_if`] but slightly
/// different. `Arg::default_value` *only* takes affect when the user has not provided this arg
Expand Down Expand Up @@ -3311,6 +3312,119 @@ impl<'a, 'b> Arg<'a, 'b> {
self
}

/// Specifies that if the value is not passed in as an argument, that it should be retrieved
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try and add similar docs to the others.

/// from the environment, if available. If it is not present in the environment, then default
/// rules will apply.
///
/// **NOTE:** If the user *does not* use this argument at runtime, [`ArgMatches::occurrences_of`]
/// will return `0` even though the [`ArgMatches::value_of`] will return the default specified.
///
/// **NOTE:** If the user *does not* use this argument at runtime [`ArgMatches::is_present`] will
/// return `true` if the variable is present in the environemnt . If you wish to determine whether
/// the argument was used at runtime or not, consider [`ArgMatches::occurrences_of`] which will
/// return `0` if the argument was *not* used at runtime.
///
/// **NOTE:** This implicitly sets [`Arg::takes_value(true)`].
///
/// **NOTE:** If [`Arg::multiple(true)`] is set then [`Arg::use_delimiter(true)`] should also be
/// set. Otherwise, only a single argument will be returned from the environment variable. The
/// default delimiter is `,` and follows all the other delimiter rules.
///
/// # Examples
///
/// In this example, we show the variable coming from the environment:
///
/// ```rust
/// # use std::env;
/// # use clap::{App, Arg};
///
/// env::set_var("MY_FLAG", "env");
///
/// let m = App::new("prog")
/// .arg(Arg::with_name("flag")
/// .long("flag")
/// .env("MY_FLAG"))
/// .get_matches_from(vec![
/// "prog"
/// ]);
///
/// assert_eq!(m.value_of("flag"), Some("env"));
/// ```
///
/// In this example, we show the variable coming from an option on the CLI:
///
/// ```rust
/// # use std::env;
/// # use clap::{App, Arg};
///
/// env::set_var("MY_FLAG", "env");
///
/// let m = App::new("prog")
/// .arg(Arg::with_name("flag")
/// .long("flag")
/// .env("MY_FLAG"))
/// .get_matches_from(vec![
/// "prog", "--flag", "opt"
/// ]);
///
/// assert_eq!(m.value_of("flag"), Some("opt"));
/// ```
///
/// In this example, we show the variable coming from the environment even with the
/// presence of a default:
///
/// ```rust
/// # use std::env;
/// # use clap::{App, Arg};
///
/// env::set_var("MY_FLAG", "env");
///
/// let m = App::new("prog")
/// .arg(Arg::with_name("flag")
/// .long("flag")
/// .env("MY_FLAG")
/// .default_value("default"))
/// .get_matches_from(vec![
/// "prog"
/// ]);
///
/// assert_eq!(m.value_of("flag"), Some("env"));
/// ```
///
/// In this example, we show the use of multiple values in a single environment variable:
///
/// ```rust
/// # use std::env;
/// # use clap::{App, Arg};
///
/// env::set_var("MY_FLAG_MULTI", "env1,env2");
///
/// let m = App::new("prog")
/// .arg(Arg::with_name("flag")
/// .long("flag")
/// .env("MY_FLAG_MULTI")
/// .multiple(true)
/// .use_delimiter(true))
/// .get_matches_from(vec![
/// "prog"
/// ]);
///
/// assert_eq!(m.values_of("flag").unwrap().collect::<Vec<_>>(), vec!["env1", "env2"]);
/// ```
pub fn env(self, name: &'a str) -> Self {
self.env_os(OsStr::new(name))
}

/// Specifies that if the value is not passed in as an argument, that it should be retrieved
/// from the environment if available in the exact same manner as [`Arg::env`] only using
/// [`OsStr`]s instead.
pub fn env_os(mut self, name: &'a OsStr) -> Self {
self.setb(ArgSettings::TakesValue);

self.v.env = env::var_os(name).map(|value| (name, value));
self
}

/// When set to `true` the help string will be displayed on the line after the argument and
/// indented once. This can be helpful for arguments with very long or complex help messages.
/// This can also be helpful for arguments with very long flag names, or many/long value names.
Expand Down
28 changes: 19 additions & 9 deletions src/args/arg_builder/flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,26 @@ use std::mem;

// Internal
use Arg;
use args::{ArgSettings, Base, Switched, AnyArg, DispOrder};
use args::{AnyArg, ArgSettings, Base, DispOrder, Switched};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me know if you don't want the default rustfmt changes to be merged in... this is current rustfmt, at least up to a few days ago.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm good with the rustfmt changes. I try to stay using the latest and greatest update so as to stick with community best practices.

use map::{self, VecMap};

#[derive(Default, Clone, Debug)]
#[doc(hidden)]
pub struct FlagBuilder<'n, 'e>
where 'n: 'e
where
'n: 'e,
{
pub b: Base<'n, 'e>,
pub s: Switched<'e>,
}

impl<'n, 'e> FlagBuilder<'n, 'e> {
pub fn new(name: &'n str) -> Self { FlagBuilder { b: Base::new(name), ..Default::default() } }
pub fn new(name: &'n str) -> Self {
FlagBuilder {
b: Base::new(name),
..Default::default()
}
}
}

impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> {
Expand Down Expand Up @@ -83,10 +89,12 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
None
}
fn env<'s>(&'s self) -> Option<(&'n OsStr, &'s OsString)> { None }
fn longest_filter(&self) -> bool { self.s.long.is_some() }
fn aliases(&self) -> Option<Vec<&'e str>> {
if let Some(ref aliases) = self.s.aliases {
let vis_aliases: Vec<_> = aliases.iter()
let vis_aliases: Vec<_> = aliases
.iter()
.filter_map(|&(n, v)| if v { Some(n) } else { None })
.collect();
if vis_aliases.is_empty() {
Expand All @@ -105,9 +113,7 @@ impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> {
}

impl<'n, 'e> PartialEq for FlagBuilder<'n, 'e> {
fn eq(&self, other: &FlagBuilder<'n, 'e>) -> bool {
self.b == other.b
}
fn eq(&self, other: &FlagBuilder<'n, 'e>) -> bool { self.b == other.b }
}

#[cfg(test)]
Expand Down Expand Up @@ -142,8 +148,12 @@ mod test {
fn flagbuilder_display_multiple_aliases() {
let mut f = FlagBuilder::new("flg");
f.s.short = Some('f');
f.s.aliases =
Some(vec![("alias_not_visible", false), ("f2", true), ("f3", true), ("f4", true)]);
f.s.aliases = Some(vec![
("alias_not_visible", false),
("f2", true),
("f3", true),
("f4", true),
]);
assert_eq!(&*format!("{}", f), "-f");
}
}
36 changes: 25 additions & 11 deletions src/args/arg_builder/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,28 @@ use std::ffi::{OsStr, OsString};
use std::mem;

// Internal
use args::{ArgSettings, AnyArg, Base, Switched, Valued, Arg, DispOrder};
use args::{AnyArg, Arg, ArgSettings, Base, DispOrder, Switched, Valued};
use map::{self, VecMap};

#[allow(missing_debug_implementations)]
#[doc(hidden)]
#[derive(Default, Clone)]
pub struct OptBuilder<'n, 'e>
where 'n: 'e
where
'n: 'e,
{
pub b: Base<'n, 'e>,
pub s: Switched<'e>,
pub v: Valued<'n, 'e>,
}

impl<'n, 'e> OptBuilder<'n, 'e> {
pub fn new(name: &'n str) -> Self { OptBuilder { b: Base::new(name), ..Default::default() } }
pub fn new(name: &'n str) -> Self {
OptBuilder {
b: Base::new(name),
..Default::default()
}
}
}

impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for OptBuilder<'n, 'e> {
Expand Down Expand Up @@ -85,14 +91,16 @@ impl<'n, 'e> Display for OptBuilder<'n, 'e> {
write!(f, "...")?;
}
} else {
write!(f,
write!(
f,
"<{}>{}",
self.b.name,
if self.is_set(ArgSettings::Multiple) {
"..."
} else {
""
})?;
}
)?;
}

Ok(())
Expand Down Expand Up @@ -132,10 +140,14 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
self.v.default_vals_ifs.as_ref().map(|vm| vm.values())
}
fn env<'s>(&'s self) -> Option<(&'n OsStr, &'s OsString)> {
self.v.env.as_ref().map(|&(key, ref value)| (key, value))
}
fn longest_filter(&self) -> bool { true }
fn aliases(&self) -> Option<Vec<&'e str>> {
if let Some(ref aliases) = self.s.aliases {
let vis_aliases: Vec<_> = aliases.iter()
let vis_aliases: Vec<_> = aliases
.iter()
.filter_map(|&(n, v)| if v { Some(n) } else { None })
.collect();
if vis_aliases.is_empty() {
Expand All @@ -154,9 +166,7 @@ impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> {
}

impl<'n, 'e> PartialEq for OptBuilder<'n, 'e> {
fn eq(&self, other: &OptBuilder<'n, 'e>) -> bool {
self.b == other.b
}
fn eq(&self, other: &OptBuilder<'n, 'e>) -> bool { self.b == other.b }
}

#[cfg(test)]
Expand Down Expand Up @@ -214,8 +224,12 @@ mod test {
fn optbuilder_display_multiple_aliases() {
let mut o = OptBuilder::new("opt");
o.s.long = Some("option");
o.s.aliases =
Some(vec![("als_not_visible", false), ("als2", true), ("als3", true), ("als4", true)]);
o.s.aliases = Some(vec![
("als_not_visible", false),
("als2", true),
("als3", true),
("als4", true),
]);
assert_eq!(&*format!("{}", o), "--option <opt>");
}
}
Loading