-
-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Improve how we infer behavior from field types #4626
Comments
I've added a requirements section and changed the headers so it was easier to see what the top-level options are. |
Could you add "expected example code" to each section for what the common case and the exception cases would be for user code? |
This let's you get an arguments values, grouped by the occurrence of the argument. Note: this does not stablize derive support. That requires a blocking change and can be enabled via `unstable-v5` flag. See clap-rs#4626 for an exploration of how we can make this easier in the future. Fixes clap-rs#2924
I was pointed here from #3114. I guess I came up with a rough idea that is basically approach 3 here but in broader aspect. Instead of passing a custom trait, I proposed adding a new argument to disable the generation of the |
Are you suggesting having a way to derive |
That was the initial idea. I guess it more related to custom containers than to support The other options seem limiting regarding custom container support. |
Just adding a somewhat minimal real-world example of where this is useful, imagine I want to use a letter to represent a list (this came up in the context of logging): use clap::Parser;
#[derive(Debug)]
struct MyError;
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "MyError")
}
}
impl std::error::Error for MyError { }
#[derive(Clone, Copy, Debug)]
enum Level {
Debug,
Info,
Warning,
Error,
}
impl Level {
pub fn from_str(src: &str) -> Result<Vec<Level>, MyError> {
let mut out = Vec::with_capacity(src.len());
for c in src.chars() {
match Self::try_from(c) {
Ok(x) => out.push(x),
Err(e) => return Err(e),
}
}
Ok(out)
}
}
impl TryFrom<char> for Level {
type Error = MyError;
fn try_from(value: char) -> Result<Self, MyError> {
match value {
'd' => Ok(Self::Debug),
'i' => Ok(Self::Info),
'w' => Ok(Self::Warning),
'e' => Ok(Self::Error),
_ => Err(MyError),
}
}
}
#[derive(Debug, Parser)]
#[command()]
struct Command {
#[arg(long, value_parser = Level::from_str)]
pub levels: Vec<Level>,
}
fn main() {
let cmd = Command::parse();
println!("Hello --> {:?}", cmd);
} Running with |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
Different way to workaround clap-rs/clap#4626.
Different way to workaround clap-rs/clap#4626.
As brought up in #4600 and #3661, there are cases where it would be nice to support additional types with special semantics for clap_derive fields. However, doing so for any existing isn't backwards compatible, because users might have custom value parsers that produce those types. In addition, currently in order to opt out of special behavior for field types of
Opt
orVec
, you need to fully qualify the paths for those types (ex.::std::vec::Vec
), which is both undocumented, and non-obvious. It would be nice to have a consistent, easy to understand way to specify how 0 or more values for an option are aggregated into a single type.In particular here are some cases that are currently difficult or impossible to include in a
Parser
derived using clap_derive:Vec<Vec<T>>
containing the values passed in, grouped by occurrence[T; N]
, wherenum_args
is set toN
Vec<[T; N]>
would be a combination of the two above (withnum_args
set toN
, but action set toAppend
)Result<T, SomeError>
for an optional argument (likeOption<T>
, but return some default error if missing)The current types with special behavior are described here. It should also be noted, that if you specify an action of
Count
with an integral field type, also sort of fits in this category.Requirements
Arg
and extract the value fromArgMatches
for our specialized types, includingOption<T>
Option<Option<T>>
Vec<T>
Option<Vec<T>>
Vec<Vec<T>>
(v5)Option<Vec<Vec<T>>
(v5)bool
()
[T; N]
(clap_derive: support fixed arrays as shortcut for number_of_values = N #1682)Options
Add semantics for additional types in a breaking change
This perhaps the most straightforward way to do it. But it has several downsides:
Add an additional argument to the arg attribute
We could add one or more additional arguments to the
arg
attribute on fields.There are a few ways this could be done:
optional
,all_values
,optional_values
,occurrences
,optional_occurrences
, etc.clap_derive::utils::ty::Ty
enum (although we might want to use more descriptive names)ArgMatches
, see User-extensible trait1-3 have the downside that either the attribute must agree with the type of the field, or there has to be some magic conversion of the field type.
4 would make it difficult to express sitiutations
Option<Vec<MyType>>
where the value parser parses aVec<MyType>
, but theOption
is because the option is optional. It also still has the backwardscompatibility problem if we add anything new in the future.
User-extensible trait
For 3 above, we can define a trait that encapsulates the wanted behavior, and give the derive macro an implementation of that trait. While it can be defined for standard types in clap_derive, it would
also be possible to allow users to create their own implementations, opening up additional ways to expand this functionality in other crates.
Note: bikeshed on the actual names
And one possible implementation would be:
This could potentially use GATs, so that it isn't necessary to include type parameters to the type itself.
Expected Example Code
Support parsing a macro invocation inside the struct definition
This might look like:
optional!(T)
is equivalent to the currentOption<T>
values!(T)
is equivalent to the currentVec<T>
flag!()
is equivalent to the currentbool
occurrences!(T)
would expand toVec<Vec<T>>
and opt in to getting a vec of values for each occurrence.optional_vec!(T)
andoptional_occurrences!(T)
or allowoptional!(values!(T))
andoptional!(occurrences!(T))
.required!(T)
which is the same as T but would make the option required?These all use the Value type
T
, but it could also use the final type instead (for example:occurrences!(Vec<Vec<T>>)
)Custom macros
This method could potentially support custom behavior by delegating to actual macro invocations in the generated impl. If the macro used looked like
example!(T)
then the generated code would have the following:CommandFactory
invoking the macro like:example!(T, arg: arg_value)
wherearg_value
is theArg
for the field, and it returns a newArg
to actually use, so that it can make modifications to the argument.example!(T)
FromArgMatches
, to get the value of the field, invokeexample!(T, matches: matches_val, name)
wherematches_val
is anArgMatches
andname
is the name of the option to extract.Note the similarity between this and the custom trait in the section on using an attribute
Expected Example Code
Use newtypes
Wrapper types might be the least confusing, since they would be clearly intended for the purpose of communicating how the field should be parsed. However, they are probably the least ergonomic to use, since you would often have to unwrap the value. Although... I'm not sure how to determine for sure that the path actually points to the right type, and not a user-defined type with the same name, without require the type to be fully specified.
From @epage:
Another possibility is we could have the newtypes implement a trait similar to the one defined above in User-extensible trait (but with
Self
instead ofSelf::Aggregate
) for the newtypes. Tha could also potentially make it user-extensible, but we still have the question of how to identify if a type is one of these types and extract the type for the value parser. Perhaps it could be combined witht he autoref specialized macro @epage mentioned above? Or use an attribute. Probably if it is user-extensible we would require that it has 0 or 1 type parameters, and if it is 1, use that to infer the type for th value parser. Or maybe we could use an associated type?Expected Example Code
Footnote:
I haven't put a lot of thought into the specific names for types, macros, attribute arguments etc. We can hash out better names for those once we decide on the general direction.
The text was updated successfully, but these errors were encountered: