Skip to content
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

Make Arguments composable and reuseable #21

Open
tertsdiepraam opened this issue Feb 23, 2023 · 1 comment
Open

Make Arguments composable and reuseable #21

tertsdiepraam opened this issue Feb 23, 2023 · 1 comment

Comments

@tertsdiepraam
Copy link
Member

tertsdiepraam commented Feb 23, 2023

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 { ... }
@tertsdiepraam
Copy link
Member Author

This also possibly ties in with https://github.com/tertsdiepraam/uutils-args/issues/11

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant