pacak / bpaf

Command line parser with applicative interface
Apache License 2.0
348 stars 17 forks source link

A way to prevent parsing of options after positionals #359

Open teohhanhui opened 5 months ago

teohhanhui commented 5 months ago

Perhaps related to #302, I'd like to be able to prevent parsing of options after positionals.

Given:

Usage: krun [--net=NET_MODE] [--passt-socket=PATH] COMMAND [COMMAND_ARGS]...

Available positional items:
    COMMAND                  the command you want to execute in the vm
    COMMAND_ARGS             arguments of COMMAND

Available options:
        --net=NET_MODE       Set network mode        NET_MODE can be either TSI (default) or PASST
                             [default: TSI]
        --passt-socket=PATH  Instead of starting passt, connect to passt socket at PATH
    -h, --help               Prints help information

If I do:

krun --net=PASST box64 --help

It should not be parsed as the --help option.

Sure, it's possible to just add -- but that's not always user-friendly.

pacak commented 5 months ago

So I can better understand the problem - what's the motivation for that?

teohhanhui commented 5 months ago

As illustrated above, it's to pass all COMMAND_ARGS to COMMAND. So basically like any, but without this "problem":

“before” in the previous line means in the parser definition, not on the user input, here --turbo gets consumed by turbo parser even the argument goes

https://docs.rs/bpaf/latest/bpaf/fn.any.html#use-any-to-capture-the-remaining-arguments

pacak commented 5 months ago

As illustrated above, it's to pass all COMMAND_ARGS to COMMAND.

I see. That's a bit tricky, my usual approach is to have different flags between inner and outer commands. To apply the restriction you want parser needs to run on arguments in order they are given on a command line, at the moment it runs in order they are specified in your parser. I need to think about an efficient way to allow this, might take some time.

teohhanhui commented 5 months ago

my usual approach is to have different flags between inner and outer commands

COMMAND in this case is just any external program. So we don't know / don't care what the arguments are.

teohhanhui commented 5 months ago

I have identified another (perhaps related) issue with any. I can't seem to get it to behave similarly like positional, while still consuming anything that comes after it.

To illustrate:

Given (the same program as before, but updated):

$ krun --help
Usage: krun [-e=ENV]... [--net=NET_MODE] [--passt-socket=PATH] COMMAND [COMMAND_ARGS]...

Available positional items:
    COMMAND                  the command you want to execute in the vm
    COMMAND_ARGS             arguments of COMMAND

Available options:
    -e, --env=ENV            Set environment variable to be passed to the microVM
                                     ENV should be in KEY=VALUE format, or KEY on its own to inherit the
                                     current value from the local environment
        --net=NET_MODE       Set network mode        NET_MODE can be either TSI (default) or PASST
                             [default: TSI]
        --passt-socket=PATH  Instead of starting passt, connect to passt socket at PATH
    -h, --help               Prints help information

If I do:

$ krun --net=PASST --net=PASST -- box64 --version

The second unconsumed --net=PASST ends up being consumed by any (COMMAND_ARGS). There should be a way to consume anything only from the current position, i.e. after COMMAND.

The code is here for your reference: https://github.com/teohhanhui/krun/blob/283a1f716f8c2078e9fbe6a4f2174bcba38b559e/crates/krun/src/cli_options.rs

pacak commented 5 months ago

I got some ideas how to implement it and for now I'm done messing with cargo-show-asm. I'll try to make something that can solve your problem soon-ish.

pacak commented 3 months ago

I pushed a right-adj branch that implements start_adjacent() method on parser trait. The idea is that the whole annotated block must be either directly adjacent to the beginning or to fully parsed block, so if you make it so xxx parses --net=PASST, but not box64 - parser like listed above will parse krun --net=PASST box64 --help by capturing "PASST" into xxx and ["box64", "--help"] into tail. At least for as long as xxx succeeds.

let option_parsers = xxx.start_adjacent();
let tail = any::<OsString, _, _>("TAIL", Some).many();
let parser = construct!(option_parsers, tail)

I'm planning to release a new version with it in a few days after covering some of other remaining issues.

teohhanhui commented 3 months ago

This is addressing the original issue, right? But what about the other issue I found? :see_no_evil:

pacak commented 3 months ago

I'm looking into fixing it as well.

On Tue, Jul 2, 2024, 16:33 Teoh Han Hui @.***> wrote:

This is addressing the original issue, right? But what about the other issue I found? 🙈

— Reply to this email directly, view it on GitHub https://github.com/pacak/bpaf/issues/359#issuecomment-2204362718, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAQFI3PKKCKNBE72SYLC7TZKMFDNAVCNFSM6AAAAABG4OC75KVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMBUGM3DENZRHA . You are receiving this because you commented.Message ID: @.***>