Add env feature gate

This commit is contained in:
Pavan Kumar Sunkara 2021-08-14 01:04:49 +01:00
parent 09257a8961
commit c7985fb73e
15 changed files with 112 additions and 67 deletions

View file

@ -81,16 +81,29 @@ version-sync = "0.9"
criterion = "0.3.2"
[features]
default = ["std", "suggestions", "color", "unicode_help", "derive", "cargo"]
std = ["indexmap/std"] # support for no_std in a backwards-compatible way
default = [
"std",
"derive",
"cargo",
"color",
"env",
"suggestions",
"unicode_help",
]
debug = ["clap_derive/debug"] # Enables debug messages
# Used in default
std = ["indexmap/std"] # support for no_std in a backwards-compatible way
derive = ["clap_derive", "lazy_static"]
cargo = ["lazy_static"] # Disable if you're not using Cargo, enables Cargo-env-var-dependent macros
color = ["atty", "termcolor"]
env = [] # Use environment variables during arg parsing
suggestions = ["strsim"]
color = ["atty", "termcolor"]
wrap_help = ["terminal_size", "textwrap/terminal_size"]
unicode_help= ["textwrap/unicode-width"] # Enable if any unicode in help message
derive = ["clap_derive", "lazy_static"]
yaml = ["yaml-rust"]
cargo = ["lazy_static"] # Disable if you're not using Cargo, enables Cargo-env-var-dependent macros
debug = ["clap_derive/debug"] # Enables debug messages
unicode_help = ["textwrap/unicode-width"] # Enable if any unicode in help message
# Optional
wrap_help = ["terminal_size", "textwrap/terminal_size"]
yaml = ["yaml-rust"]
[profile.test]
opt-level = 1

View file

@ -464,12 +464,16 @@ Then run `cargo build` or `cargo update && cargo build` for your project.
### Optional Dependencies / Features
Disabling optional features can decrease the binary size of `clap` and decrease the compile time. If binary size or compile times are extremely important to you, it is a good idea to disable the feautres that you are not using.
#### Features enabled by default
* **derive**: Enables the custom derive (i.e. `#[derive(Clap)]`). Without this you must use one of the other methods of creating a `clap` CLI listed above.
* **std**: _Not Currently Used._ Placeholder for supporting `no_std` environments in a backwards compatible manner.
* **derive**: Enables the custom derive (i.e. `#[derive(Clap)]`). Without this you must use one of the other methods of creating a `clap` CLI listed above. (builds dependency `clap_derive`)
* **cargo**: Turns on macros that read values from `CARGO_*` environment variables.
* **suggestions**: Turns on the `Did you mean '--myoption'?` feature for when users make typos. (builds dependency `strsim`)
* **color**: Turns on colored error messages. You still have to turn on colored help by setting `AppSettings::ColoredHelp`. (builds dependency `termcolor`)
* **env**: Turns on the usage of environment variables during parsing.
* **suggestions**: Turns on the `Did you mean '--myoption'?` feature for when users make typos. (builds dependency `strsim`)
* **unicode_help**: Turns on support for unicode characters in help messages. (builds dependency `textwrap`)
To disable these, add this to your `Cargo.toml`:
@ -494,9 +498,9 @@ features = ["std", "suggestions", "color"]
#### Opt-in features
* **"regex"**: Enables regex validators. (builds dependency `regex`)
* **"wrap_help"**: Turns on the help text wrapping feature, based on the terminal size. (builds dependency `term-size`)
* **"yaml"**: Enables building CLIs from YAML documents. (builds dependency `yaml-rust`)
* **"regex"**: Enables regex validators. (builds dependency `regex`)
### More Information

View file

@ -49,9 +49,8 @@ bitflags! {
const SUBCOMMAND_PRECEDENCE_OVER_ARG = 1 << 41;
const DISABLE_HELP_FLAG = 1 << 42;
const USE_LONG_FORMAT_FOR_HELP_SC = 1 << 43;
const DISABLE_ENV = 1 << 44;
const INFER_LONG_ARGS = 1 << 45;
const IGNORE_ERRORS = 1 << 46;
const INFER_LONG_ARGS = 1 << 44;
const IGNORE_ERRORS = 1 << 45;
}
}
@ -96,8 +95,6 @@ impl_settings! { AppSettings, AppFlags,
=> Flags::COLOR_AUTO,
ColorNever("colornever")
=> Flags::COLOR_NEVER,
DisableEnv("disableenv")
=> Flags::DISABLE_ENV,
DontDelimitTrailingValues("dontdelimittrailingvalues")
=> Flags::DONT_DELIM_TRAIL,
DontCollapseArgsInUsage("dontcollapseargsinusage")
@ -583,18 +580,6 @@ pub enum AppSettings {
/// ```
ColorNever,
/// Disables the use of environment variables in the app
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, AppSettings};
/// App::new("myprog")
/// .setting(AppSettings::DisableEnv)
/// .get_matches();
/// ```
DisableEnv,
/// Disables the automatic collapsing of positional args into `[ARGS]` inside the usage string
///
/// # Examples

View file

@ -84,7 +84,9 @@ fn assert_app_flags(arg: &Arg) {
checker!(Last requires TakesValue);
checker!(HideDefaultValue requires TakesValue);
checker!(MultipleValues requires TakesValue);
#[cfg(feature = "env")]
checker!(HideEnv requires TakesValue);
#[cfg(feature = "env")]
checker!(HideEnvValues requires TakesValue);
checker!(IgnoreCase requires TakesValue);
}

View file

@ -12,13 +12,14 @@ pub use self::value_hint::ValueHint;
use std::{
borrow::Cow,
cmp::{Ord, Ordering},
env,
error::Error,
ffi::{OsStr, OsString},
ffi::OsStr,
fmt::{self, Display, Formatter},
str,
sync::{Arc, Mutex},
};
#[cfg(feature = "env")]
use std::{env, ffi::OsString};
// Third Party
#[cfg(feature = "regex")]
@ -112,6 +113,7 @@ pub struct Arg<'help> {
pub(crate) default_vals: Vec<&'help OsStr>,
pub(crate) default_vals_ifs: VecMap<(Id, Option<&'help OsStr>, Option<&'help OsStr>)>,
pub(crate) default_missing_vals: Vec<&'help OsStr>,
#[cfg(feature = "env")]
pub(crate) env: Option<(&'help OsStr, Option<OsString>)>,
pub(crate) terminator: Option<&'help str>,
pub(crate) index: Option<usize>,
@ -262,6 +264,7 @@ impl<'help> Arg<'help> {
/// let arg = Arg::new("foo").env("ENVIRONMENT");
/// assert_eq!(Some(OsStr::new("ENVIRONMENT")), arg.get_env());
/// ```
#[cfg(feature = "env")]
pub fn get_env(&self) -> Option<&OsStr> {
self.env.as_ref().map(|x| x.0)
}
@ -3141,6 +3144,7 @@ impl<'help> Arg<'help> {
/// [`ArgMatches::is_present`]: ArgMatches::is_present()
/// [`Arg::takes_value(true)`]: Arg::takes_value()
/// [`Arg::use_delimiter(true)`]: Arg::use_delimiter()
#[cfg(feature = "env")]
#[inline]
pub fn env(self, name: &'help str) -> Self {
self.env_os(OsStr::new(name))
@ -3149,6 +3153,7 @@ impl<'help> Arg<'help> {
/// 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.
#[cfg(feature = "env")]
#[inline]
pub fn env_os(mut self, name: &'help OsStr) -> Self {
self.env = Some((name, env::var_os(name)));
@ -3941,6 +3946,7 @@ impl<'help> Arg<'help> {
///
/// If we were to run the above program with `--help` the `[env: MODE]` portion of the help
/// text would be omitted.
#[cfg(feature = "env")]
#[inline]
pub fn hide_env(self, hide: bool) -> Self {
if hide {
@ -3980,6 +3986,7 @@ impl<'help> Arg<'help> {
///
/// If we were to run the above program with `$ CONNECT=super_secret connect --help` the
/// `[default: CONNECT=super_secret]` portion of the help text would be omitted.
#[cfg(feature = "env")]
#[inline]
pub fn hide_env_values(self, hide: bool) -> Self {
if hide {
@ -4753,6 +4760,7 @@ impl<'help> From<&'help Yaml> for Arg<'help> {
"default_value_if" => yaml_tuple3!(a, v, default_value_if),
"default_value_ifs" => yaml_tuple3!(a, v, default_value_if),
"default_missing_value" => yaml_to_str!(a, v, default_missing_value),
#[cfg(feature = "env")]
"env" => yaml_to_str!(a, v, env),
"value_names" => yaml_vec_or_str!(a, v, value_name),
"groups" => yaml_vec_or_str!(a, v, group),
@ -4764,6 +4772,9 @@ impl<'help> From<&'help Yaml> for Arg<'help> {
"last" => yaml_to_bool!(a, v, last),
"value_hint" => yaml_str_parse!(a, v, value_hint),
"hide_default_value" => yaml_to_bool!(a, v, hide_default_value),
#[cfg(feature = "env")]
"hide_env" => yaml_to_bool!(a, v, hide_env),
#[cfg(feature = "env")]
"hide_env_values" => yaml_to_bool!(a, v, hide_env_values),
"hide_possible_values" => yaml_to_bool!(a, v, hide_possible_values),
"overrides_with" => yaml_to_str!(a, v, overrides_with),
@ -4955,7 +4966,10 @@ impl<'help> Eq for Arg<'help> {}
impl<'help> fmt::Debug for Arg<'help> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
f.debug_struct("Arg")
let mut ds = f.debug_struct("Arg");
#[allow(unused_mut)]
let mut ds = ds
.field("id", &self.id)
.field("provider", &self.provider)
.field("name", &self.name)
@ -4990,15 +5004,20 @@ impl<'help> fmt::Debug for Arg<'help> {
.field("val_delim", &self.val_delim)
.field("default_vals", &self.default_vals)
.field("default_vals_ifs", &self.default_vals_ifs)
.field("env", &self.env)
.field("terminator", &self.terminator)
.field("index", &self.index)
.field("help_heading", &self.help_heading)
.field("global", &self.global)
.field("exclusive", &self.exclusive)
.field("value_hint", &self.value_hint)
.field("default_missing_vals", &self.default_missing_vals)
.finish()
.field("default_missing_vals", &self.default_missing_vals);
#[cfg(feature = "env")]
{
ds = ds.field("env", &self.env);
}
ds.finish()
}
}

View file

@ -23,10 +23,12 @@ bitflags! {
const LAST = 1 << 14;
const HIDE_DEFAULT_VAL = 1 << 15;
const CASE_INSENSITIVE = 1 << 16;
#[cfg(feature = "env")]
const HIDE_ENV_VALS = 1 << 17;
const HIDDEN_SHORT_H = 1 << 18;
const HIDDEN_LONG_H = 1 << 19;
const MULTIPLE_VALS = 1 << 20;
#[cfg(feature = "env")]
const HIDE_ENV = 1 << 21;
}
}
@ -51,7 +53,9 @@ impl_settings! { ArgSettings, ArgFlags,
RequireEquals("requireequals") => Flags::REQUIRE_EQUALS,
Last("last") => Flags::LAST,
IgnoreCase("ignorecase") => Flags::CASE_INSENSITIVE,
#[cfg(feature = "env")]
HideEnv("hideenv") => Flags::HIDE_ENV,
#[cfg(feature = "env")]
HideEnvValues("hideenvvalues") => Flags::HIDE_ENV_VALS,
HideDefaultValue("hidedefaultvalue") => Flags::HIDE_DEFAULT_VAL,
HiddenShortHelp("hiddenshorthelp") => Flags::HIDDEN_SHORT_H,
@ -107,9 +111,11 @@ pub enum ArgSettings {
/// Possible values become case insensitive
IgnoreCase,
/// Hides environment variable arguments from the help message
#[cfg(feature = "env")]
HideEnv,
/// Hides any values currently assigned to ENV variables in the help message (good for sensitive
/// information)
#[cfg(feature = "env")]
HideEnvValues,
/// The argument should **not** be shown in short help text
HiddenShortHelp,
@ -178,10 +184,12 @@ mod test {
"ignorecase".parse::<ArgSettings>().unwrap(),
ArgSettings::IgnoreCase
);
#[cfg(feature = "env")]
assert_eq!(
"hideenv".parse::<ArgSettings>().unwrap(),
ArgSettings::HideEnv
);
#[cfg(feature = "env")]
assert_eq!(
"hideenvvalues".parse::<ArgSettings>().unwrap(),
ArgSettings::HideEnvValues

View file

@ -526,24 +526,36 @@ macro_rules! clap_app {
macro_rules! impl_settings {
($settings:ident, $flags:ident,
$( $setting:ident($str:expr) => $flag:path ),+
$(
$(#[$inner:ident $($args:tt)*])*
$setting:ident($str:expr) => $flag:path
),+
) => {
impl $flags {
pub(crate) fn set(&mut self, s: $settings) {
match s {
$($settings::$setting => self.0.insert($flag)),*
$(
$(#[$inner $($args)*])*
$settings::$setting => self.0.insert($flag),
)*
}
}
pub(crate) fn unset(&mut self, s: $settings) {
match s {
$($settings::$setting => self.0.remove($flag)),*
$(
$(#[$inner $($args)*])*
$settings::$setting => self.0.remove($flag),
)*
}
}
pub(crate) fn is_set(&self, s: $settings) -> bool {
match s {
$($settings::$setting => self.0.contains($flag)),*
$(
$(#[$inner $($args)*])*
$settings::$setting => self.0.contains($flag),
)*
}
}
}
@ -552,7 +564,10 @@ macro_rules! impl_settings {
type Err = String;
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
match &*s.to_ascii_lowercase() {
$( $str => Ok($settings::$setting), )*
$(
$(#[$inner $($args)*])*
$str => Ok($settings::$setting),
)*
_ => Err(format!("unknown AppSetting: `{}`", s)),
}
}

View file

@ -559,6 +559,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
fn spec_vals(&self, a: &Arg) -> String {
debug!("Help::spec_vals: a={}", a);
let mut spec_vals = vec![];
#[cfg(feature = "env")]
if let Some(ref env) = a.env {
if !a.is_set(ArgSettings::HideEnv) {
debug!(

View file

@ -11,6 +11,7 @@ use crate::INTERNAL_ERROR_MSG;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueType {
Unknown,
#[cfg(feature = "env")]
EnvVariable,
CommandLine,
DefaultValue,

View file

@ -16,7 +16,7 @@ use crate::{
parse::features::suggestions,
parse::{ArgMatcher, SubCommand},
parse::{Validator, ValueType},
util::{str_to_bool, termcolor::ColorChoice, ArgStr, ChildGraph, Id},
util::{termcolor::ColorChoice, ArgStr, ChildGraph, Id},
INTERNAL_ERROR_MSG, INVALID_UTF8,
};
@ -1767,15 +1767,13 @@ impl<'help, 'app> Parser<'help, 'app> {
}
}
#[cfg(feature = "env")]
pub(crate) fn add_env(
&mut self,
matcher: &mut ArgMatcher,
trailing_values: bool,
) -> ClapResult<()> {
if self.app.is_set(AS::DisableEnv) {
debug!("Parser::add_env: Env vars disabled, quitting");
return Ok(());
}
use crate::util::str_to_bool;
self.app.args.args().try_for_each(|a| {
// Use env only if the arg was absent among command line args,

View file

@ -32,8 +32,12 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
) -> ClapResult<()> {
debug!("Validator::validate");
let mut reqs_validated = false;
#[cfg(feature = "env")]
self.p.add_env(matcher, trailing_values)?;
self.p.add_defaults(matcher, trailing_values);
if let ParseState::Opt(a) = parse_state {
debug!("Validator::validate: needs_val_of={:?}", a);
self.validate_required(matcher)?;

View file

@ -4,11 +4,14 @@ mod argstr;
mod fnv;
mod graph;
mod id;
#[cfg(feature = "env")]
mod str_to_bool;
pub use self::fnv::Key;
pub(crate) use self::{argstr::ArgStr, graph::ChildGraph, id::Id, str_to_bool::str_to_bool};
#[cfg(feature = "env")]
pub(crate) use self::str_to_bool::str_to_bool;
pub(crate) use self::{argstr::ArgStr, graph::ChildGraph, id::Id};
pub(crate) use vec_map::VecMap;
#[cfg(feature = "color")]

View file

@ -1,7 +1,9 @@
#![cfg(feature = "env")]
use std::env;
use std::ffi::OsStr;
use clap::{App, AppSettings, Arg};
use clap::{App, Arg};
#[test]
fn env() {
@ -351,23 +353,3 @@ fn validator_invalid() {
assert!(r.is_err());
}
#[test]
fn env_disabled() {
env::set_var("CLP_TEST_DISABLE_ENV", "env");
let r = App::new("df")
.arg(
Arg::from("[arg] 'some opt'")
.env("CLP_TEST_DISABLE_ENV")
.takes_value(true),
)
.setting(AppSettings::DisableEnv)
.try_get_matches_from(vec![""]);
assert!(r.is_ok());
let m = r.unwrap();
assert!(!m.is_present("arg"));
assert_eq!(m.occurrences_of("arg"), 0);
assert_eq!(m.value_of("arg"), None);
}

View file

@ -549,6 +549,7 @@ FLAGS:
-V, --version
Print version information";
#[cfg(feature = "env")]
static HIDE_ENV: &str = "ctest 0.1
USAGE:
@ -561,6 +562,7 @@ FLAGS:
OPTIONS:
-c, --cafe <FILE> A coffeehouse, coffee shop, or café.";
#[cfg(feature = "env")]
static SHOW_ENV: &str = "ctest 0.1
USAGE:
@ -573,6 +575,7 @@ FLAGS:
OPTIONS:
-c, --cafe <FILE> A coffeehouse, coffee shop, or café. [env: ENVVAR=MYVAL]";
#[cfg(feature = "env")]
static HIDE_ENV_VALS: &str = "ctest 0.1
USAGE:
@ -586,6 +589,7 @@ OPTIONS:
-c, --cafe <FILE> A coffeehouse, coffee shop, or café. [env: ENVVAR]
-p, --pos <VAL> Some vals [possible values: fast, slow]";
#[cfg(feature = "env")]
static SHOW_ENV_VALS: &str = "ctest 0.1
USAGE:
@ -1729,6 +1733,7 @@ fn issue_1052_require_delim_help() {
));
}
#[cfg(feature = "env")]
#[test]
fn hide_env() {
use std::env;
@ -1747,6 +1752,7 @@ fn hide_env() {
assert!(utils::compare_output(app, "ctest --help", HIDE_ENV, false));
}
#[cfg(feature = "env")]
#[test]
fn show_env() {
use std::env;
@ -1765,6 +1771,7 @@ fn show_env() {
assert!(utils::compare_output(app, "ctest --help", SHOW_ENV, false));
}
#[cfg(feature = "env")]
#[test]
fn hide_env_vals() {
use std::env;
@ -1799,6 +1806,7 @@ fn hide_env_vals() {
));
}
#[cfg(feature = "env")]
#[test]
fn show_env_vals() {
use std::env;

View file

@ -75,6 +75,7 @@ fn multiple_occurrences_of_flags_large_quantity() {
assert_eq!(m.occurrences_of("multflag"), 1024);
}
#[cfg(feature = "env")]
#[test]
fn multiple_occurrences_of_before_env() {
let app = App::new("mo_before_env").arg(
@ -102,6 +103,7 @@ fn multiple_occurrences_of_before_env() {
assert_eq!(m.unwrap().occurrences_of("verbose"), 3);
}
#[cfg(feature = "env")]
#[test]
fn multiple_occurrences_of_after_env() {
let app = App::new("mo_after_env").arg(