Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accepting any arguments after a certain option #121

Closed
mark-sed opened this issue Apr 3, 2024 · 3 comments
Closed

Accepting any arguments after a certain option #121

mark-sed opened this issue Apr 3, 2024 · 3 comments

Comments

@mark-sed
Copy link

mark-sed commented Apr 3, 2024

Hello, is it possible to create a parser, which will accept (not parse) argument after a specific argument is parsed?
Consider for example the python interpreter, which after input file does not parse any arguments as those are not for the interpreter, but for the program it will run:

python3 -b main.py -h

In this case -b will be used by python3, but help won't be printed (it appears after input file).

I thought about running the ParseCLI twice, first with all the arguments and then somehow getting the position of the argument after which I don't want to parse. And then running it second time with modified argc based on the first run, but I did not find a way to get the position of this argument in argv.

So is there a way to have such argument pattern or a way to get the position of some argument?

@Taywee
Copy link
Owner

Taywee commented Apr 4, 2024

There's the KickOut attribute on Base: https://taywee.github.io/args/classargs_1_1Base.html

You can set this to true on any argument that you want to terminate parsing on, and then use the iterator APIs for the rest.

As an example of this:

#include <iostream>
#include <args.hxx>
int main(int argc, char **argv)
{
    args::ArgumentParser parser("This is a test program.", "This goes after the options.");
    args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"});
    args::Positional<std::string> filename(parser, "filename", "The file name");
    filename.KickOut(true);
    parser.Prog(argv[0]);
    const std::vector<std::string> args(argv + 1, argv + argc);
    const auto begin = std::begin(args);
    const auto end = std::end(args);
    auto rest = begin;
    try
    {
        rest = parser.ParseArgs(begin, end);
    }
    catch (const args::Completion& e)
    {
        std::cout << e.what();
        return 0;
    }
    catch (const args::Help&)
    {
        std::cout << parser;
        return 0;
    }
    catch (const args::ParseError& e)
    {
        std::cerr << e.what() << std::endl;
        std::cerr << parser;
        return 1;
    }
    std::cout << "Filename: " << *filename << '\n';

    for (;rest != end; ++rest) {
        std::cout << "Arg: " << *rest << '\n';
    }

    return 0;
}

And use of it:

> g++ -osample sample.cxx -I.

> ./sample -h main.py
  ./sample {OPTIONS} [filename]

    This is a test program.

  OPTIONS:

      -h, --help                        Display this help menu
      filename                          The file name
      "--" can be used to terminate flag options and force all following
      arguments to be treated as positional options

    This goes after the options.

> ./sample main.py -h
Filename: main.py
Arg: -h

>

You could then use another parser on the rest (which is how subparsers and commands work), or pass it into an internal program, or whatever you want. rest to end are your iterators in that case.

@Taywee Taywee closed this as completed Apr 4, 2024
@mark-sed
Copy link
Author

mark-sed commented Apr 4, 2024

Hello, thank you so much for the quick answer and amazing example. It all works just as I wanted.

@Taywee
Copy link
Owner

Taywee commented Apr 4, 2024

Gladly! Another option that will work similarly is to gather the rest in a PositionalList<std::string> rest argument at the end, and enforce that all calls use -- between the arguments. I find this way slightly less surprising, because the -- clearly signals separation between the internal and external arguments, but plenty of tools (particularly most language interpreters and docker and podman) just use a positional to implicitly separate. It's just a matter of personal taste and style.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants