Allow custom titles for arguments group
As far as I understand, now the library automatically distributes arguments into groups in the --help output like this:
Positional arguments:
somearg do something
Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
-i, --input-file input file path
-o, --output-file output file path
--other Other important parameter
However, I (with another library at the moment, but this one seems to me better in almost everything) describe the parameters for the user by custom groups, something like this:
Basic options:
-h, --help shows help message and exits
-v, --version prints version information and exits
File options
-i, --input-file input file path
-o, --output-file output file path
Another purpose options:
--other Other important parameter
Positional arguments:
somearg do something
I would be grateful if something like this was added to this library.
P.S. I know, that I can define argparse::default_arguments::none and manually rewrite help message, but I'm looking for a more scalable and convenient solution
If we were to add formatters, IMHO it would take almost as much code to make a custom formatter as to make custom help output.
Because Argument objects handle their own help output, you can order them however you prefer. With a std::map to hold groups of Arguments, there is little overhead.
#include <argparse/argparse.hpp>
#include <iostream>
#include <map>
#include <string>
#include <vector>
using OptionGroup = std::vector<argparse::Argument>;
using GroupsMap = std::map<std::string, OptionGroup>;
using KeyOGPair = std::pair<std::string, OptionGroup>;
void print_grouped_help(const GroupsMap &groups) {
for ( auto &group : groups ) {
std::cout << group.first << ':' << std::endl;
for ( auto &arg : group.second ) {
std::cout << arg;
}
std::cout << std::endl;
}
}
int main(int argc, char *argv[]) {
GroupsMap help_groups {
KeyOGPair{"Basic Options", {}},
KeyOGPair{"File Options", {}}
};
argparse::ArgumentParser program("test", "1.0",
argparse::default_arguments::none);
help_groups.at("Basic Options").push_back(
program.add_argument("-h", "--help")
.help("show help")
.nargs(0));
help_groups.at("Basic Options").push_back(
program.add_argument("-v", "--version")
.help("show version")
.nargs(0));
help_groups.at("File Options").push_back(
program.add_argument("-i", "--input-file")
.help("read IN file")
.metavar("IN"));
help_groups.at("File Options").push_back(
program.add_argument("-o", "--output-file")
.help("write to OUT file")
.metavar("OUT"));
program.parse_args(argc, argv);
if ( program.is_used("--help") ) {
std::cout << program.usage() << std::endl;
print_grouped_help(help_groups);
exit(1);
}
return 0;
}
If we were to add formatters, IMHO it would take almost as much code to make a custom formatter as to make custom help output.
Because
Argumentobjects handle their own help output, you can order them however you prefer. With astd::mapto hold groups ofArguments, there is little overhead.
Thanks for help, I'll try your suggestion
Because
Argumentobjects handle their own help output, you can order them however you prefer. With astd::mapto hold groups ofArguments, there is little overhead
How to add a positional argument to a group? When trying to do this, it throws an error
@skrobinson
@Theodikes Thank you for the bump, I missed your reply in the recent flood of Issues.
I'll assume the error you see is something like
terminate called after throwing an instance of 'std::runtime_error'
what(): in_file: 1 argument(s) expected. 0 provided.
Aborted
For my example, I left out the try...catch block recommended to surround program.parse_args(argc, argv);. You should catch std::runtime_error and output an appropriate user message. Something like
try {
program.parse_args(argc, argv);
} catch (const std::runtime_error& err) {
std::cout << program.usage() << std::endl;
print_grouped_help(help_groups);
std::cerr << err.what() << std::endl;
std::exit(1);
}
@Theodikes Thank you for the bump, I missed your reply in the recent flood of Issues.
I'll assume the error you see is something like
terminate called after throwing an instance of 'std::runtime_error' what(): in_file: 1 argument(s) expected. 0 provided. AbortedFor my example, I left out the
try...catchblock recommended to surroundprogram.parse_args(argc, argv);. You should catchstd::runtime_errorand output an appropriate user message. Something liketry { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { std::cout << program.usage() << std::endl; print_grouped_help(help_groups); std::cerr << err.what() << std::endl; std::exit(1); }
@skrobinson, thank you for help, it worked well. But how can I combine it with subcommands? My program uses subcommands with custom argument group titles, for example:

And is it possible to make a "abbreviated" command name, like with an argument? For example, fullname of argument - --input, shortname - -i, same way for subcommand: fullname - deduplicate, shortname d

And is it possible to make a "abbreviated" command name, like with an argument? For example, fullname of argument -
--input, shortname --i, same way for subcommand: fullname -deduplicate, shortnamed
@skrobinson
The pattern I showed for Argument groups can be used for subparsers, as well. There will be some differences in the needed syntax, but the general idea will transfer.
For command abbreviations, there is no built-in support for aliases like there is for Argument. I have not thought through all the corner cases, but I would handle this with a mapping of abbreviations to commands and pre-process the command line.
For command abbreviations, there is no built-in support for aliases like there is for
Argument.
Does it make sense to implement this and send you a pull request, or you do not plan to add this to code?
I'm neutral on the concept. What about you, @p-ranav?
I'm neutral on the idea as well. Feel free to create a PR adding aliases to subcommands. It would not be in my typical use cases but if it is for someone else (like yourself), I have no particular concerns supporting it.
If you do create a PR, please add test cases and update the README.
For the record, I wanted to have argument groups and ended up using the solution above with in place creation of the vector instead of multiple push_backs. But it would have been more handy if it was built-in :)
GroupsMap help_groups{
KeyOGPair{"General", {}},
KeyOGPair{"Simulation parameters", {}},
KeyOGPair{"Environment Hyperparameters", {}},
};
program.add_description("Yet Another Artificial Life Program in cpp");
help_groups["Environment Hyperparameters"] = {
program.add_argument("-H", "--height").help("Height of the map").default_value(1000).scan<'i', int>(),
program.add_argument("-W", "--width").help("Width of the map").default_value(1000).scan<'i', int>(),
program.add_argument("-C", "--channels").help("Number of channels in the map").default_value(
5).scan<'i', int>(),
...
};
help_groups["General"] = {
program.add_argument("-h", "--help").help("Print this help").nargs(0),
};
help_groups["Simulation parameters"] = {
program.add_argument("-n", "--num-yaals").help(
"Number of yaals at the start of the simulation").default_value(100).scan<'i', int>(),
program.add_argument("-t", "--timesteps").help("Number of timesteps to simulate").default_value(
10000).scan<'i', int>(),
};
This gives:
➜ ./cmake-build-debug/yaalpp --help
Usage: yaalpp [--help] [--version] [--height VAR] [--width VAR] [--channels VAR] [--decay-factors VAR...] [--diffusion-rate VAR...] [--max-values VAR...] [--help] [--num-yaals VAR] [--timesteps VAR]
Environment Hyperparameters:
-H, --height Height of the map [nargs=0..1] [default: 1000]
-W, --width Width of the map [nargs=0..1] [default: 1000]
-C, --channels Number of channels in the map [nargs=0..1] [default: 5]
-D, --decay-factors Decay factors for each channel [nargs: 0 or more] [default: {0 0 0 0.9 0.5}]
-d, --diffusion-rate Diffusion rate for channel [nargs: 0 or more] [default: {0 0 0 0.1 0.9}]
-m, --max-values Max values for each channel [nargs: 0 or more] [default: {1 1 1 5 5}]
General:
-h, --help Print this help
Simulation parameters:
-n, --num-yaals Number of yaals at the start of the simulation [nargs=0..1] [default: 100]
-t, --timesteps Number of timesteps to simulate [nargs=0..1] [default: 10000]
and
✗ ./cmake-build-debug/yaalpp --foo
Usage: yaalpp [--help] [--version] [--height VAR] [--width VAR] [--channels VAR] [--decay-factors VAR...] [--diffusion-rate VAR...] [--max-values VAR...] [--help] [--num-yaals VAR] [--timesteps VAR]
Environment Hyperparameters:
-H, --height Height of the map [nargs=0..1] [default: 1000]
-W, --width Width of the map [nargs=0..1] [default: 1000]
-C, --channels Number of channels in the map [nargs=0..1] [default: 5]
-D, --decay-factors Decay factors for each channel [nargs: 0 or more] [default: {0 0 0 0.9 0.5}]
-d, --diffusion-rate Diffusion rate for channel [nargs: 0 or more] [default: {0 0 0 0.1 0.9}]
-m, --max-values Max values for each channel [nargs: 0 or more] [default: {1 1 1 5 5}]
General:
-h, --help Print this help
Simulation parameters:
-n, --num-yaals Number of yaals at the start of the simulation [nargs=0..1] [default: 100]
-t, --timesteps Number of timesteps to simulate [nargs=0..1] [default: 10000]
Unknown argument: --foo