Many of the coreutils share arguments, possibly with some slight variations. Some examples:
- head & tail
- cksum, sum, b2sum, sha1sum, etc.
- base32, base64, basenc
- mv, cp
We could copy-paste the Arguments enums for each of these, but I think we can do better.
Essentially, it must be possible to compose multiple Arguments enums. Let's first establish what's currently possible. Given two enums Arg1 and Arg2, we could make a new enum Arg for which the Arg::next_arg calls Arg1::next_arg and Arg2::next_arg in order. That works somewhat but has a problem with abbreviations of long options, because Arg1::next_arg and Arg2::next_arg do not know about each other's long arguments. They also don't know about each other's positional indices.
So we have to break up the trait into multiple methods. One for each of the following operations, which can be composed:
- Parse short option.
- Get an iterator of long options.
- Parse long option.
- Get the maximum number of positional arguments.
- Parse positional argument.
- Get the help text per argument (so we can sort it)
- (And parsing free arguments once those are implemented)
The next_arg method can then be provided based on these, possibly only implemented by ArgumentsIter.
That's all the internal plumbing that needs to change, but what does it look like in the API?
One option is to provide a macro that creates an automatic implementation:
enum Arg1 { ... }
enum Arg2 { ... }
compose_args!(Arg, [Arg1, Arg2])
That works but I think I prefer supporting it via the derive macro instead, which makes it easier how it's structured:
#[derive(Arguments)]
enum Arg {
#[include] A(Arg1),
#[include] B(Arg2),
#[arg("-a")]
All,
}
This also provides a nice way of grouping options in --help. A variation on this design makes a distinction between an ArgumentGroup and Arguments, where the former can be included into the latter. This makes sense, because Arguments have, for instance, about text and version info, whereas ArgumentGroup doesn't need that. Additionally, we can restrict ArgumentGroup to not include positional arguments, because those get hard to reason about (and hard to implement in proc macros).
Taking this further can lead to some pretty cool stuff, by combining ArgumentGroup with Value. For example, we can do this:
#[derive(ArgumentGroup, Value)]
enum Format {
#[arg("--long")]
#[value("long")]
Long,
#[value("columns")]
Columns,
}
#[derive(Arguments)]
enum Arg {
#[include]
#[arg("--format=FORMAT")]
Format(Format),
}
We could also allow the --format argument to be defined like this:
#[derive(ArgumentGroup, Value)]
#[option("--format=FORMAT")]
enum Format { ... }
Many of the coreutils share arguments, possibly with some slight variations. Some examples:
We could copy-paste the
Argumentsenums for each of these, but I think we can do better.Essentially, it must be possible to compose multiple
Argumentsenums. Let's first establish what's currently possible. Given two enumsArg1andArg2, we could make a new enumArgfor which theArg::next_argcallsArg1::next_argandArg2::next_argin order. That works somewhat but has a problem with abbreviations of long options, becauseArg1::next_argandArg2::next_argdo not know about each other's long arguments. They also don't know about each other's positional indices.So we have to break up the trait into multiple methods. One for each of the following operations, which can be composed:
The
next_argmethod can then be provided based on these, possibly only implemented byArgumentsIter.That's all the internal plumbing that needs to change, but what does it look like in the API?
One option is to provide a macro that creates an automatic implementation:
That works but I think I prefer supporting it via the derive macro instead, which makes it easier how it's structured:
This also provides a nice way of grouping options in
--help. A variation on this design makes a distinction between anArgumentGroupandArguments, where the former can be included into the latter. This makes sense, becauseArgumentshave, for instance, about text and version info, whereasArgumentGroupdoesn't need that. Additionally, we can restrictArgumentGroupto not include positional arguments, because those get hard to reason about (and hard to implement in proc macros).Taking this further can lead to some pretty cool stuff, by combining
ArgumentGroupwithValue. For example, we can do this:We could also allow the
--formatargument to be defined like this: