From c7985fb73e51cfece8c0c5946e3fbe42a1552ee9 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Sat, 14 Aug 2021 01:04:49 +0100 Subject: [PATCH] Add env feature gate --- Cargo.toml | 31 ++++++++++++++++++++++--------- README.md | 10 +++++++--- src/build/app/settings.rs | 19 ++----------------- src/build/arg/debug_asserts.rs | 2 ++ src/build/arg/mod.rs | 31 +++++++++++++++++++++++++------ src/build/arg/settings.rs | 8 ++++++++ src/macros.rs | 25 ++++++++++++++++++++----- src/output/help.rs | 1 + src/parse/matches/matched_arg.rs | 1 + src/parse/parser.rs | 8 +++----- src/parse/validator.rs | 4 ++++ src/util/mod.rs | 5 ++++- tests/env.rs | 24 +++--------------------- tests/help.rs | 8 ++++++++ tests/multiple_occurrences.rs | 2 ++ 15 files changed, 112 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6b5929b878f..81e25f27168 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/README.md b/README.md index e130481c549..f18d2303467 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/build/app/settings.rs b/src/build/app/settings.rs index 13b90ea833e..2a69e124fb0 100644 --- a/src/build/app/settings.rs +++ b/src/build/app/settings.rs @@ -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 diff --git a/src/build/arg/debug_asserts.rs b/src/build/arg/debug_asserts.rs index 5528aa1aa9f..56a5e2c4d81 100644 --- a/src/build/arg/debug_asserts.rs +++ b/src/build/arg/debug_asserts.rs @@ -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); } diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs index 773745c3cc0..7377040c57a 100644 --- a/src/build/arg/mod.rs +++ b/src/build/arg/mod.rs @@ -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)>, pub(crate) terminator: Option<&'help str>, pub(crate) index: Option, @@ -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() } } diff --git a/src/build/arg/settings.rs b/src/build/arg/settings.rs index 7143aa51aff..0b3cd3b6c8f 100644 --- a/src/build/arg/settings.rs +++ b/src/build/arg/settings.rs @@ -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::().unwrap(), ArgSettings::IgnoreCase ); + #[cfg(feature = "env")] assert_eq!( "hideenv".parse::().unwrap(), ArgSettings::HideEnv ); + #[cfg(feature = "env")] assert_eq!( "hideenvvalues".parse::().unwrap(), ArgSettings::HideEnvValues diff --git a/src/macros.rs b/src/macros.rs index 8d73dc69c17..1caccd2fa13 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -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::Err> { match &*s.to_ascii_lowercase() { - $( $str => Ok($settings::$setting), )* + $( + $(#[$inner $($args)*])* + $str => Ok($settings::$setting), + )* _ => Err(format!("unknown AppSetting: `{}`", s)), } } diff --git a/src/output/help.rs b/src/output/help.rs index 8de210e158b..7e118d04aec 100644 --- a/src/output/help.rs +++ b/src/output/help.rs @@ -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!( diff --git a/src/parse/matches/matched_arg.rs b/src/parse/matches/matched_arg.rs index 3ce7f514cbe..c9a5f2d0198 100644 --- a/src/parse/matches/matched_arg.rs +++ b/src/parse/matches/matched_arg.rs @@ -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, diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 7b114f783c2..0183940af35 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -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, diff --git a/src/parse/validator.rs b/src/parse/validator.rs index d2cae08777c..e58a7464785 100644 --- a/src/parse/validator.rs +++ b/src/parse/validator.rs @@ -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)?; diff --git a/src/util/mod.rs b/src/util/mod.rs index eb7b8331e58..a3e9544e5e6 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -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")] diff --git a/tests/env.rs b/tests/env.rs index 6ae883211ef..3508d3b4dbc 100644 --- a/tests/env.rs +++ b/tests/env.rs @@ -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); -} diff --git a/tests/help.rs b/tests/help.rs index bb3dd0846f0..863f122146b 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -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 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 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 A coffeehouse, coffee shop, or café. [env: ENVVAR] -p, --pos 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; diff --git a/tests/multiple_occurrences.rs b/tests/multiple_occurrences.rs index 4fa15b4db07..745d87e0b65 100644 --- a/tests/multiple_occurrences.rs +++ b/tests/multiple_occurrences.rs @@ -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(