Skip to content
Open
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
53 changes: 53 additions & 0 deletions clap_builder/src/builder/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ pub struct Arg {
pub(crate) default_missing_vals: Vec<OsStr>,
#[cfg(feature = "env")]
pub(crate) env: Option<(OsStr, Option<OsString>)>,
#[cfg(all(feature = "env", feature = "string"))]
pub(crate) env_prefix: Option<Option<OsStr>>,
pub(crate) terminator: Option<Str>,
pub(crate) index: Option<usize>,
pub(crate) help_heading: Option<Option<Str>>,
Expand Down Expand Up @@ -2221,6 +2223,41 @@ impl Arg {
pub fn env_os(self, name: impl Into<OsStr>) -> Self {
self.env(name)
}

/// Sets an env variable prefix for this argument.
///
/// When set, the env variable name specified via [`Arg::env`] will be
/// prefixed with this value (joined by `_`) during build.
///
/// An explicit `Arg::env_prefix` takes precedence over
/// [`Command::next_env_prefix`].
///
/// This can be reset with `None`.
///
/// # Examples
///
/// ```rust
/// # #[cfg(all(feature = "env", feature = "string"))] {
/// # use clap_builder as clap;
/// # use clap::{Command, Arg};
/// let cmd = Command::new("myapp")
/// .arg(Arg::new("config")
/// .long("config")
/// .env("CONFIG")
/// .env_prefix("MYAPP"));
/// // env var will be MYAPP_CONFIG
/// # }
/// ```
///
/// [`Arg::env`]: Arg::env()
/// [`Command::next_env_prefix`]: crate::Command::next_env_prefix()
#[cfg(all(feature = "env", feature = "string"))]
#[inline]
#[must_use]
pub fn env_prefix(mut self, prefix: impl IntoResettable<OsStr>) -> Self {
self.env_prefix = Some(prefix.into_resettable().into_option());
self
}
}

/// # Help
Expand Down Expand Up @@ -4407,6 +4444,18 @@ impl Arg {
self.env.as_ref().map(|x| x.0.as_os_str())
}

/// Get the env variable prefix for this argument, if any.
///
/// See [`Arg::env_prefix`].
#[cfg(all(feature = "env", feature = "string"))]
#[inline]
pub fn get_env_prefix(&self) -> Option<&std::ffi::OsStr> {
self.env_prefix
.as_ref()
.and_then(|p| p.as_ref())
.map(|p| p.as_os_str())
}

/// Get the default values specified for this argument, if any
///
/// # Examples
Expand Down Expand Up @@ -4829,6 +4878,10 @@ impl fmt::Debug for Arg {
{
ds = ds.field("env", &self.env);
}
#[cfg(all(feature = "env", feature = "string"))]
{
ds = ds.field("env_prefix", &self.env_prefix);
}

ds.finish()
}
Expand Down
65 changes: 65 additions & 0 deletions clap_builder/src/builder/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use std::path::Path;
// Internal
use crate::builder::ArgAction;
use crate::builder::IntoResettable;
#[cfg(all(feature = "env", feature = "string"))]
use crate::builder::OsStr;
use crate::builder::PossibleValue;
use crate::builder::Str;
use crate::builder::StyledStr;
Expand Down Expand Up @@ -101,6 +103,8 @@ pub struct Command {
subcommands: Vec<Command>,
groups: Vec<ArgGroup>,
current_help_heading: Option<Str>,
#[cfg(all(feature = "env", feature = "string"))]
current_env_prefix: Option<OsStr>,
current_disp_ord: Option<usize>,
subcommand_value_name: Option<Str>,
subcommand_heading: Option<Str>,
Expand Down Expand Up @@ -185,6 +189,11 @@ impl Command {

arg.help_heading
.get_or_insert_with(|| self.current_help_heading.clone());
#[cfg(all(feature = "env", feature = "string"))]
{
arg.env_prefix
.get_or_insert_with(|| self.current_env_prefix.clone());
}
self.args.push(arg);
}

Expand Down Expand Up @@ -2378,6 +2387,41 @@ impl Command {
self
}

/// Sets a prefix to be prepended to the environment variable names of all
/// subsequent arguments added to this command.
///
/// This is a stateful method that affects all future [`Arg`]s added via
/// [`Command::arg`]. An explicit [`Arg::env_prefix`] on an argument takes
/// precedence over this.
///
/// The prefix and the argument's env name will be joined with `_`.
///
/// This is modeled after [`Command::next_help_heading`].
///
/// # Examples
///
/// ```rust
/// # #[cfg(all(feature = "env", feature = "string"))] {
/// # use clap_builder as clap;
/// # use clap::{Command, Arg};
/// let cmd = Command::new("myapp")
/// .next_env_prefix("MYAPP")
/// .arg(Arg::new("config").long("config").env("CONFIG"))
/// .arg(Arg::new("verbose").long("verbose"));
/// // config's env var will be MYAPP_CONFIG
/// # }
/// ```
///
/// [`Command::arg`]: Command::arg()
/// [`Arg::env_prefix`]: crate::Arg::env_prefix()
#[cfg(all(feature = "env", feature = "string"))]
#[inline]
#[must_use]
pub fn next_env_prefix(mut self, prefix: impl IntoResettable<OsStr>) -> Self {
self.current_env_prefix = prefix.into_resettable().into_option();
self
}

/// Change the starting value for assigning future display orders for args.
///
/// This will be used for any arg that hasn't had [`Arg::display_order`] called.
Expand Down Expand Up @@ -3834,6 +3878,13 @@ impl Command {
self.current_help_heading.as_deref()
}

/// Get the env prefix specified via [`Command::next_env_prefix`].
#[cfg(all(feature = "env", feature = "string"))]
#[inline]
pub fn get_next_env_prefix(&self) -> Option<&std::ffi::OsStr> {
self.current_env_prefix.as_ref().map(|s| s.as_os_str())
}

/// Iterate through the *visible* aliases for this subcommand.
#[inline]
pub fn get_visible_aliases(&self) -> impl Iterator<Item = &str> + '_ {
Expand Down Expand Up @@ -4440,6 +4491,18 @@ impl Command {
}
}

// Apply env prefix to env variable names
#[cfg(all(feature = "env", feature = "string"))]
if let Some(Some(ref prefix)) = a.env_prefix {
if let Some((ref env_name, _)) = a.env {
let mut prefixed = prefix.to_os_string();
prefixed.push("_");
prefixed.push(env_name.as_os_str());
let value = env::var_os(&prefixed);
Copy link
Member

Choose a reason for hiding this comment

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

Not thrilled with us having looked up the env and now we look it up again

a.env = Some((OsStr::from_string(prefixed), value));
}
}

// Figure out implied settings
a._build();
if hide_pv && a.is_takes_value_set() {
Expand Down Expand Up @@ -5221,6 +5284,8 @@ impl Default for Command {
subcommands: Default::default(),
groups: Default::default(),
current_help_heading: Default::default(),
#[cfg(all(feature = "env", feature = "string"))]
current_env_prefix: Default::default(),
current_disp_ord: Some(0),
subcommand_value_name: Default::default(),
subcommand_heading: Default::default(),
Expand Down
2 changes: 2 additions & 0 deletions clap_derive/src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ impl Parse for ClapAttr {
"skip" => Some(MagicAttrName::Skip),
"next_display_order" => Some(MagicAttrName::NextDisplayOrder),
"next_help_heading" => Some(MagicAttrName::NextHelpHeading),
"next_env_prefix" => Some(MagicAttrName::NextEnvPrefix),
"default_value_t" => Some(MagicAttrName::DefaultValueT),
"default_values_t" => Some(MagicAttrName::DefaultValuesT),
"default_value_os_t" => Some(MagicAttrName::DefaultValueOsT),
Expand Down Expand Up @@ -167,6 +168,7 @@ pub(crate) enum MagicAttrName {
DefaultValuesOsT,
NextDisplayOrder,
NextHelpHeading,
NextEnvPrefix,
}

#[derive(Clone)]
Expand Down
7 changes: 5 additions & 2 deletions clap_derive/src/derives/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ pub(crate) fn gen_augment(
};

let next_help_heading = item.next_help_heading();
let next_env_prefix = item.next_env_prefix();
let next_display_order = item.next_display_order();
let flatten_group_assert = if matches!(**ty, Ty::Option) {
quote_spanned! { kind.span()=>
Expand All @@ -240,15 +241,17 @@ pub(crate) fn gen_augment(
#flatten_group_assert
let #app_var = #app_var
#next_help_heading
#next_display_order;
#next_display_order
#next_env_prefix;
let #app_var = <#inner_type as clap::Args>::augment_args_for_update(#app_var);
})
} else {
Some(quote_spanned! { kind.span()=>
#flatten_group_assert
let #app_var = #app_var
#next_help_heading
#next_display_order;
#next_display_order
#next_env_prefix;
let #app_var = <#inner_type as clap::Args>::augment_args(#app_var);
})
}
Expand Down
7 changes: 5 additions & 2 deletions clap_derive/src/derives/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,21 +188,24 @@ fn gen_augment(
quote!()
};
let next_help_heading = item.next_help_heading();
let next_env_prefix = item.next_env_prefix();
let next_display_order = item.next_display_order();
let subcommand = if override_required {
quote! {
#deprecations
let #app_var = #app_var
#next_help_heading
#next_display_order;
#next_display_order
#next_env_prefix;
let #app_var = <#ty as clap::Subcommand>::augment_subcommands_for_update(#app_var);
}
} else {
quote! {
#deprecations
let #app_var = #app_var
#next_help_heading
#next_display_order;
#next_display_order
#next_env_prefix;
let #app_var = <#ty as clap::Subcommand>::augment_subcommands(#app_var);
}
};
Expand Down
16 changes: 16 additions & 0 deletions clap_derive/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub(crate) struct Item {
force_long_help: bool,
next_display_order: Option<Method>,
next_help_heading: Option<Method>,
next_env_prefix: Option<Method>,
is_enum: bool,
is_positional: bool,
skip_group: bool,
Expand Down Expand Up @@ -273,6 +274,7 @@ impl Item {
force_long_help: false,
next_display_order: None,
next_help_heading: None,
next_env_prefix: None,
is_enum: false,
is_positional: true,
skip_group: false,
Expand Down Expand Up @@ -819,6 +821,13 @@ impl Item {
self.next_help_heading = Some(Method::new(attr.name.clone(), quote!(#expr)));
}

Some(MagicAttrName::NextEnvPrefix) => {
assert_attr_kind(attr, &[AttrKind::Command])?;

let expr = attr.value_or_abort()?;
self.next_env_prefix = Some(Method::new(attr.name.clone(), quote!(#expr)));
}

Some(MagicAttrName::RenameAll) => {
let lit = attr.lit_str_or_abort()?;
self.casing = CasingStyle::from_lit(lit)?;
Expand Down Expand Up @@ -967,9 +976,11 @@ impl Item {
pub(crate) fn initial_top_level_methods(&self) -> TokenStream {
let next_display_order = self.next_display_order.as_ref().into_iter();
let next_help_heading = self.next_help_heading.as_ref().into_iter();
let next_env_prefix = self.next_env_prefix.as_ref().into_iter();
quote!(
#(#next_display_order)*
#(#next_help_heading)*
#(#next_env_prefix)*
)
}

Expand Down Expand Up @@ -1011,6 +1022,11 @@ impl Item {
quote!( #(#next_help_heading)* )
}

pub(crate) fn next_env_prefix(&self) -> TokenStream {
let next_env_prefix = self.next_env_prefix.as_ref().into_iter();
quote!( #(#next_env_prefix)* )
}

pub(crate) fn id(&self) -> &Name {
&self.name
}
Expand Down
Loading
Loading