clap icon indicating copy to clipboard operation
clap copied to clipboard

derive feature: an attribute to add a prefix to all arg names in a struct, for use with flatten

Open wfraser opened this issue 2 years ago • 9 comments

Please complete the following tasks

Clap Version

3.1.2

Describe your use case

I work on a large Rust project that has many different modules which have command-line options to add to the program. For readability and to reduce collisions, we prefix the name of the flags with the name of the module it belongs to. For example, we have a module log, with a struct with several fields, and each of them has a corresponding flag like --log.whatever. The main program then combines all these together into one big options struct.

We've been using docopt, which lets you omit the prefix from the struct field name at least, but you still have to type it manually in the help text for each flag, which doesn't scale well.

Clap lets me use #[clap(flatten)] to separate these options into their own struct like I've been doing with docopt, but I'd have to add the prefix to all the field names, which then leaks into all the code that accesses it (yuck). It also doesn't let me preserve the --prefix.name argument style we've already established.

What I'd like is some way to add a prefix to all the arguments used with a particular struct.

Describe the solution you'd like

A similar feature was discussed in https://github.com/clap-rs/clap/issues/3117, which proposed a prefix attribute to be placed alongside flatten, at the point of the sub-struct's inclusion into the main one. This was ultimately dismissed as being ugly and/or difficult to implement given how the derive code works.

This proposal is to attach the attribute to the sub-struct itself, which is much more easily implemented, and, at least for my use case, works just as well.

I have an implementation ready (https://github.com/wfraser/clap/tree/flatten-prefix) which makes it another type of casing style, using the rename_all attribute. The precise syntax is #[clap(rename_all = "prefix:foo")]. It can be combined with another prefix style as well: #[clap(rename_all = "prefix:foo,snake")].

Example:

#[derive(Parser, Debug, PartialEq)]
struct Main {
    #[clap(long)]
    some_string: String,

    #[clap(flatten)]
    foo_args: Foo,

    #[clap(flatten)]
    bar_args: Bar,
}

#[derive(Parser, Debug, PartialEq)]
#[clap(rename_all = "prefix:foo", next_help_heading = "Foo options")]
struct Foo {
    #[clap(long)]
    some_param: String,
}

#[derive(Parser, Debug, PartialEq)]
#[clap(rename_all = "prefix:bar,pascal", next_help_heading = "Bar options")]
struct Bar {
    #[clap(long)]
    another_param: String,
}

help text:

clap-nesting

USAGE:
    clap-nesting [OPTIONS]

OPTIONS:
    -h, --help                         Print help information
        --some-string <SOME_STRING>

Foo options:
        --foo.some-param <SOME_PARAM>

Bar options:
        --bar.AnotherParam <ANOTHER_PARAM>

If this approach looks acceptable, I can submit my branch as a PR.

Alternatives, if applicable

No response

Additional Context

No response

wfraser avatar Feb 26 '22 09:02 wfraser