argh
argh copied to clipboard
Support for a minimum number of repeated arguments
Whether positional
or option
arguments that are of type Vec<T>
are always treated as "0 or more", with no way to specify "one or more", or "two or more".
This is similar to issue #13, but a more general case.
I can make two proposals here:
Proposal 1:
add an attribute such as #[argh(option, required)]
, which forces some value to be present for vec-types.
Proposal 2:
add an attribute such as #[argh(option, required(2+)]
or #[argh(option, required="2+")]
Help text would need to be adjusted accordingly as well, such as:
Usage: cmdname --option <option> --option <option> [--option <option> ...]
Options:
--option some option (2 or more required)
Implementation-wise, this seems like it could be added to parse_attrs::FieldAttrs
as a Option<usize>
for minimum number of elements with Vec
types.
I am not too familiar with writing proc macros, but I arrived here having just run into a need for this feature, and I have an idea for the syntax wrt your Proposal 2 — Range syntax. Like so:
-
#[argh(option, required)]
: Requires a non-zero number of occurrences. -
#[argh(option, required(1))]
: Requires exactly one occurrence. -
#[argh(option, required(2..))]
: Requires two or more occurrences. -
#[argh(option, required(..=2))]
: Requires two or fewer occurrences. -
#[argh(option, required(3..5))]
: Requires either three or four (not five) occurrences. -
#[argh(option, required(3..=5))]
: Requires three, four, or five occurrences.
Assuming the supported syntax is roughly similar to standard Macros, I could see this being done with a special case for a Literal, and then a fallback case which takes whatever $x:expr
is found and verifies that $x.contains(vec.len())
.
I would not complicate so much the space of possibilities. My view is the following.
At the moment there is a Optionality::Repeating
value defined which translates to "zero or more positional arguments in a Vec
". Obviously, there are (possibly many) cases where what we want is "one or more positional arguments in a Vec
". The current implementation does not distinguish between them.
My proposal would be to start distinguishing them, as follows:
-
Option<Vec<String>>
is an optional list of positional arguments represented by something likeOptionality::ZeroOrMore
, currentlyOptionality::Repeating
, and -
Vec<String>
is a "required" list of positional arguments represented by something likeOptionality::OneOrMore
.
Then, Option<Vec<String>>
will print the square brackets around the positional argument in the help message ([<files>...]
) while Vec<String>
will not (<files>...
).
The problem is that this is a breaking change, it is not backwards compatible. However, I find it more natural to think of "optionality" in terms of Option
and "cardinality" in terms of Vec
.
Let me know your thoughts.
Edit: this approach is based on the presumption that a Vec
will always have at least one element, which may be counter-intuitive in a programming context (after all we most of the times start with empty Vec
instances and populate them later), but it actually makes sense in the command-line context. When we say "my text editor program takes as parameters a list of files to open or no file at all", we actually mean that there is either a non-empty list of files (one or more) or no list at all, so having this perspective in mind, a Vec
will always have at least one element, or there will be no Vec
at all. Basically Option<Vec<String>>
. But I understand that this is not how programmers think of Vec
.
To @alexonea's proposal, I would add two things.
-
I think that application-specific use-cases like the range-based method @Yaulendil proposed needn't be handled by the parser. If desired cardinality is
3..=5
, the application can ignore extra items and report when it sees too few items. What stays true for the command-line interface is the "one or more" optionality. -
I think the current design is great in saying that for an argument
Option<T>
or a default value means optional, otherwise it is required.
optionality = if let Some(x) = ty_inner(&["Option"], &field.ty) {
Optionality::Optional
} else if let Some(x) = ty_inner(&["Vec"], &field.ty) {
Optionality::Repeating
} else {
Optionality::None
};
However, in my view, the problem here is that the second branch creates a rule that supersedes the first one, and it shouldn't. Vec<T>
should adhere as well to the first rule. If Option<Vec<T>>
is given, then it is optional, if Vec<T>
is given and there is no default value, it is required. And what does it mean for a Vec
to be required? As @alexonea exemplified as well, it means it must have at least one element to make sense in a command-line context.
I strongly believe that in a command-line context linking required and empty makes no sense. Therefore, accepting an empty Vec
as a required positional argument should not be an option.
It is either required and has at least one element, or it is optional but then it is marked accordingly (Option<Vec<T>>
).