clap-rs / clap

A full featured, fast Command Line Argument Parser for Rust
docs.rs/clap
Apache License 2.0
13.65k stars 1.02k forks source link

Visually Separate ArgGroups #5426

Closed Swivelgames closed 3 months ago

Swivelgames commented 3 months ago

Please complete the following tasks

Clap Version

4.4.7

Describe your use case

ArgGroups are very useful for developers, but they don't always show up sequentially in the help text, which can make it difficult to decipher between different options.

For instance, all + are part of the "Initializer" ArgGroup, and the - are this specific subcommand's options. The rest are global = true options:

  $ dotctl init --help
  Initializes a new directory in dotctl for a new machine

  Usage: dotctl machine init [OPTIONS] <--template <TEMPLATE>|--copy-from <COPY_FROM>|--bare>

  Options:
        --debug                  Prints extra information
+   -t, --template <TEMPLATE>    Use a template as the base for this machine
+   -c, --copy-from <COPY_FROM>  Use an existing machine config as the base for this machine
    -d, --dry-run                Prints actions, but doesn't perform them
+       --bare                   Initialize an empty machine
        --mock                   
-       --home <HOME>            Use a different destination than $HOME
-   -m, --machine <MACHINE>      Specify a different hostname
-       --force                  
    -h, --help                   Print help
    -V, --version                Print version

Describe the solution you'd like

It would be great if we could visually separate options and arguments from the rest of the list if they're grouped, to make it easier to read for users:

Again, all + are part of the "Initializer" ArgGroup, and the - are this specific subcommand's options. The rest are global = true options:

  $ dotctl init --help
  Initializes a new directory in dotctl for a new machine

  Usage: dotctl machine init [OPTIONS] <--template <TEMPLATE>|--copy-from <COPY_FROM>|--bare>

  Init Options:
-       --home <HOME>            Use a different destination than $HOME
-   -m, --machine <MACHINE>      Specify a different hostname
-       --force                  

  Initializer Options:
+   -t, --template <TEMPLATE>    Use a template as the base for this machine
+   -c, --copy-from <COPY_FROM>  Use an existing machine config as the base for this machine
+       --bare                   Initialize an empty machine config

  Global Options:
        --debug                  Prints extra information
    -d, --dry-run                Prints actions, but doesn't perform them
        --mock
    -h, --help                   Print help
    -V, --version                Print version

I'm imagining this implemented with something like #[group(distinguish = true)]:

#[derive(ClapArgs, Clone)]
#[group(required = true, multiple = false, distinguish = true)]
pub struct Args {
    /// Use a template as the base for this machine
    #[arg(short, long, group = "Initializer")]
    template: Option<String>,

    /// Use an existing machine config as the base for this machine
    #[arg(short, long, group = "Initializer")]
    copy_from: Option<String>,

    /// Initialize an empty machine
    #[arg(long, action, group = "Initializer")]
    bare: bool,

    /// Specify a different hostname
    #[arg(short, long)]
    machine: Option<String>,

    #[arg(long, action)]
    force: bool,
}
epage commented 3 months ago

Clap has this but its not expressed through groups. As someone who mostly did CLIs in Python before Rust, this surprised me about clap but it turned out to be so much better. Groups are a way of tagging arguments to define arbitrary relationships. These can't boil down to simple things like "organize help by group". Instead, clap has the concept of applying a help_heading to an argument. See the find example for what this looks like in code (builder API but concept carries over) and in the help output.

Swivelgames commented 3 months ago

@epage Oh, awesome! I totally overlooked that. :sweat_smile: Thanks! Apologies for the frivolous issue :upside_down_face: