clap-rs / clap

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

Support for Option Value Binding or ArgMatches Traversal #5727

Open JS-Zheng opened 2 months ago

JS-Zheng commented 2 months ago

Please complete the following tasks

Clap Version

4.5.11

Describe your use case

I am developing a GNU rm-like tool and need to support options like:

I am following the GNU "last one wins" precedence rule. When two mutually exclusive options are provided (e.g., -i and -f), the last one specified on the command line should override the previous ones.

Additionally, I want to extend this behavior to support levels for interactive and force options. For example, -i -i would set the interactive level = 2, and -fff would set the force level = 3.

Clap handles the "last one wins" behavior using overrides_with_all, and I used ArgAction::Count for level counting.

.arg(
    Arg::new(ARG_FORCE)
        .short('f')
        .long(ARG_FORCE)
        .help("Ignore nonexistent files and arguments, never prompt")
        .overrides_with_all(&[ARG_PROMPT_ALWAYS, ARG_PROMPT_ONCE, ARG_INTERACTIVE])
        .action(ArgAction::Count),
)
.arg(
    Arg::new(ARG_PROMPT_ALWAYS)
        .short('i')
        .help("Prompt before every removal")
        .overrides_with_all(&[ARG_PROMPT_ONCE, ARG_FORCE])
        .action(ArgAction::Count),
)
.arg(
    Arg::new(ARG_PROMPT_ONCE)
        .short('I')
        .help("Prompt once before removing more than three files, or when removing recursively")
        .overrides_with_all(&[ARG_PROMPT_ALWAYS, ARG_FORCE])
        .action(ArgAction::Count),
)

However, implementing the long option --interactive=WHEN alongside short options like -i and -I poses challenges. Specifically:

Describe the solution you'd like

I propose two features:

  1. Support for ArgMatches Traversal
    Traversing ArgMatches would allow me to check the order in which options were provided, making it possible to implement the "last one wins" precedence rule accurately.

  2. Option Value Binding
    This would allow short options to be bound to specific values of long options. For example, -i would be treated as an alias for --interactive=always, and -I would map to --interactive=once. This would provide an elegant solution to handle the complexity of combining long and short options with the same meaning.

Example (pseudo-code):

Arg::new("interactive")
    .long("interactive")
    .value_name("WHEN")
    .value_parser(["always", "once", "never"])
    .bind_to("-i", "always") // Bind short option -i to --interactive=always
    .bind_to("-I", "once")   // Bind short option -I to --interactive=once
    .bind(Fn)                // Bind by Closure

In addition to allowing option precedence, this feature could benefit other tools that rely on complex option parsing and precedence rules. It would also make it easier to support aliasing of options and reduce manual argument handling for developers.

Alternatives, if applicable

An alternative would be to manually parse the arguments in sequence and manage conflicts ourselves, but this would result in more complex code, reduce the benefits of using Clap, and potentially introduce bugs. Supporting these features natively in Clap would be much cleaner and more reliable.

Additional Context

Related issue: #1206

epage commented 2 months ago

I propose two features:

Please keep issues focused on a single feature. It helps keep the conversation focused and makes the state clear as we have a single status (close, rejected) for an issue.

Support for ArgMatches Traversal Traversing ArgMatches would allow me to check the order in which options were provided, making it possible to implement the "last one wins" precedence rule accurately.

We support position-sensitive arguments, though it might not be the most intuitive, see https://docs.rs/clap/latest/clap/_derive/_cookbook/find/index.html

fn position_sensitive_flag(arg: Arg) -> Arg {
    // Flags don't track the position of each occurrence, so we need to emulate flags with
    // value-less options to get the same result

Huh, that shouldn't be the case anymore and we should be able to drop that part from the example

Option Value Binding This would allow short options to be bound to specific values of long options. For example, -i would be treated as an alias for --interactive=always, and -I would map to --interactive=once. This would provide an elegant solution to handle the complexity of combining long and short options with the same meaning.

We previously had Arg::replace which ran into issues, see #2836. We'll need this more fleshed out to evaluate how to move forward.