Skip to content

Add Command::next_env_prefix for env variable prefixing#6281

Open
veeceey wants to merge 2 commits intoclap-rs:masterfrom
veeceey:feat/issue-3221-env-prefix
Open

Add Command::next_env_prefix for env variable prefixing#6281
veeceey wants to merge 2 commits intoclap-rs:masterfrom
veeceey:feat/issue-3221-env-prefix

Conversation

@veeceey
Copy link

@veeceey veeceey commented Feb 23, 2026

Adds Command::next_env_prefix which sets a prefix that gets prepended to all subsequent arg env variable names during build. Modeled directly after next_help_heading as @epage suggested in #3221.

How it works:

  • Command::next_env_prefix("MYAPP") sets the prefix for all future args
  • When an arg has both an env name and an inherited prefix, the env name gets rewritten to MYAPP_<NAME> during _build_self
  • The prefix can be reset with next_env_prefix(None::<&str>)
  • CLI arguments still override env values as expected

The env prefix is stored on each Arg (via env_prefix field) during arg_internal, same pattern as help_heading. The actual name rewriting happens during _build_self which keeps help generation and env lookup unchanged.

Requires both env and string features since we need runtime string construction for the prefixed names.

let cmd = Command::new("myapp")
    .next_env_prefix("MYAPP")
    .arg(Arg::new("config").long("config").env("CONFIG"));
// env var will be MYAPP_CONFIG

Closes #3221

@veeceey
Copy link
Author

veeceey commented Feb 23, 2026

Test results (all env tests pass, including 5 new env_prefix tests):

running 30 tests
test env::env_prefix_does_not_affect_args_without_env ... ok
test env::env_os ... ok
test env::env ... ok
test env::env_bool_literal ... ok
test env::env_prefix_cli_overrides_env ... ok
test env::env_prefix_basic ... ok
test env::env_prefix_multiple_args ... ok
test env::env_prefix_reset ... ok
test env::multiple_no_delimiter ... ok
test env::multiple_one ... ok
test env::multiple_three ... ok
test env::no_env ... ok
test env::no_env_no_takes_value ... ok
test env::opt_user_override ... ok
test env::not_possible_value ... ok
test env::positionals ... ok
test env::positionals_user_override ... ok
test env::possible_value ... ok
test env::value_parser ... ok
test env::value_parser_invalid ... ok
test env::with_default ... ok
test env::value_parser_output ... ok
test help_env::show_env ... ok
test help_env::hide_env ... ok
test help_env::hide_env_vals_flag ... ok
test help_env::hide_env_flag ... ok
test help_env::hide_env_vals ... ok
test help_env::show_env_flag ... ok
test help_env::show_env_vals ... ok
test help_env::show_env_vals_flag ... ok

test result: ok. 30 passed; 0 failed; 0 ignored; 0 measured; 916 filtered out

@epage
Copy link
Member

epage commented Feb 23, 2026

We recommend that commits represent how things should be reviewed and merged and not how they were developed. Having fixup commits means they are not atomic.

There is also a strong preference for adding tests in a previous commit, with them passing, showing the current behavior. How to handle this when its a new API is context dependent. In this case, I would add the tests with the prefixes being hardcoded in each env variable and then the main commit would port it to the new API.

if let Some(Some(ref prefix)) = a.env_prefix {
if let Some((ref env_name, _)) = a.env {
let prefixed = format!("{}_{}", prefix, env_name.to_str().unwrap_or(""));
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

@veeceey veeceey force-pushed the feat/issue-3221-env-prefix branch from 3450bca to e8ca0e8 Compare February 24, 2026 07:13

#[cfg(feature = "string")]
#[test]
fn env_prefix_cli_overrides_env() {
Copy link
Member

Choose a reason for hiding this comment

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

This seems like it is testing env support generally rather than env-prefix-specific logic

@veeceey veeceey force-pushed the feat/issue-3221-env-prefix branch from e8ca0e8 to 05b4713 Compare February 28, 2026 06:21
Copy link
Member

Choose a reason for hiding this comment

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

Please keep in mind that new tests should be added in the previous commit like the other new tests.

Comment on lines +46 to +59
#[test]
fn command_next_env_prefix_cli_overrides() {
env::set_var("DERIVE_CLI_NAME", "from_env");

#[derive(Debug, Clone, Parser)]
#[command(next_env_prefix = "DERIVE_CLI")]
struct CliOptions {
#[arg(long, env = "NAME")]
name: Option<String>,
}

let m = CliOptions::try_parse_from(vec!["", "--name", "from_cli"]).unwrap();
assert_eq!(m.name.as_deref(), Some("from_cli"));
}
Copy link
Member

Choose a reason for hiding this comment

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

This seems to be testing env support

Comment on lines +62 to +76
fn command_next_env_prefix_with_flatten() {
env::set_var("FLAT_APP_DB", "mydb");

#[derive(Debug, Clone, Args)]
#[command(next_env_prefix = "FLAT_APP")]
struct DbArgs {
#[arg(long, env = "DB")]
db: Option<String>,
}

#[derive(Debug, Clone, Parser)]
struct CliOptions {
#[command(flatten)]
db_args: DbArgs,
}
Copy link
Member

Choose a reason for hiding this comment

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

Missing some flatten cases that next_help_heading has, like

  • flattened field with help heading

@veeceey
Copy link
Author

veeceey commented Mar 10, 2026

hey @epage, thanks for the thorough review — really helpful feedback. let me go through each point:

  1. Arg::env_prefix vs Command::env_prefix — yeah, rereading Add an env_prefix derive option for a consistent prefix for environment variables #3221 I see they wanted per-arg prefix support too. I'll look into adding that as a follow-up or folding it into this PR if it makes sense.

  2. Hand-written Debug impl — missed that, I'll update it to include the new field.

  3. env feature gate — good catch, the builder methods shouldn't silently do nothing when env isn't enabled. I'll add a compile error or gate the methods properly.

  4. to_str — agreed, I'll find a better approach here. probably working with OsStr directly instead of converting.

  5. Double env lookup — yeah that's wasteful. I'll refactor so we only resolve the env var once and pass the result through.

  6. Missing getter — will add it.

  7. Test testing env support generally — you're right, I'll trim that test down to only cover prefix-specific behavior.

  8. Turbofish — good point, I'll remove it if type inference handles it.

  9. Derive tests — will add derive tests modeled after next_help_heading.

  10. Commit structure — understood, I'll restructure so the tests come in a prior commit showing current behavior, then the implementation commit ports them to the new API.

  11. Test testing env support — will remove the redundant env test from the prefix test file.

  12. Flatten cases — will add the missing flatten scenarios like next_help_heading has.

I'll rework the commits and push an updated version. appreciate the detailed review!

veeceey added 2 commits March 11, 2026 21:37
Add tests that demonstrate the desired behavior of env variable
prefixing using hardcoded prefixed env names. These tests pass with
the current code and will be ported to use the new next_env_prefix
/ env_prefix API in the next commit.

Includes both builder and derive tests covering:
- Basic prefix application
- Multiple args with prefix
- Prefix reset
- Arg-level prefix
- Arg override of command prefix
- Flatten with prefix on the flattened struct
- Flatten with prefix on the flatten field
Add env variable prefixing support as proposed in clap-rs#3221. This follows
the same pattern as next_help_heading / help_heading.

- Command::next_env_prefix sets a prefix for all future args
- Arg::env_prefix allows per-arg override, taking precedence
- Arg::get_env_prefix getter for querying the prefix
- Prefix is joined with '_' and applied during _build_self
- Uses OsString operations for concatenation (no to_str)
- Requires both 'env' and 'string' features
- Includes env_prefix in Arg's hand-written Debug impl
- Derive support via #[command(next_env_prefix = "...")] attribute
- Port tests from hardcoded prefixes to the new API

Closes clap-rs#3221
@veeceey veeceey force-pushed the feat/issue-3221-env-prefix branch from 05b4713 to 1a94daa Compare March 12, 2026 04:39
@veeceey
Copy link
Author

veeceey commented Mar 12, 2026

rebased and addressed the test feedback:

  • removed the env_prefix_cli_overrides_env builder test and command_next_env_prefix_cli_overrides derive test — both were just testing general env/cli override behavior, not prefix-specific logic
  • added flatten_field_with_env_prefix derive test (mirrors flatten_field_with_help_heading — prefix set on the flatten field itself, not inside the flattened struct)
  • restructured commits: first commit now has both builder and derive tests with hardcoded prefixed env names, second commit ports them to the new API
  • dropped the unrelated CI/toolchain changes that crept in from the fork being behind upstream

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an env_prefix derive option for a consistent prefix for environment variables

2 participants