Skip to content

Commit

Permalink
Merge pull request #2694 from clap-rs/env-feature
Browse files Browse the repository at this point in the history
Add env feature gate
  • Loading branch information
pksunkara committed Aug 14, 2021
2 parents 52d064b + c7985fb commit 441ff68
Show file tree
Hide file tree
Showing 15 changed files with 112 additions and 67 deletions.
31 changes: 22 additions & 9 deletions Cargo.toml
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions README.md
Expand Up @@ -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`:
Expand All @@ -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

Expand Down
19 changes: 2 additions & 17 deletions src/build/app/settings.rs
Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/build/arg/debug_asserts.rs
Expand Up @@ -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);
}
31 changes: 25 additions & 6 deletions src/build/arg/mod.rs
Expand Up @@ -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")]
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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))
Expand All @@ -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)));
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -4756,6 +4763,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),
Expand All @@ -4767,6 +4775,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),
Expand Down Expand Up @@ -4954,7 +4965,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)
Expand Down Expand Up @@ -4989,15 +5003,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()
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/build/arg/settings.rs
Expand Up @@ -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;
}
}
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
25 changes: 20 additions & 5 deletions src/macros.rs
Expand Up @@ -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),
)*
}
}
}
Expand All @@ -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)),
}
}
Expand Down
1 change: 1 addition & 0 deletions src/output/help.rs
Expand Up @@ -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!(
Expand Down
1 change: 1 addition & 0 deletions src/parse/matches/matched_arg.rs
Expand Up @@ -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,
Expand Down
8 changes: 3 additions & 5 deletions src/parse/parser.rs
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions src/parse/validator.rs
Expand Up @@ -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)?;
Expand Down

0 comments on commit 441ff68

Please sign in to comment.