CommandLine 2.0 Library Manual |
Introduction |
Although there are a lot of command line argument parsing libraries out there in many different languages, none of them fit well with what I needed. By looking at the features and problems of other libraries, I designed the CommandLine library to have the following features:
Quick Start Guide |
To start out, you need to include the CommandLine header file into your program:
#include "Support/CommandLine.h"
Additionally, you need to add this as the first line of your main program:
int main(int argc, char **argv) { cl::ParseCommandLineOptions(argc, argv); ... }
... which actually parses the arguments and fills in the variable declarations.
Now that you are ready to support command line arguments, we need to tell the system which ones we want, and what type of argument they are. The CommandLine library uses a declarative syntax to model cammand line arguments with the variable declarations that capture the parsed values. This means that for every command line option that you would like to support, there should be a variable declaration to capture the result. For example, in a compiler, we would like to support the unix standard '-o <filename>' option to specify where to put the output. With the CommandLine library, this is represented like this:
cl::opt<string> OutputFilename("o", cl::desc("Specify output filename"), cl::value_desc("filename"));
This declares a variable "OutputFilename" that is used to capture the result of the "o" argument (first parameter). We specify that this is a simple scalar option by using the "opt<>" template (as opposed to the "list<> template), and tell the CommandLine library that the data type that we are parsing is a string.
The second and third parameters (which are optional) are used to specify what to output for the "--help" option. In this case, we get a line that looks like this:
USAGE: compiler [options] OPTIONS: -help - display available options (--help-hidden for more) -o <filename> - Specify output filenameBecause we specified that the command line option should parse using the string data type, the variable declared is automatically usable as a real string in all contexts that a normal C++ string object may be used. For example:
... ofstream Output(OutputFilename.c_str()); if (Out.good()) ... ...
There are many different options that you can use to customize the command line option handling library, but the above example shows the general interface to these options. The options can be specified in any order, and are specified with helper functions like cl::desc(...), so there are no positional dependencies to have to remember. We will discuss the options you can use later in this document. Also note that if your compiler supports Koenig lookup (gcc 2.95.x doesn't), that you don't have to specify as many cl:: namespace qualifiers to use the library.
Continuing the example, we would like to have our compiler take an input filename as well as an output filename, but we do not want the input filename to be specified with a hyphen (ie, not -filename.c). To support this style of argument, the CommandLine library allows for positional arguments to be specified for the program. These positional arguments are filled with command line parameters that are not in option form. We use this feature like this:
cl::opt<string> InputFilename(cl::Positional, cl::desc("<input file>"), cl::init("-"));This declaration indicates that the first positional argument should be treated as the input filename. Here we use the cl::init option to specify an initial value for the command line option, which is used if the option is not specified (if you do not specify a cl::init modifier for an option, then the default constructor for the data type is used to initialize the value). Command line options default to being optional, so if we would like to require that the user always specify an input filename, we would add the cl::Required flag, and we could eliminate the cl::init modifier, like this:
cl::opt<string> InputFilename(cl::Positional, cl::desc("<input file>"), cl::Required);Again, the CommandLine library does not require the options to be specified in any particular order, so the above declaration is equivalent to:
cl::opt<string> InputFilename(cl::Positional, cl::Required, cl::desc("<input file>"));By simply adding the cl::Required flag, the CommandLine library will automatically issue an error if the argument is not specified, which shifts all of the command line option verification code out of your application into the library. This is just one example of how using flags can alter the default behaviour of the library, on a per-option basis. By adding one of the declarations above, the --help option synopsis is now extended to:
USAGE: compiler [options] <input file> OPTIONS: -help - display available options (--help-hidden for more) -o <filename> - Specify output filename... indicating that an input filename is expected.
Boolean Arguments |
cl::opt<bool> Force ("f", cl::desc("Overwrite output files")); cl::opt<bool> Quiet ("quiet", cl::desc("Don't print informational messages")); cl::opt<bool> Quiet2("q", cl::desc("Don't print informational messages"), cl::Hidden);
This does what you would expect: it declares three boolean variables ("Force", "Quiet", and "Quiet2") to recognize these options. Note that the "-q" option is specified with the "cl::Hidden" flag. This modifier prevents it from being shown by the standard "--help" output (note that it is still shown in the "--help-hidden" output).
The CommandLine library uses a different parser for different data types. For example, in the string case, the argument passed to the option is copied literally into the content of the string variable... we obviously cannot do that in the boolean case, however, so we must use a smarter parser. In the case of the boolean parser, it allows no options (in which case it assigns the value of true to the variable), or it allows the values "true" or "false" to be specified, allowing any of the following inputs:
compiler -f # No value, 'Force' == true compiler -f=true # Value specified, 'Force' == true compiler -f=TRUE # Value specified, 'Force' == true compiler -f=FALSE # Value specified, 'Force' == false... you get the idea. The bool parser just turns the string values into boolean values, and rejects things like 'compiler -f=foo'. Similarly, the float, double, and int parsers work like you would expect, using the 'strtol' and 'strtod' C library calls to parse the string value into the specified data type.
With the declarations above, "compiler --help" emits this:
USAGE: compiler [options] <input file> OPTIONS: -f - Overwrite output files -o - Override output filename -quiet - Don't print informational messages -help - display available options (--help-hidden for more)
and "opt --help-hidden" prints this:
USAGE: opt [options] <input file> OPTIONS: -f - Overwrite output files -o - Override output filename -q - Don't print informational messages -quiet - Don't print informational messages -help - display available options (--help-hidden for more)
This brief example has shown you how to use the 'opt<>' class to parse simple scalar command line arguments. In addition to simple scalar arguments, the CommandLine library also provides primitives to support CommandLine option aliases, and lists of options.
Argument Aliases |
... if (!Quiet && !Quiet2) printInformationalMessage(...); ...
... which is a real pain! Instead of defining two values for the same condition, we can use the "cl::alias" class to make the "-q" option an alias for the "-quiet" option, instead of providing a value itself:
cl::opt<bool> Force ("f", cl::desc("Overwrite output files")); cl::opt<bool> Quiet ("quiet", cl::desc("Don't print informational messages")); cl::alias QuietA("q", cl::desc("Alias for -quiet"), cl::aliasopt(Quiet));
The third line (which is the only one we modified from above) defines a "-q alias that updates the "Quiet" variable (as specified by the cl::aliasopt modifier) whenever it is specified. Because aliases do not hold state, the only thing the program has to query is the Quiet variable now. Another nice feature of aliases is that they automatically hide themselves from the -help output (although, again, they are still visible in the --help-hidden output).
Now the application code can simply use:
... if (!Quiet) printInformationalMessage(...); ...
... which is much nicer! The "cl::alias" can be used to specify an alternative name for any variable type, and has many uses.
Selecting an alternative from a set of possibilities |
The answer is that it uses a table driven generic parser (unless you specify your own parser, as described in the Extension Guide). This parser maps literal strings to whatever type is required, are requires you to tell it what this mapping should be.
Lets say that we would like to add four optimizations levels to our optimizer, using the standard flags "-g", "-O0", "-O1", and "-O2". We could easily implement this with boolean options like above, but there are several problems with this strategy:
To cope with these problems, we can use an enum value, and have the CommandLine library fill it in with the appropriate level directly, which is used like this:
enum OptLevel { g, O1, O2, O3 }; cl::opt<OptLevel> OptimizationLevel(cl::desc("Choose optimization level:"), cl::values( clEnumVal(g , "No optimizations, enable debugging"), clEnumVal(O1, "Enable trivial optimizations"), clEnumVal(O2, "Enable default optimizations"), clEnumVal(O3, "Enable expensive optimizations"), 0)); ... if (OptimizationLevel >= O2) doPartialRedundancyElimination(...); ...
This declaration defines a variable "OptimizationLevel" of the "OptLevel" enum type. This variable can be assigned any of the values that are listed in the declaration (Note that the declaration list must be terminated with the "0" argument!). The CommandLine library enforces that the user can only specify one of the options, and it ensure that only valid enum values can be specified. The "clEnumVal" macros ensure that the command line arguments matche the enum values. With this option added, our help output now is:
USAGE: compiler [options] <input file> OPTIONS: Choose optimization level: -g - No optimizations, enable debugging -O1 - Enable trivial optimizations -O2 - Enable default optimizations -O3 - Enable expensive optimizations -f - Overwrite output files -help - display available options (--help-hidden for more) -o <filename> - Specify output filename -quiet - Don't print informational messagesIn this case, it is sort of awkward that flag names correspond directly to enum names, because we probably don't want a enum definition named "g" in our program. Because of this, we can alternatively write this example like this:
enum OptLevel { Debug, O1, O2, O3 }; cl::opt<OptLevel> OptimizationLevel(cl::desc("Choose optimization level:"), cl::values( clEnumValN(Debug, "g", "No optimizations, enable debugging"), clEnumVal(O1 , "Enable trivial optimizations"), clEnumVal(O2 , "Enable default optimizations"), clEnumVal(O3 , "Enable expensive optimizations"), 0)); ... if (OptimizationLevel == Debug) outputDebugInfo(...); ...
By using the "clEnumValN" macro instead of "clEnumVal", we can directly specify the name that the flag should get. In general a direct mapping is nice, but sometimes you can't or don't want to preserve the mapping, which is when you would use it.
Named Alternatives |
enum DebugLev { nodebuginfo, quick, detailed }; // Enable Debug Options to be specified on the command line cl::optThis definition defines an enumerated command line variable of type "enum DebugLev", which works exactly the same way as before. The difference here is just the interface exposed to the user of your program and the help output by the "--help" option:DebugLevel("debug_level", cl::desc("Set the debugging level:"), cl::values( clEnumValN(nodebuginfo, "none", "disable debug information"), clEnumVal(quick, "enable quick debug information"), clEnumVal(detailed, "enable detailed debug information"), 0));
USAGE: compiler [options] <input file> OPTIONS: Choose optimization level: -g - No optimizations, enable debugging -O1 - Enable trivial optimizations -O2 - Enable default optimizations -O3 - Enable expensive optimizations -debug_level - Set the debugging level: =none - disable debug information =quick - enable quick debug information =detailed - enable detailed debug information -f - Overwrite output files -help - display available options (--help-hidden for more) -o <filename> - Specify output filename -quiet - Don't print informational messages
Again, the only structural difference between the debug level declaration and the optimiation level declaration is that the debug level declaration includes an option name ("debug_level"), which automatically changes how the library processes the argument. The CommandLine library supports both forms so that you can choose the form most appropriate for your application.
Parsing a list of options |
enum Opts { // 'inline' is a C++ keyword, so name it 'inlining' dce, constprop, inlining, strip };
Then define your "cl::list" variable:
cl::list<Opts> OptimizationList(cl::desc("Available Optimizations:"), cl::values( clEnumVal(dce , "Dead Code Elimination"), clEnumVal(constprop , "Constant Propogation"), clEnumValN(inlining, "inline", "Procedure Integration"), clEnumVal(strip , "Strip Symbols"), 0));
This defines a variable that is conceptually of the type "std::vector<enum Opts>". Thus, you can access it with standard vector methods:
for (unsigned i = 0; i != OptimizationList.size(); ++i) switch (OptimizationList[i]) ...... to iterate through the list of options specified.
Note that the "cl::list" template is completely general and may be used with any data types or other arguments that you can use with the "cl::opt" template. One especially useful way to use a list is to capture all of the positional arguments together if there may be more than one specified. In the case of a linker, for example, the linker takes several '.o' files, and needs to capture them into a list. This is naturally specified as:
... cl::list<std::string> InputFilenames(cl::Positional, cl::desc("<Input files>"), cl::OneOrMore); ...
This variable works just like a "vector<string>" object. As such, accessing the list is simple, just like above. In this example, we used the cl::OneOrMore modifier to inform the CommandLine library that it is an error if the user does not specify any .o files on our command line. Again, this just reduces the amount of checking we have to do.
Reference Guide |
Extension Guide |