subcommands + `configurable()` = help doesn't work as expected
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?
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 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:
- Create root app and
set_config - Clear the help/help-all flags and manually add the above help callback with configurable(false)
- Add all subcommands, each of which is configurable(true)
I'm glad I could have helped :) Yeah, it seems I have overcomplicated solution, but it was first which simply clicked.