If you've gone through the tutorial, you'll already have some idea of how Ookii.CommandLine parses arguments. This page will explain the rules in detail, including all the possible kinds or arguments.
Command line arguments are passed to your application when it is started, and are usually accessed
through the parameters of the int main(int argc, char *argv[])
method (or int wmain(int argc, wchar_t *argv[])
or CommandLineToArgvW
for Windows applications using Unicode). This provides the arguments as an
array of strings, which Ookii.CommandLine will parse to extract strongly-typed, named values that
you can easily access in your application.
The method used to extract values from the array of string arguments is determined by the command line argument parsing rules. Ookii.CommandLine supports two sets of parsing rules: the default mode, which uses parsing rules similar to those used by PowerShell, and long/short mode, which is more POSIX-like, and lets arguments have a long name and a short name, with different prefixes. Most of the below information applies to both modes, with the differences described at the end.
In Ookii.CommandLine, all command line arguments have a name, and can be assigned a value on the command line using that name. They follow the name of your application's executable on the command prompt, and typically take the following form:
-ArgumentName ArgumentValue
The argument name is preceded by the argument name prefix. This prefix is configurable, but
Ookii.CommandLine defaults to accepting a dash (-
) and a forward slash (/
) on Windows, and only
a dash (-
) on other platforms such as Linux or MacOS.
Argument names are case insensitive by default, though this can be customized using the
parser_builder::case_sensitive()
method.
The argument's value follows the name, separated by either white space (as a separate argument token),
or by the argument name/value separator, which is a colon (:
) by default. The following is
identical to the previous example:
-ArgumentName:value
Whether white-space is allowed to separate the name and value is configured using the
parser_builder::allow_whitespace_separator()
method, and the argument name/value separator can be
customized using the parser_builder::argument_value_separator()
method.
Not all arguments require values; those that do not are called switch arguments and have a value determined by their presence or absence on the command line.
An argument can have one or more aliases: alternative names that can also be used to supply the same
argument. For example, an argument named -Verbose
might use the alias -v
as a shorter to type
alternative.
An argument can be positional, which means in addition to being supplied by name, it can also be supplied without the name, using the position of the value. Which argument the value belongs to is determined by its position relative to other positional arguments.
If an argument value is encountered without being preceded by a name, it is matched to the next positional argument without a value. For example, take the following command line arguments:
value1 –ArgumentName value2 value3
In this case, value1 is not preceded by a name; therefore, it is matched to the first positional
argument. Value2 follows a name, so it is matched to the argument with the name -ArgumentName
.
Finally, value3 is matched to the second positional argument.
A positional argument can still be supplied by name. If a positional argument is supplied by name,
it cannot also be specified by position; in the previous example, if the argument named
-ArgumentName
was the second positional argument, then value3 becomes the value for the third
positional argument, because the value for -ArgumentName
was already specified by name. If
-ArgumentName
is the first positional argument, this would cause an error (unless duplicate
arguments are allowed in the options), because it already had a value set by value1
.
A command line argument that is required must be supplied on all invocations of the application. If a required argument is not supplied, this is considered an error and parsing will fail.
Any argument can be made required. Usually, it is recommended for any required argument to also be a positional argument, but this is not mandatory.
A switch argument, sometimes also called a flag, is an argument with a Boolean type (bool
). Its
value is determined by its presence or absence on the command line; the value will be true if the
argument is supplied, and false if not. The following sets the switch argument named “Switch” to
true:
-Switch
A switch argument’s value can be specified explicitly, as in the following example:
-Switch:false
You must use the name/value separator (a colon by default) to specify an explicit value for a switch
argument; you cannot use white space. If the command line contains -Switch false
, then false
is
the value of the next positional argument, not the value for -Switch
.
If you use std::optional<bool>
as the type of the argument, it will be std::nullopt
if not
supplied, true
if supplied, and false
only if explicitly set to false using -Switch:false
.
Some arguments can take multiple values; these are multi-value arguments. These arguments can be supplied multiple times, and each value is added to the set of values. For example, consider the following command line arguments:
-ArgumentName value1 –ArgumentName value2 –ArgumentName value3
In this case, if -ArgumentName
is a multi-value argument, the value of the argument will be a list
holding all three values.
It’s possible to specify a separator for multi-value arguments using the
parser_builder::multi_value_argument_builder::separator()
method. This makes it possible to
specify multiple values for the argument while the argument itself is specified only once. For
example, if the separator is set to a comma, you can specify the values as follows:
-ArgumentName value1,value2,value3
In this case, the value of the argument named -ArgumentName
will be a list with the three values
"value1", "value2" and "value3".
Note: if you specify a separator for a multi-value argument, it is not possible to have an argument value containing the separator. There is no way to escape the separator. Therefore, make sure you pick a separator that will never be used in the argument values, and be extra careful with culture-sensitive argument types.
If a multi-value argument is positional, it must be the last positional argument. All remaining positional argument values will be considered values for the multi-value argument.
If a multi-value argument is required, it means it must have at least one value.
If an argument is not a multi-value argument, it is an error to supply it more than once, unless
duplicate arguments were set to allowed using the parser_builder::allow_duplicate_arguments()
method, in which case only the last value is used.
If the type of the argument is a container of Boolean values (e.g. std::vector<bool>
), it will
act as a multi-value argument and a switch. A value of true (or the explicit value if one is given)
gets added to the list for every time that the argument is supplied.
Ookii.CommandLine allows you to define arguments with any type; the only requirement is that it is possible to convert that type to and from a string.
String conversion is performed using the ookii::lexical_convert
template. The default
implementation uses stream extraction (operator>>
on an std::istringstream
), so if such an
operator is defined for your type, this is sufficient. Note that the operator must consume the
entire contents of the stream, and leave the stream without badbit
or failbit
set, for
conversion to be considered successful.
You can also provide a template specialization of ookii::lexical_convert
to perform conversion
without depending on streams. This template struct has a single method, from_string()
, which
you must implement. The method returns an std::optional<T>
, and you should return std::nullopt
if conversion failed.
template<>
struct ookii::lexical_convert<your_type, char>
{
static std::optional<your_type> from_string(std::string_view value, const std::locale &loc)
{
// Implement string conversion here.
}
};
If you are using Unicode arguments on Windows, use
wchar_t
andstd::wstring_view
.
It is possible to override the default conversion for a type by specifying a custom conversion
function using the parser_builder::typed_argument_builder::converter()
method. Note that even
if you supply a custom converter, a default one must still exist otherwise your code will not
compile. The custom converter is intended for situations where a default conversion exists, but you
wish to deviate from that behavior.
In order to display default values, it must also be possible to output the type using stream
insertion (operator<<
). This is always necessary, even if you don't use a default value for
arguments of that type.
An example of writing implementations of operator<<
and operator>>
for a custom type, as well as
an example of directly specializing ookii::lexical_convert
, can be seen in the
custom_types.h file used by the unit tests.
If the argument is a multi-value argument, string conversion must be available for the type indicated
by the container's value_type
. Usually, this is the type of element in the container (e.g. for
std::vector<int>
it would be int
).
If the argument uses the type std::optional<T>
, string conversion must be available for the
contained type T
.
For many types, the conversion can be locale dependent. For example, converting numbers or dates
depends on the std::locale
which defines the accepted formats and how they’re interpreted; some
locales might use a period as the decimal separators, while others use a comma.
The locale used for argument value conversions is specified by the ookii::parser_builder::locale()
method. If not specified, it defaults to the global locale set by std::locale::global()
, which
itself defaults to the invariant "C" locale.
For a consistent parsing experience, it's strongly recommended to always use an invariant locale for command line parsing. If you use the locale based on the user's current culture, the same command line may not be parsed the same for users with different regional settings (for example, if you use floating point numbers).
POSIX and GNU conventions specify that options use a dash (-
) followed by a single character, and
define the concept of long options, which use --
followed by an a multi-character name. This style
is used by many tools like cmake
, git
, and many others, and may be preferred if you are writing
a cross-platform application.
Ookii.CommandLine calls this style of parsing "long/short mode," and offers it as an alternative
mode to augment the default parsing rules. In this mode, an argument can have the regular long name
and an additional single-character short name, each with its own argument name prefix. By default,
the prefix --
is used for long names, and -
(and /
on Windows) for short names.
This mode can be enabled by passing parsing_mode::long_short
to the parser_builder::mode()
method.
POSIX conventions also specify the use of lower case argument names, with dashes separating words
("dash-case"). If you are using the code generation scripts, you can easily achieve
that using name transformation. It's also common to use case-sensitive argument names in this mode,
which can be enabled with the parser_builder::case_sensitive()
method.
For example, an argument named --path
could have a short name -p
. It could then be supplied
using either name:
--path value
Or:
-p value
Note that you must use the correct prefix: using -path
or --p
will not work.
An argument can have either a short name or a long name, or both.
Arguments in this mode can still have aliases. You can set separate long and short aliases, which follow the same rules as the long and short names.
For switch arguments with short names, the switches can be combined in a single argument. For
example, given the switches -a
, -b
and -c
, the following command line sets all three switches:
-abc
This is equivalent to:
-a -b -c
This only works for switch arguments, and does not apply to long names.
Besides these differences, long/short mode follows the same rules and conventions as the default mode outlined above, with all the same options.
Next, let's take a look at how to define arguments.