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

Early support for reading program arguments from env-variable (issue #712) #734

Closed
wants to merge 3 commits into from
Closed
Changes from 2 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
55 changes: 55 additions & 0 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod help;
use std::borrow::Borrow;
use std::env;
use std::ffi::OsString;
use osstringext::OsStrExt2;
use std::fmt;
use std::io::{self, BufRead, BufWriter, Write};
use std::path::Path;
Expand Down Expand Up @@ -1227,6 +1228,42 @@ impl<'a, 'b> App<'a, 'b> {
self.get_matches_from_safe(&mut env::args_os())
}

/// Similar to [`App::get_matches`] but also reads args from
/// env-var `env_var_name`.
///
/// If the same argument is specified both as program argument
Copy link
Member

Choose a reason for hiding this comment

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

Let's change this to say, "The env var args are appended to the manually entered arguments (if any) and thus if an argument is provided both in the env var and manually, this could result in either a conflict, override, or additional value depending on the specific rules for said arg."

/// and as environment var, the program argument takes precedence.
///
/// Do note that using [`ArgMatches::values_of`] returns values from
/// both program arguments as well as env-var, if set, with program
/// arguments first.
///
/// Defaults to reading from "VAR" if `env_var_name` is `None`.
///
/// # Examples
///
/// ```ignore
/// $ VAR="--arg1 val1 --arg2 val2" ./program --arg2 val3
/// ```
pub fn get_matches_with_env<S: AsRef<str>>(self, env_var: &S)
-> ArgMatches<'a> {
// Build `args` by chaining `args_os()` and `var_os()`
let mut args: Vec<OsString> = env::args_os().collect();
// Handle first env-format:
// VAR="--option1=val1 --option2=val2" ./prog
if let Some(vargs) = env::var_os(env_var.as_ref()) {
// String has split(char::is_whitespace) but for OsStr
// we have to define closure manually.
//let whitespace = |c| c == b' ' || c == b'\t' || c == b'\n';
//args.extend(vargs.split(whitespace)
// Turns out OsStr.split does not take closure like regular
// string slices. So sticking with just space for now.
args.extend(vargs.split(b' ')
.map(|s| s.to_os_string()));
}
self.get_matches_from(&mut args.into_iter())
}

/// Starts the parsing process. Like [`App::get_matches`] this method does not return a [`clap::Result`]
/// and will automatically exit with an error message. This method, however, lets you specify
/// what iterator to use when performing matches, such as a [`Vec`] of your making.
Expand Down Expand Up @@ -1597,3 +1634,21 @@ impl<'n, 'e> fmt::Display for App<'n, 'e> {
write!(f, "{}", self.p.meta.name)
}
}

#[cfg(test)]
mod tests {

use std::env;

use ::{Arg, App};

#[test]
fn test_get_matches_with_env() {
env::set_var("VAR", "--arg1 val1");
let matches = App::new("testprog").arg(Arg::with_name("arg1")
.takes_value(true)
.long("arg1"))
.get_matches_with_env(&"VAR");
assert_eq!(matches.value_of("arg1").unwrap(), "val1");
}
}