When you have an application that accepts command line arguments, you'll want to be able to show a help message to the user, indicating all the possible command line arguments.
Creating this kind of usage help text is tedious, and you must make sure it is kept up to date whenever you change the arguments to your application. Ookii.CommandLine generates this usage help text automatically, alleviating this problem.
Usage help can be generated using the command_line_parser::write_usage() method. The output can be
customized using the usage_writer class. Various overloads of the command_line_parser::parse()
method that take a parameter of type usage_writer also automatically print usage help if an
error occurred while parsing.
By default, the usage_writer class will use an ookii::line_wrapping_ostream
for the standard output, white-space wrapping the generated output at the console line width.
The following example shows the usage help generated for the parser sample included with the Ookii.CommandLine library:
Sample command line application. The application parses the command line and prints the results, but
otherwise does nothing and none of the arguments are actually used for anything.
Usage: parser [-Source] <string> [-Destination] <string> [[-OperationIndex] <int>] [-Count <number>]
[-Help] [-Value <string>...] [-Verbose] [-Version]
-Source <string>
The source data.
-Destination <string>
The destination data.
-OperationIndex <int>
The operation's index. Default value: 1.
-Count <number>
Provides the count for something to the application.
-Help [<bool>] (-?, -h)
Displays this help message.
-Value <string>
This is an example of a multi-value argument, which can be repeated multiple times to set
more than one value.
-Verbose [<bool>] (-v)
Print verbose information; this is an example of a switch argument.
-Version [<bool>]
Displays version information.
The usage help consists of three components: the application description, the argument usage syntax, and the argument descriptions.
The first part of the usage help is a description of your application. This is a short description that explains what your application does and how it can be used. It can be any text you like, though it’s recommended to keep it short.
The description is specified by using the parser_builder::description() method, as in the
following example:
auto parser = ookii::parser_builder{argv[0]}
.description("This is the application description that is included in the usage help.")
.add_argument(/* ... */);If the description is not specified, no description is included in the usage help.
If you are using subcommands, this first line is the command description, which
is specified when calling the command_manager::add_command method.
The argument syntax shows the arguments and their types, telling the user in short how your application can be invoked from the command line. The argument syntax typically starts with the name of the application executable, and is followed by all the arguments, indicating their name and type. The syntax shows which arguments are required or optional, positional, and whether they allow multiple values.
The order of the arguments in the usage syntax is as follows:
- The positional arguments, in their defined order.
- Required non-positional arguments, in alphabetical order.
- The remaining arguments, in alphabetical order.
The syntax for a single argument has the following default format:
-
For a required, non-positional argument:
-ArgumentName <ArgumentType> -
For an optional, non-positional argument:
[-ArgumentName <ArgumentType>] -
For a required, positional argument:
[-ArgumentName] <ArgumentType> -
For an optional, positional argument:
[[-ArgumentName] <ArgumentType>] -
For a switch argument:
[-ArgumentName] -
For a multi-value argument (which can be combined with the other formatting options):
-ArgumentName <ArgumentType>...
Essentially, anything that's optional is enclosed in square brackets, switch arguments have their
value description (the argument type) omitted, and multi-value arguments are followed by an ellipsis.
The exact formatting can be customized using the ookii::usage_writer class.
If your application has a lot of arguments, the usage syntax may become very long, and therefore
hard to read. Set the usage_writer::use_abbreviated_syntax field to omit all but the positional
arguments; the user can instead use the argument description list to see what arguments are
available.
If you are using long/short mode, you can set the
usage_writer::use_short_names_for_syntax field to use short arguments names instead of long
names, for arguments that have a short name, in the usage syntax.
Arguments are followed by a short description of the type of value they support, in angle brackets. This is called the value description. It's a short, typically one-word description of the kind of value the argument expects. It should not be used for the longer description of the argument's purpose.
The value description defaults to the type of the argument (e.g. int or string), stripping off
any namespace prefixes. For multi-value arguments or arguments using std::optional<T>, the name
of the underlying type is used.
To specify a different value description for a particular argument, use the
parser_builder::argument_builder_common::value_description() method:
int argument;
auto parser = ookii::parser_builder{argv[0]}
.add_argument(argument, "Argument").value_description("number")
.build();This will cause the argument's syntax to show -Argument <number> instead of -Argument <int>.
You can also provide a custom default value description for a particular type by specializing
the ookii::value_description template:
template<>
struct ookii::value_description<int>
{
static std::string get()
{
return "number";
}
};Now, all arguments of type int will use "number" as the value description.
Usually, you should provide a custom value description for any template type, because their type
name will not look very good. Ookii.CommandLine already provides a value_description
specialization for std::basic_string, so the value description looks like "string" and not like
"basic_string<char, std::char_traits<char>, std::allocator<char> >"
Switch arguments don't have a value description in the argument syntax, though they do in the argument description list.
After the usage syntax, the usage help will write a list of all arguments with their detailed descriptions.
An argument's description can be specified using the
parser_builder::argument_builder_common::description() method:
int arg;
auto parser = ookii::parser_builder{argv[0]}
.add_argument(arg, "Arg").description("Provides a value to the application.")
.build();By default, the list of argument descriptions will include any argument aliases, and their default
values if set. This, along with the format of the descriptions, can be customized using the
usage_writer class.
You can choose which arguments are included in the description list using the
usage_writer::argument_description_list_filter field. By default, this is set to
description_list_filter_mode::information, which means that any argument that has any information
that isn't part of the usage syntax will be included. This could be a description, aliases, or a
default value. You can choose to include only arguments with descriptions (this was the default
behavior before version 2.0), all arguments, or to omit the description list entirely.
You can also choose the sort order of the description list using the
usage_writer::argument_description_list_order field. This defaults to the same order as the
usage syntax, but you can also choose to sort by ascending or descending long or short name.
Since the parse() method overloads that take a usage_writer will show usage
help on error, if you have a lot of arguments it may be necessary for the user to scroll up past the
argument description list to see the error message to determine what was wrong with the command
line. Since this may be inconvenient, you can choose to omit the argument description list, or the
usage help entirely, when an error occurs, using the parser_builder::show_usage_on_error()
method. In this case, the user will have to use the -Help argument to see the full help.
When possible, Ookii.CommandLine will use color when writing usage help. This is controlled by the
use_color parameter of the usage_writer class constructor. When set to std::nullopt (the
default), the usage_writer tries to determine whether color is supported. Color will only be
enabled if:
- There is no environment variable named
NO_COLOR. - The standard output stream is not redirected.
- The
TERMenvironment variable is not set todumb. - On Windows, enabling virtual terminal sequences using
SetConsoleModemust succeed. - On other platforms, the
TERMenvironment variable must be defined.
The usage_writer class uses virtual terminal sequences to set color. Several components of the
help have preset colors, which can be customized using fields of the usage_writer class. Set them
to any of the constants in the ookii::vt::text_format namespace, or any other valid virtual
terminal sequence.
In order to support proper white-space wrapping for text that contains virtual terminal sequences,
the line_wrapping_ostream class will not count virtual terminal sequences as part of the line
length.
The below is an example of the usage help with the default colors.
The usage help can be heavily customized. We've already seen how it can be customized using things
such as custom value descriptions, or various fields of the usage_writer class. These can also
be used to control the indentation of the text, what elements to include, and various small
formatting changes such as whether to use white space or the custom name/value separator.
To customize the usage even further, you can derive a class from the usage_writer class. The
usage_writer class has protected virtual methods for every part of the usage. These range from
top-level methods like write_parser_usage_core() which drives the entire process, methods
responsible for a section such as write_parser_usage_syntax() or
write_argument_descriptions(), methods responsible for a single argument like
write_argument_syntax() or write_argument_description(),
down to methods that write a single piece of text like write_argument_name() or
write_value_description().
The
usage_writerclass has several fields and methods that apply only to subcommands, so setting or overriding these will have no effect if you are not using subcommands.
These methods call each other, so you can customize as little or as much as you like, depending on
which methods you override. For example, if you want to use something other than angle brackets for
value descriptions, just override write_value_description() (and probably also
write_value_description_for_description()). Or, if you want to change the entire format of the
descriptions, override write_argument_description().
To specify a custom usage writer, pass it to the command_line_parser::parse() method or the
command_line_parser::write_usage() method.
The custom usage sample uses a custom usage writer to radically alter the format of the usage help, as seen below.
DESCRIPTION:
Sample command line application. The application parses the command line and prints the results,
but otherwise does nothing and none of the arguments are actually used for anything.
USAGE:
custom_usage [--source] <string> [--destination] <string> [[--operation-index] <int>] [arguments]
OPTIONS:
-c|--count <number> Provides the count for something to the application.
-d|--destination <string> The destination data.
-?|-h|--help Displays this help message.
--operation-index <int> The operation's index. [default: 1]
-p|--process Does the processing.
-s|--source <string> The source data.
--value <string> This is an example of a multi-value argument, which can be repeated
multiple times to set more than one value.
-v|--verbose Print verbose information; this is an example of a switch argument.
--version Displays version information.
You can see that the usage_writer class offers a lot of flexibility to customize the usage help
to your liking.
Please see the subcommand documentation for information about their usage help.
And actually, subcommands are what we'll cover next.
