-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use clap::ArgEnum to auto-generate possible cli arguments from Rust enums #10369
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @ehuss (or someone else) soon. Please see the contribution instructions for more information. |
83ce9af
to
87e28eb
Compare
cc @epage |
src/bin/cargo/commands/config.rs
Outdated
@@ -34,7 +34,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { | |||
Some(("get", args)) => { | |||
let opts = cargo_config::GetOptions { | |||
key: args.value_of("key"), | |||
format: args.value_of("format").unwrap().parse()?, | |||
format: args.value_of_t_or_exit("format"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't be short-circuiting cargo's exiting. We'll change expected exit codes, if nothing else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the swift review. can I use value_of_t("format")?
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ta! will change to that
src/bin/cargo/commands/tree.rs
Outdated
} else { | ||
args.value_of("prefix").unwrap() | ||
args.value_of_t_or_exit("prefix") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
src/cargo/ops/cargo_config.rs
Outdated
impl AsRef<str> for ConfigFormat { | ||
fn as_ref(&self) -> &str { | ||
match self { | ||
ConfigFormat::Toml => "toml", | ||
ConfigFormat::Json => "json", | ||
ConfigFormat::JsonValue => "json-value", | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can use PossibleValue
to help with this, like we do Display
in clap_complete::Shell
.
impl Display for Shell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_possible_value()
.expect("no values are skipped")
.get_name()
.fmt(f)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for my education, what's the difference between using a PossibleValue
vs creating a match arm that returns static strings? Are both of them exhaustive checks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This single-sources the values so you don't have to worry about typos.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To confirm, should I replace the derived clap::ArgEnum implementation with my own impl that has to_possible_value()
and value_variants()
implementations?
error[E0119]: conflicting implementations of trait `clap::ArgEnum` for type `ops::cargo_config::ConfigFormat`
--> src/cargo/ops/cargo_config.rs:13:10
|
13 | #[derive(clap::ArgEnum, Clone)]
| ^^^^^^^^^^^^^ conflicting implementation for `ops::cargo_config::ConfigFormat`
...
28 | impl ArgEnum for ConfigFormat {
| ----------------------------- first implementation here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, you can keep deriving, just use the functions from the trait to get the generated values. Shell
doesn't derive to avoid foisting the derive
feature on people not using it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the tip and sorry if I am being daft here.
self.to_possible_value()
.expect("no values are skipped")
.get_name()
this looks like a runtime check that might throw at runtime, while the current implementation is an exhaustive compile-time check. Should I still use the to_possible_value().expect("Should be fine")
and if so, what's the safest way to do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to_possible_value
only "fails" if you have a #[clap(skip)]
field. None of them do which is why an expect
is ok here to act as an assert (the expect
is documenting the invariant that it is relying on).
This is better because the other approach would have required tests for each field to make sure there were no typos. People adding new fields would need to remember to update those tests.
src/cargo/ops/tree/mod.rs
Outdated
impl AsRef<str> for Prefix { | ||
fn as_ref(&self) -> &str { | ||
match self { | ||
Prefix::None => "none", | ||
Prefix::Indent => "indent", | ||
Prefix::Depth => "depth", | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
☔ The latest upstream changes (presumably #10396) made this pull request unmergeable. Please resolve the merge conflicts. |
Use the derive feature of clap to stop relying on hardcoded vector of strings and programmatically derive config values from the enum type
Stop returning static &str and use the clap-required possible_values trait method
early bailing and messing up return code
delegate &str creation to the to_possible_value() method provided by clap::ArgEnum. Since both arguments are non-skipped, we can safely use expect()
6822cdd
to
8879d98
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this!
This adds several new dependencies, and adds some more boilerplate code. Is it possible to do this without the derive macros? Are the |
Thanks for reviewing!
Would be happy to, what specifically do you want me to trim down?
From the documentation for ArgEnum, using the trait requires the derive feature The AsRef implementations are needed for the I appreciate this might increase compile-times. It brings the benefits of automatically maintaining and generating lists of possible ConfigFormats and Prefix types by delegating to clap-based derives instead of keeping those updated manually. |
Since cargo is using the builder API, the main value /// Display format
#[long, arg_enum, default_value_t = cargo_config::ConfigFormat::Toml]
format: cargo_config::ConfigFormat
Clap accepts |
ConfigFormat::value_variants() | ||
.iter() | ||
.filter_map(ArgEnum::to_possible_value) | ||
} | ||
} | ||
|
||
impl FromStr for ConfigFormat { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we move forward with this, we should switch the FromStr
s to be implemented in terms of ArgEnum
as well
For example, clap_complete
s Shell
:
impl FromStr for Shell {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
for variant in Self::value_variants() {
if variant.to_possible_value().expect("No value is skipped").matches(s, false) {
return Ok(*variant);
}
}
Err(format!("Invalid variant: {}", s))
}
}
Closing as I would prefer to avoid pulling in several new dependencies for a relatively minor improvement (and it also seems to add quite a bit more lines of code and subjectively seems more complex). Thank you for the PR, though! |
What does this PR try to resolve?
I was familiarising myself with the codebase after the upgrade to clap-3.0 PR #10265 and
spotted a couple of low-hanging refactors. Vectors of strings were duplicated as cli representations
of enums. Those strings can be exhaustively auto-generated from the enum variants.
Tangentially related to #6104, since this automates the need to manually keep the
vectors of static strings up to date with the possible values of ConfigFormat and tree::Prefix.
How should we test and review this PR?
The PR is split into 2 commits - one to set up the whole cargo workspace with
clap + derive feature and refactor ConfigFormat. The second follows up with a
refactor to use clap derive ArgEnum for tree::Prefix.
Feel free to suggest tests, if you feel this is too untested as is.
Additional information