Support env-backed choices with choices env=...#548
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the application's argument parsing capabilities by allowing choices for arguments and flags to be dynamically sourced from environment variables. This provides greater flexibility in configuring the application's behavior based on the environment in which it is run. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
The pull request successfully introduces support for environment-backed choices using the choices env=... syntax. The implementation is thorough, covering parsing, shell completion, documentation generation, and KDL serialization. The refactoring of choice validation into a dedicated function improves code organization and readability. New tests adequately cover the added functionality, ensuring correctness and robustness.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a84853de12
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let env_value = env | ||
| .and_then(|env_map| env_map.get(env_key).cloned()) | ||
| .or_else(|| std::env::var(env_key).ok()); |
There was a problem hiding this comment.
Keep
Parser::with_env isolated from process env
When callers use Parser::with_env to parse against a supplied environment map, this path still falls back to std::env::var, so choices env=... can be validated against ambient host variables that were never provided by the caller. That breaks the documented with_env contract in lib/src/parse.rs (“instead of the process environment”) and makes parsing nondeterministic in tests/monorepo tooling—for example, with_env(HashMap::new()) will still reject inputs if the parent process happens to export the same choice variable.
Useful? React with 👍 / 👎.
| template: FigArg::get_template(&arg.name), | ||
| generators: FigArg::get_generator(&arg.name), | ||
| suggestions: arg.choices.clone().map(|c| c.choices).unwrap_or_default(), | ||
| suggestions: arg.choices.as_ref().map(|c| c.values()).unwrap_or_default(), |
There was a problem hiding this comment.
Avoid baking current env into generated Fig suggestions
usage generate fig writes a static completion file, but this now resolves choices env=... immediately into suggestions. That means the generated script snapshots whatever environment was present when usage generate fig ran (or nothing at all if the variable was unset), so users of a checked-in/CI-generated Fig spec will see stale or empty suggestions instead of their own runtime env-backed choices.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR adds Key changes:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant CLI as CLI user
participant PW as parse_partial_with_env
participant VC as validate_choices
participant CE as choice_error
participant SC as SpecChoices::values_with_env
participant ENV as Environment
CLI->>PW: parse(["--env", "prod"], custom_env)
PW->>VC: validate_choices(target, "prod", choices, custom_env)
VC->>SC: values_with_env(custom_env)
alt custom_env provided (Parser::with_env)
SC->>SC: lookup key in custom_env map only
else no custom_env
SC->>ENV: std::env::var(env_key)
ENV-->>SC: env value (or None)
end
SC-->>VC: ["dev", "staging"] (split on comma/whitespace, deduped)
VC->>VC: is_help_arg? and value not in list?
VC-->>PW: Ok(true) — push help error & return early
VC->>CE: choice_error(target, "prod", choices, custom_env)
alt values is empty (env unset)
CE-->>VC: "no choices resolved from env DEPLOY_ENVS"
else value not in list
CE-->>VC: "expected one of dev, staging"
else value found
CE-->>VC: None (valid)
end
VC-->>PW: bail!(err) or Ok(false)
PW-->>CLI: ParseOutput or Error
Reviews (6): Last reviewed commit: "parse: fast-path help choice checks befo..." | Re-trigger Greptile |
|
Addressed review feedback in Changes made:
I could not run |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 55196528b5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let values = choices.values_with_env(custom_env); | ||
| if !values.is_empty() && !values.iter().any(|choice| choice == value) { |
There was a problem hiding this comment.
Reject inputs when env-backed choices resolve to no values
For specs that use only choices env=..., values_with_env returns an empty list when that variable is unset, and this guard then skips validation entirely. That means any explicit CLI value is accepted even though the spec declared a constrained choice set, so a missing env var in CI or a custom Parser::with_env map silently disables the new restriction instead of failing fast.
Useful? React with 👍 / 👎.
| self.inner.choices = Some(SpecChoices { | ||
| choices: choices.into_iter().map(Into::into).collect(), | ||
| env: None, | ||
| }); |
There was a problem hiding this comment.
Preserve
choices_env when choices() is called later
The new builder API is order-sensitive here: choices() recreates SpecChoices with env: None, so .choices_env("DEPLOY_ENVS").choices(["local"]) drops the env-backed values completely. Callers building specs programmatically can easily hit this and end up with parser/completion behavior that no longer matches the intended combined literal+env choices.
Useful? React with 👍 / 👎.
|
Addressed the two additional review comments in Updates:
I also updated the PR description to the v3 message. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c49eff4d7d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let Some(choices) = &arg.choices { | ||
| return Ok(choices | ||
| .choices | ||
| .iter() | ||
| .map(|c| (c.clone(), String::new())) | ||
| .filter(|(c, _)| c.starts_with(ctoken)) | ||
| .collect()); | ||
| let values = choices.values(); | ||
| if !values.is_empty() { |
There was a problem hiding this comment.
Return no completions when env-backed choices resolve empty
When an arg/flag is constrained only by choices env=... and that variable is unset in the completion process, choices.values() is empty and this branch falls through instead of short-circuiting. In complete_word(), an empty result is then treated as “no known completion” and we fall back to complete.run/filesystem suggestions, so pressing Tab for examples/env-choices.usage.kdl with DEPLOY_ENVS unset will suggest arbitrary paths even though the parser rejects every value. Treating the presence of arg.choices as authoritative here avoids advertising impossible completions.
Useful? React with 👍 / 👎.
|
Addressed the latest completion feedback in Changes:
Verification run:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de8e0d15a5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if validate_choices( | ||
| spec, | ||
| &out.cmd, | ||
| &mut out.errors, | ||
| "arg", | ||
| &arg.name, | ||
| &w, | ||
| arg.choices.as_ref(), | ||
| custom_env, |
There was a problem hiding this comment.
Revalidate env/default-populated values against resolved choices
The new choice check only runs while consuming argv words here. Parser::parse still fills missing args/flags from arg.env, flag.env, and defaults afterwards without rechecking them, so a spec like arg <env> env="CURRENT_ENV" { choices env="DEPLOY_ENVS" } will accept CURRENT_ENV=prod even when DEPLOY_ENVS=dev,staging. That means the new choices env=... constraint is bypassed whenever the value comes from env/defaults instead of the command line.
Useful? React with 👍 / 👎.
| if let Some(default_cmd) = spec.cmd.find_subcommand(default_name) { | ||
| // Include completions from default subcommand's first arg | ||
| if let Some(arg) = default_cmd.args.first() { | ||
| has_explicit_choices = has_explicit_choices || arg.choices.is_some(); |
There was a problem hiding this comment.
Keep root file fallback when default subcommand has choices
This marks has_explicit_choices based on the default subcommand's first arg even when the active completion target is still a root positional arg. In CompleteWord::complete_word, that suppresses the file fallback at lines 161-165, so a command shaped like cmd <path> loses filesystem suggestions as soon as its default_subcommand happens to start with a choices-constrained arg.
Useful? React with 👍 / 👎.
|
Addressed the latest two review comments in Fixes:
Regression tests added:
Targeted verification:
|
There was a problem hiding this comment.
💡 Codex Review
usage/cli/src/cli/complete_word.rs
Lines 146 to 147 in 82adee1
When the root command has no positional arg of its own but default_subcommand does, this branch appends the default subcommand’s first-arg completions without updating has_explicit_choices. If the current token matches none of those choices, line 161 falls back to filesystem suggestions even though parse() will route that word into the default subcommand and reject every path. A spec like default_subcommand "run" with cmd "run" { arg "<env>" { choices "prod" "staging" } } will therefore suggest files for tool x<Tab> instead of returning no completions.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if !arg.default.is_empty() { | ||
| for default_value in &arg.default { | ||
| validate_choices( | ||
| self.spec, | ||
| &out.cmd, |
There was a problem hiding this comment.
Validate only the default value a scalar arg can emit
For non-variadic args, parse() still returns only arg.default[0], but this new loop rejects every entry in arg.default before we reach that branch. Specs built with SpecArgBuilder::default_values([..]) (which the builder tests already allow for scalar args) will now start failing choice validation on later defaults that are never observable at runtime—for example, default_values(["dev", "prod"]) plus choices ["dev"] now errors even though parsing would still yield "dev". The same regression is repeated in the nested flag-arg default block below.
Useful? React with 👍 / 👎.
|
i think you need to rebase |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #548 +/- ##
==========================================
- Coverage 70.04% 61.94% -8.10%
==========================================
Files 48 48
Lines 7080 7629 +549
Branches 7080 7629 +549
==========================================
- Hits 4959 4726 -233
- Misses 1322 1542 +220
- Partials 799 1361 +562 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Addressed the latest review comment in What changed:
Regression coverage added:
Targeted verification:
|
|
Addressed the latest scalar-default review point and the current CI lint failure in Changes:
Local verification:
|
26ab9f3 to
bcad97c
Compare
|
is it ready? |
I hope so! Full disclaimer: heavily AI-assisted. |
### 🚀 Features - Support env-backed choices with `choices env=...` by [@mustafa0x](https://github.com/mustafa0x) in [#548](#548) ### 🐛 Bug Fixes - **(zsh)** escape parentheses and brackets in completion descriptions by [@jdx](https://github.com/jdx) in [#559](#559) ### New Contributors - @mustafa0x made their first contribution in [#548](#548)
⚠️ **CAUTION: this is a major update, indicating a breaking change!**⚠️ This MR contains the following updates: | Package | Update | Change | |---|---|---| | [usage](https://github.com/jdx/usage) | major | `2.18.2` → `3.2.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>jdx/usage (usage)</summary> ### [`v3.2.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#320---2026-03-23) [Compare Source](jdx/usage@v3.1.0...v3.2.0) ##### 🚀 Features - Support env-backed choices with `choices env=...` by [@​mustafa0x](https://github.com/mustafa0x) in [#​548](jdx/usage#548) ##### 🐛 Bug Fixes - **(zsh)** escape parentheses and brackets in completion descriptions by [@​jdx](https://github.com/jdx) in [#​559](jdx/usage#559) ##### New Contributors - [@​mustafa0x](https://github.com/mustafa0x) made their first contribution in [#​548](jdx/usage#548) ### [`v3.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#310---2026-03-22) [Compare Source](jdx/usage@v3.0.0...v3.1.0) ##### 🚀 Features - **(cli)** render all doc-related fields in --help output by [@​jdx](https://github.com/jdx) in [#​554](jdx/usage#554) - **(cli)** support reading spec from stdin via --file - by [@​jdx](https://github.com/jdx) in [#​555](jdx/usage#555) ##### 🐛 Bug Fixes - **(zsh)** remove trailing space from completions and add directory slash by [@​jdx](https://github.com/jdx) in [#​556](jdx/usage#556) - use field assignment for non-exhaustive Spec in benchmarks by [@​jdx](https://github.com/jdx) in [#​552](jdx/usage#552) ##### 📦️ Dependency Updates - update apple-actions/import-codesign-certs digest to [`fe74d46`](jdx/usage@fe74d46) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​550](jdx/usage#550) - update codecov/codecov-action digest to [`1af5884`](jdx/usage@1af5884) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​551](jdx/usage#551) - lock file maintenance by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​547](jdx/usage#547) ### [`v3.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#300---2026-03-13) [Compare Source](jdx/usage@v2.18.2...v3.0.0) ##### 🚀 Features - **(spec)** **breaking** add support for license, before/after help metadata by [@​jdx](https://github.com/jdx) in [#​542](jdx/usage#542) ##### 🐛 Bug Fixes - **(cobra)** escape newlines, tabs, and carriage returns in kdlQuoteAlways by [@​thecodesmith](https://github.com/thecodesmith) in [#​539](jdx/usage#539) - bump major version for breaking changes in release automation by [@​jdx](https://github.com/jdx) in [#​544](jdx/usage#544) - add custom\_major\_increment\_regex for breaking change detection by [@​jdx](https://github.com/jdx) in [#​545](jdx/usage#545) - handle all breaking change commit formats in major bump regex by [@​jdx](https://github.com/jdx) in [27e1ab1](jdx/usage@27e1ab1) - normalize breaking change commit format in preprocessor by [@​jdx](https://github.com/jdx) in [aa72b92](jdx/usage@aa72b92) ##### 📚 Documentation - add argparse-usage integration by [@​jdx](https://github.com/jdx) in [#​531](jdx/usage#531) - mark KDL code blocks as KDL and use correct inline-comment `//` by [@​muzimuzhi](https://github.com/muzimuzhi) in [#​536](jdx/usage#536) - fix include syntax to match implementation by [@​jdx](https://github.com/jdx) in [#​540](jdx/usage#540) - consolidate integration list to single source by [@​jdx](https://github.com/jdx) in [#​541](jdx/usage#541) - fix link to integrations by [@​muzimuzhi](https://github.com/muzimuzhi) in [#​543](jdx/usage#543) ##### 🛡️ Security - **(deps)** update dependency eslint to v10 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​526](jdx/usage#526) ##### 🔍 Other Changes - Added an integration with ruby's OptionParser by [@​packrat386](https://github.com/packrat386) in [#​533](jdx/usage#533) ##### 📦️ Dependency Updates - update actions/setup-node digest to [`53b8394`](jdx/usage@53b8394) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​525](jdx/usage#525) - update jdx/mise-action action to v3 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​528](jdx/usage#528) - update rust crate roff to v1 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​529](jdx/usage#529) ##### New Contributors - [@​thecodesmith](https://github.com/thecodesmith) made their first contribution in [#​539](jdx/usage#539) - [@​packrat386](https://github.com/packrat386) made their first contribution in [#​533](jdx/usage#533) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDIuMTAiLCJ1cGRhdGVkSW5WZXIiOiI0My4xMDIuMTAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbIlJlbm92YXRlIEJvdCIsImF1dG9tYXRpb246Ym90LWF1dGhvcmVkIiwiZGVwZW5kZW5jeS10eXBlOjptYWpvciJdfQ==-->
Summary
envsupport tochoicesso specs can source allowed values from an environment variableParser::with_env(...)without falling back to the process environment when a custom env map is providedchoices env=...resolves to no values instead of silently disabling validationchoices env=...whenSpecArgBuilder::choices()is called afterchoices_env(...)choices env=...in help/docs instead of snapshotting live env-backed values into generated outputusage generate figdoes not bake generation-time environment values into a static fileExample
With
DEPLOY_ENVS="foo,bar baz", valid values becomefoo,bar, andbaz.Implementation notes
choicesnow accepts an optionalenvproperty and preserves it when serializing back to KDLParser::with_env(...)is used; it only consults the process environment when no custom env map is providedSpecArgBuilder::choices()now preserves any previously configuredchoices_env(...)Tests
Parser::with_env(...).choices_env(...).choices(...)ordering