CLI11 icon indicating copy to clipboard operation
CLI11 copied to clipboard

subcommands + `configurable()` = help doesn't work as expected

Open emusgrave opened this issue 1 year ago • 3 comments

I'm trying to setup what I think would be a pretty standard configuration file using TOML and subcommands. However, as soon as I put content into the configuration file, the --help flag fails to work as expected.

Output of demo.exe --help when config file is empty (which is what I would also expect if the config file has content):

Demo app
Usage: demo.exe [OPTIONS] [SUBCOMMAND]

Options:
  -h,--help                   Print this help message and exit
  --config [config.cfg]  REQUIRED
                              Load configuration from a file

Subcommands:
  sub                         Some subcommand

Output of demo.exe --help when config file has a [sub] section:

Config file:

[sub]
sub_arg="hello world"

Output:

Some subcommand
Usage: demo.exe sub [OPTIONS] sub_arg

Positionals:
  sub_arg TEXT REQUIRED       Argument for subcommand

Options:
  -h,--help                   Print this help message and exit

Code to reproduce:

int main(int argc, char* argv[])
{
  CLI::App cli_global {"Demo app"};
  cli_global.set_config("--config", "config.cfg", "Load configuration from a file", true);

  auto&       cli_sub = *cli_global.add_subcommand("sub", "Some subcommand");
  std::string sub_arg;
  cli_sub.add_option("sub_arg", sub_arg, "Argument for subcommand")->required();
  cli_sub.configurable();

  CLI11_PARSE(cli_global, argc, argv);
  if (cli_sub)
  {
    std::cout << "Got: " << sub_arg << '\n';
  }
  return 0;
}

Additional Tests

I have also tried adding the HelpAll with cli_global.set_help_all_flag("--help-all", "Print help message including hidden options and exit"); but I get the same incorrect output.

Q:

Are my expectations incorrect or is this a bug?

emusgrave avatar Feb 05 '24 14:02 emusgrave

I don't know what is expected behaviour, but in my project I've ended up with manually written --help callback bound to all subcommands.

#include "CLI11.hpp"

void bind_help_all_command(CLI::App& app, CLI::App& cmd, 
                           std::string name = "--help",
                           std::string description = "Print help")
{
    auto callback = [&] {
        std::cout << app.get_formatter()->make_help(&app, "", CLI::AppFormatMode::All);
        throw CLI::Success();
    };
    cmd.set_help_all_flag("", "");
    cmd.add_flag_callback(std::move(name), std::move(callback), std::move(description))->configurable(false);
}

CLI::App* add_subcommand(CLI::App& app, std::string name, std::string description)
{
    auto* cmd = app.add_subcommand(std::move(name), std::move(description));
    bind_help_all_command(app, *cmd);
    cmd->configurable();
    cmd->fallthrough(true);
    return cmd;
}

int main(int argc, char* argv[])
{
  CLI::App cli_global {"Demo app"};
  cli_global.set_config("--config", "config.cfg", "Load configuration from a file", true);
  cli_global.set_help_flag("--help-group", "Print help of single group");
  bind_help_all_command(cli_global, cli_global);

  auto& cli_sub = *add_subcommand(cli_global, "sub", "Some subcommand");
  std::string sub_arg;
  cli_sub.add_option("sub_arg", sub_arg, "Argument for subcommand")->required();
  cli_sub.configurable();

  CLI11_PARSE(cli_global, argc, argv);
  if (cli_sub)
  {
    std::cout << "Got: " << sub_arg << '\n';
  }
  return 0;
}

prints

> main.exe --help
Demo app
Usage: [OPTIONS] [SUBCOMMAND]

Options:
  --config [config.cfg]  REQUIRED
                              Load configuration from a file
  --help-group                Print help of single group
  --help                      Print help

Subcommands:
sub
  Some subcommand
  Positionals:
    sub_arg TEXT REQUIRED       Argument for subcommand
  Options:
    --help                      Print help

--help is duplicated, but you can probably somehow tweak it out.

R2RT avatar Feb 15 '24 14:02 R2RT

@R2RT Thank you!!! I haven't spent the time to figure out why it works, but as I was taking your recommendation I figured out that just doing this on the root App is enough to make the help work for all subcommands:

app.set_help_flag("");
app.set_help_all_flag("", "");
auto callback = [&] {
  std::cout << app.get_formatter()->make_help(&app, "", CLI::AppFormatMode::All);
  throw CLI::Success();
};
app.add_flag_callback("-h,--help", std::move(callback), "Print help")->configurable(false);

So the working flow is:

  1. Create root app and set_config
  2. Clear the help/help-all flags and manually add the above help callback with configurable(false)
  3. Add all subcommands, each of which is configurable(true)

emusgrave avatar Feb 15 '24 18:02 emusgrave

I'm glad I could have helped :) Yeah, it seems I have overcomplicated solution, but it was first which simply clicked.

R2RT avatar Feb 16 '24 08:02 R2RT