I've been working on a library that would do something that's been done already, which is CLI argument parsing. What I'm trying to do differently is:
-
Strive towards zero-cost abstractions in each of the features expected from such a library. I've been using the argparse-rosetta benchmark to see where my library falls in terms of overhead and speed, and tried to measure memory use with
core::mem::size_of_value()
, but I get the feeling I'm not doin' it right (most things are stored on the stack fromconst
functions with parse results stored on the heap -
Reuse for routing URL paths. I'd like to do an experiment where a program could offer a similar API for both a CLI and a web server
I don't see "zero-cost" happening any time soon, but in building with that goal in mind I've landed on something that in some places I like better than existing libraries while other places don't feel quite right.
If this sort of thing interests you, or you'd like to pass along some recommendations, I welcome your feedback. For the slim chance that you'd want to use this, please know it's not ready for real use and I will gladly change the API if it furthers the two goals mentioned above.
Areas I feel are lacking:
Retrieving options that were passed in
After arguments are parsed and the command to execute is selected, retrieving the options that were passed in is quite verbose.
For example, a selected command gets a context that holds the options that were passed in.
enum PossibleOptions {
Help,
Number
}
context
.opt::<u32>(PossibleOptions::Number)
.unwrap()
.and_then(|args| Some(args[0]))
The opt()
method retrieves the value for an option called Number
from a list of found options. Found options are stored as OsString
s so they can be stored in a single Vec
. They're then parsed to String
and then to the type specified, i.e. ::<u32>
.
The first unwrap()
is for those two points of failure. Then, the option may have been given without also giving a value, which is why there's a second Rust Option
method (and_then()
). The reason and_then()
is used here is because options can either be a flag (no value), a single value, or multiple values.
Since opt()
has to return the same type, a Vec
is returned in all cases. For options with a single value, I pull the first item out. For "flag" options, an empty Vec
would be returned.
I wish I could write a validator function for each option, and use it in the parsing algorithm so I wouldn't need to validate it in opt()
(Keep reading on why this might not be the best idea). I also wish I could return a Vec
for found options that expect multiple values, a single value for found options that expect a single value, and a boolean
for found options that are flags.
Validation conditions are specified in multiple places
An optmap!
macro is where you define all possible options, and each one can specify the type of value it can have (or none if it is a flag).
optmap!(enum PossibleOptions using [
/// This option has a shorthand, "-f"
Format | 'f',
Help | 'h',
/// This option cannot have a value
Quiet,
/// This option can have a single `String` value
ValueArg > String,
//// This option can have multiple `String` values
MultiArg > String[],
Number > i32
]);
Currently, the constraints are only used in generated docs. The actual validation needs to be done when calling Context::opt()
.
That's prone to documentation getting out of sync. Another issue is, I'm not certain where validation should happen; even if the basic type validation or validation for whether the option is required/not required is handled by the library, what if someone needs more complex validations.. They could easily do so, but then they'd have the issue mentioned above again.