Clap: Partial / Pre Parsing a CLI

Created on 29 Apr 2020  路  9Comments  路  Source: clap-rs/clap

Make sure you completed the following tasks

  • [x] Searched the discussions
  • [x] Searched the closed issues

Describe your use case

A way to declare a subset of arguments to be parsed, then return from parsing early. With the expectation that parsing will be called again, and run to full completion.

This most often comes up when you want some argument to handle some aspect of the CLI itself (such as --color flags to control error messages during parsing, or in the above --arg-file which needs to determine a file to load prior to parsing the rest of the CLI).

Describe the solution you'd like

From a user stand point, nothing changes. The run the CLI and things just work. From the a clap consumer stand point, it essentially allows you to pull out some values from the CLI (ignoring everything else), change something about the CLI and continue parsing.

// .. already have some App struct
let pre_matches = app.get_partial_matches("color"); // Only parse the `--color` option, ignore all else
app = if let Some("never") = pre_matches.value_of("color") {
    app.setting(AppSettings::ColorNever)
} else {
    app
};

let matches = app.get_matches();

Maybe it's biting off more than we want to chew for now, but this issue seems to come up time and time again where we want to parse some particular subset of arguments, and use those values to inform how the CLI itself behaves.

Alternatives, if applicable

You can already reparse your CLI twice.

let matches = app.get_matches();
app = if let Some("never") = matches.value_of("color") {
    app.setting(AppSettings::ColorNever)
} else {
    app
};

let matches = app.get_matches();

But this doesn't help if there are errors in the initial parse, or options included included in the initial parse that the initial app isn't aware of. For example, an iptables style CLI where -m <foo> enables many other arguments and options.

Additional context

This could potentially be used in #1693 #1089 #1232 #568

parsing medium nice to have RFC / question new feature 3.x

All 9 comments

This feature would also be immensely useful for implementing #380, localizing a CLI interface.

You can already reparse your CLI twice.

This is not _quite_ true. You already mentioned some exceptions such as in the case of parse errors, but there is one more road block that makes that technique unworkable. A number of things happen in get_matches() that have the potential to short circuit the rest of the application life-cycle. For example having a help subcommand. If this is detected it immediately outputs stuff to the terminal and quits. This means you don't get a chance to reliably parse the CLI a second time.

This makes the technique useless for localization because by the time we get information about a potential language flag the App has already spit out strings that may have needed localization.

As a minimal viable solution, we could simply allow ignoring unknown or missing positionals and options.

// first "probe" app
let preprocess = App::new("app")
    .settings(IgnoreErrors) // also disables --help, version, and others
    .arg("--color <color> 'coloring policy: never/always'")
    .get_matches(); // will succeed no matter what

let mut app = App::new("main_app");

match preprocess.value_of("color") {
    Some("always") => app = app.setting(ColorAlways),
    Some("never") => app = app.setting(ColorNever),
    _ => {}
}

// real parsing starts here
app.get_matches()

This kind or partial parsing would allow for most, if not all, cases and would be fairly easy to implement.

Yes that does indeed sound like a MVS, and something along the lines of what I expected to be able to hack together already and failed. It's not the most elegant solution maybe, but it would get me by for my use case of localization.

@alerque It sounds like get_matches_safe could be what you're looking for? That would address the immediate problem of clap prematurely exiting with help or an error.

@wabain But it wouldn't give you the partial mach, see the color example above

I am building an app which spawns another process, and needs to "sniff" if certain flags were passed to the subprocess. Having an AppSettings::IgnoreErrors would be invaluable, since I only want to match some specific args, and leave the rest alone.

Right now I believe this is impossible with clap, and I have to parse the args manually.


For some more detail, my app is used like this:

app <args sent to subprocess>

The subprocess supports _hundreds_ of arguments, and I don't want to have to write code to match _all_ of them if I'm just interested in 2 or 3 flags.

The get_matches_safe doesn't work here for me though, since that bails out and completely fails.

Let me have some examples because my English is poor and I might miss points.

The original problem is "lax position of arguments."

# Good.  `--project` option belongs to `Arg("gcloud")` and `--zone` to `Arg("ssh")`.
$ gcloud --project=hoge compute ssh --zone=asia-northeast1-a instance-name

# Good.  `--project` option can descend to subcommands.
$ gcloud compute --project=hoge ssh --zone=asia-northeast1-a instance-name

# Also good.
$ gcloud compute ssh --project=hoge --zone=asia-northeast1-a instance-name

# Bad.  `--zone` option belongs to `ssh` subcommand.
$ gcloud --project=hoge compute --zone=asia-northeast1-a ssh instance-name

I think this is solved by adding option to Arg. No tricks.
There's possibility the implementation of parsing become little complicated, but developer using clap can use intuitively.
This is also glad with the viewpoint of https://github.com/clap-rs/clap/issues/1232 . Tricks like alternative App and get_matches_safe make the systematic completion harder.

This approach will be match with the @acheronfail 's case. I guess there're two types of app:

time othercommand ...
cargo run -- othercommand-or-options ...

The former is very limited. time do not have subcommands because subcommands conflict with othercommand. It can have only root App and parse will terminate with othercommand. Hence the above approach has no ambiguity.

The later is similar. Parsing will terminate with first "--".

@kenoss Thanks for looking into this, we appreciate that. I didn't quite get your comment, so let's try to get on the same page first.

The original problem is "lax position of arguments."

Could you please elaborate? I think this is where the misunderstanding comes from. The original problem in my understanding is to "loosely parse flags, options, and subcommands but ignore positional arguments entirely". The -- thing is another topic; I'm not sure why you brought it up.

Good. --project option belongs to Arg("gcloud") and --zone to Arg("ssh").

You have introduced "belongs to" relationship between two arguments. I can't wrap my head around it - arguments can't belong to each other. An argument can "belong" to a subcommand or the top-level app.

Maybe you meant App?


The complexity of implementation... Guys, I think we've all been missing the elephant in the room. The current parsing algorithm can't just "ignore" arguments, _especially positional_ ones. Well, It can _ignore_, but it can't _recover_. I think this issue belongs to "postponed until after I finally rewrite the parser algorithm" category.

Sorry for late reply.

The original problem in my understanding is to "loosely parse flags, options, and subcommands but ignore positional arguments entirely".

I read the issue again and I've got the point. Thanks to catch me up the argument.

The -- thing is another topic; I'm not sure why you brought it up.

Sorry. I misunderstood by get_matches_safe and app <args sent to subprocess>.

Maybe you meant App?

Yes.

I've found that this is a critical blocker of https://github.com/clap-rs/clap/issues/1232 and I should parsing implementation.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

casey picture casey  路  25Comments

smklein picture smklein  路  35Comments

pickfire picture pickfire  路  21Comments

casey picture casey  路  25Comments

matchai picture matchai  路  19Comments