Skip to content

A simple header-only option parser

License

Notifications You must be signed in to change notification settings

cnweaver/cl_options

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

General

This is a simple, header-only library for parsing command line options. Many others exist; this one happens to suit the author's personal taste. It has no dependencies except the C++ standard library. C++11 or newer is required.

As the library itself is a single header file there is nothing which must be compiled. However, included are an example program demonstrating usage and a test program which runs through a very basic set of unit tests. The provided makefile will compile these. The makefile can also install (and uninstall) the library. By default the install prefix is /usr/local but this can be changed using the PREFIX variable:

make install PREFIX=/preferred/location

Code Documentation

A simple utility for parsing command line options.

Assuming an object of this class:

OptionParser op;

Options may be flags (like ls -l) which are handled via a callback function:

bool longFormat=false;
op.addOption('l', [&]{longFormat=true;}, "List in long format.");

or may take values, which can be directly stored in a variable (like tar -f archive.tar):

std::string archiveFile;
op.addOption('f', archiveFile, 
     "Read the archive from or write the archive to the specified file.",
     "archive");

Here, the option is also given a description ("Read the archive. . . ") and a name for the value it takes ("archive"). These are used to provide help messages for users (see below). A description is required, and though optional, a value name is strongly recommended.

Options taking values may also do so via a callback, although in this case it is unfortunately necessary to explicit specify the datatype expected by the callback:

std::string archiveFile;
op.addOption<std::string>('f',
     [&archiveFile](std::string file){ archiveFile=file; },
     "Read the archive from or write the archive to the specified file.",
     "archive");

Note that options which take value may do so either by consuming the following argument, or may have the value speficier 'inline' with an equals sign so that both of the following invocations of a program with the above option are equivalent:

tar -f archive.tar
tar -f=archive.tar

Options may be either short ('-l') or long ('--long'). A single option may have multiple synonymous forms:

bool longFormat=false;
op.addOption({"l","long","long-listing"}, 
     [&]{longFormat=true;}, "List in long format.");

Positional arguments are also supported and are separated out from options and their values. A more complete 'tar' example:

OptionParser op;
bool create=false, extract=false;
op.addOption('c', [&]{create=true}, 
     "Create a new archive containing the specified items.");
op.addOption('x', [&]{extract=true}, 
     "Extract to disk from the archive.");
std::string archiveFile;
op.addOption('f', archiveFile, 
     "Read the archive from or write the archive to the specified file.",
     "archive");
auto positionals = op.parseArgs(argc, argv);

With this code in place one could invoke the hypothetical 'tar' program as

tar -c -f my_files.tar file1 file2

and create would be set to true, while positionals would be an std::vector<std::string> containing {"tar","file1","file2"}.

A common shortcut to reduce typing is to allow multiple short options to be run together in a single argument. This is not enabled by default, but can be turned on:

op.allowsShortOptionCombination(true);

Inserting this into the example makes it allow

tar -cf my_files.tar file1 file2

Any number of flags may be combined in this way, but at most one option taking a value may appear, and it must be the last in the combined argument. Otherwise, there would be no way for the parser to know in general where the value for an option ended and where additional option packed into the same argument might begin.

Since we have taken tar as an example, it should also be noted that even when short option combining is allowed, the BSD-style 'bundled flags' first positional argument is not supported. (It is not exactly forbidden, as this implementation will simply treat it as a positional argument and pass it as such back to the calling code, but no automatic parsing is directly provided.)

This class also supports generating simple usage information, and by default the options '-h', '-?', '--help', and '--usage' will write it to standard output. (These default options may be suppressed when the OptionParser is constructed.) Each option is summarized using the description string provided when it is added, and custom introductory text can be added before this. Adding to the above example:

op.setBaseUsage("This is toy example of the tar interface");

Running this program with any of the built-in help options produces:

This is toy example of the tar interface
 -c: Create a new archive containing the specified items.
 -x: Extract to disk from the archive.
 -f archive: Read the archive from or write the archive to the specified file.
    (default: "")

The generation of this help message is currently very simple, and makes no effort to reflow text, so users are advised to format description strings themselves. It will attempt to show the default values of options, in cases where the option is directly associated with a variable. This feature is also fairly simplistic, and takes the value of the variable at the time addOption was called, so if the value of the variable is otherwise changed between when the option is configured and when argument parsing occurs, the user can be misled as to the default value.

For short options, it may be desirable to support accepting a value as part of the same argument, without an intervening equals sign, as in

# run make with 8 jobs
make -j8
# compile foo.c, linking against libbar and libbaz
$CC foo.c -lbar -lbaz -o foo

This behavior is supported, but turned off by default. To activate it use

op.allowsShortValueWithoutEquals(true);

This covers short options only, as supporting long options would require both more complex parsing and could lead to ambiguities. (If 'foo' were an option taking a value and 'foobar' were also an option, how should --foobar be interpreted?)

Another feature which is sometimes important is providing a way to terminate option parsing and treat all remaining arguments as positional. This is relevant for allowing programs to accept filenames beginning with dashes as arguments, as well as debuggers and other wrapper programs, which may have many options of their own, but also wish to take a series of arguments defining another program to be run including its own options and arguments. This is optionally supported by the special option '--', which can be used to indicate that all following arguments should be passed through without further interpretation. This feature is enabled by

op.allowsOptionTerminator(true);

Because of the simplistic way that the help text is accumulated currently, disabling this feature after enabling it does not remove the automatically added entry in the help text, and enabling it multiple times will result in multiple copies of that text.

When a program needs to be run repeatedly with a large set of options (and positional arguments), it may be useful to read them from a configuration file. The OptionParser::addConfigFileOption family of member functions allow designating an option name which takes a file path, and causes the contents of the referenced file to be read as further options. The file format supported is essentially the same as for direct arguments; it is not INI, YAML, TOML, or some other distinct format. Instead, Bourne shell-like argument splitting on whitespace is performed, whitespace characters may be escaped with backslahes or protected by quotes, and double quotes permit single quotes and backslash escapes within, while single quotes treat all characters within literally (and thus do not allow escape sequences, but can contain literal double quotes). The intention is that, in general, any arguments which would be supplied at the command line can instead be placed in a configuration file and will be treated equivalently. Note, however, that the config parser does not implement all shell features, for example, it will not expand ~ to the value of $HOME.

Configuration file options may be used from within configuration files. This is useful when some block of options or arguments is shared between several configurations, as it can be factored out into a separate file rather than being repeated. Cyclic references among configuration files would lead to infinite recursion, and so should be avoided. The library has a simplistic ability to detect such cycles, but since it merely matches file paths as written, it can be fooled by symbolic links or other mechanisms which allow the same file to be referenced by multiple names.

About

A simple header-only option parser

Resources

License

Stars

Watchers

Forks

Releases

No releases published