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

Is there a way to create a default subparser? #390

Open
garrett-is-a-swann opened this issue Dec 24, 2024 · 2 comments
Open

Is there a way to create a default subparser? #390

garrett-is-a-swann opened this issue Dec 24, 2024 · 2 comments

Comments

@garrett-is-a-swann
Copy link

I'd like the ability to use one of my subparsers as the default one if no subparser is specified. It is somewhat possible to do as:

    program.add_subparser(subparser1);
    program.add_subparser(subparser2);
    program.add_subparser(...);

    try {
        program.parse_args(argc, argv);
    } catch (const std::exception& err) {
        std::cout << err.what() << std::endl;
        // ignore error
    }

    // Test all subparsers...
    if (program.is_subcommand_used(subparser1)) {
      // ...
    } else if (program.is_subcommand_used(subparser2)) {
      // ...
    } else if (program.is_subcommand_used(...)) {
      // ...
    } else {
      // Parse again with default subparser...
      subparser1.parse_args(argc, argv);                                                                                                                                  
    }

But if your subparser is not specified, you're guaranteed an error on the initial parse:
Failed to parse '...', did you mean 'subparser1'

This half measure also falls a part if you have arguments on the parent parser that you want set as well.

Is there another way to do this currently?

@garrett-is-a-swann
Copy link
Author

I think this is actually what I want...

#include <iostream>

#include <argparse/argparse.hpp>

int main(int argc, const char** argv) {
    argparse::ArgumentParser program("compiler");
    program.add_description("test please ignore");

    argparse::ArgumentParser sub_command("compile");

    sub_command.add_argument("-o").default_value(std::string("a.out")).nargs(1);

    program.add_subparser(sub_command);

    program.add_argument("--test").default_value(std::string{"test"});

    try {
        auto unknown_args = program.parse_known_args(argc, argv);
        if (unknown_args.size()) {
            sub_command.parse_args(unknown_args);
        }
    } catch (const std::exception& err) {
        std::cerr << err.what() << std::endl;
        std::cerr << program;
        std::exit(1);
    }

    std::cout << program.get<std::string>("--test") << std::endl;
    if (program.is_subcommand_used(sub_command)) {
        std::cout << sub_command.get<std::string>("-o") << std::endl;
    }
}

but there seems to be a bug with the functionality? If you specify compiler -o ggg, program.parse_known_args(...) returns {"-o", "ggg"} as expected, but sub_command fails to parse the remaining arguments for some reason, making it here:

2360                  for (const auto &opt : m_optional_arguments) {
2361                    if (!opt.m_implicit_value.has_value()) {
2362                      // not a flag, requires a value
2363                      if (!opt.m_is_used) {
2364                        throw std::runtime_error(
2365                            "Zero positional arguments expected, did you mean " +
2366                            opt.get_usage_full());
2367                      }
2368                    }
2369                  }
Zero positional arguments expected, did you mean -o VAR
Usage: compiler [--help] [--version] [--test VAR] {compile}

test please ignore

Optional arguments:
  -h, --help     shows help message and exits
  -v, --version  prints version information and exits
  --test         [nargs=0..1] [default: "test"]

Subcommands:
  compile

Oddly enough, as shown above, it also shows the program help instead of the sub_parser help, which is what failed the parse?

@garrett-is-a-swann
Copy link
Author

Ohh, it's because it skips the first argument... Passing a dummy value (the subparser's name) seems to fix this. I also need to modify the subparser-usage check to see if the subparser was used via the super-parser, or directly (using ArgumentParser::operator bool):

#include <iostream>

#include <argparse/argparse.hpp>

int main(int argc, const char** argv) {
    argparse::ArgumentParser program("compiler");
    program.add_description("test please ignore");

    argparse::ArgumentParser sub_command("compile");

    sub_command.add_argument("-o").default_value(std::string("a.out")).nargs(1);

    program.add_subparser(sub_command);

    program.add_argument("--test").default_value(std::string{"test"});

    try {
        auto unknown_args = program.parse_known_args(argc, argv);
        if (unknown_args.size()) {
            std::vector<std::string> compiler_args{"compile"};
            compiler_args.reserve(unknown_args.size() + 1);
            compiler_args.insert(
                compiler_args.end(), unknown_args.begin(), unknown_args.end()
            );
            sub_command.parse_args(compiler_args);
        }
    } catch (const std::exception& err) {
        std::cerr << err.what() << std::endl;
        std::cerr << program;
        std::exit(1);
    }

    std::cout << program.get<std::string>("--test") << std::endl;
    if (program.is_subcommand_used(sub_command) || sub_command) {
        std::cout << sub_command.get<std::string>("-o") << std::endl;
    }
}

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

1 participant