Brad Appleton
Software Tools Developer
E-mail: brad@bradapp.net
WWW: http://www.bradapp.net


Options: a C++ option-parser


Introduction to Options

Options is a C++ library for parsing Unix-style command-line options. The full source code distribution for Options may be found in Options.tar.gz (22.5KB, gzipped tar file).

Options understands options and gnu-long-options and the parsing behavior is somewhat configurable: You may specify options to be be case insensitive, matched by long (keyword) name or short (single character) name; and a number of other features (see the file <options.h> for a complete description).

Using Options

Options defines a C++ class of the same name which represents the allowable set of command-line options and how to parse them:
  #include <options.h>

  Options opts(cmdname, optv);
  char cmdname[], *optv[];
You "specify" your options by declaring an array of strings like so:

  const char * optv[] = {
    "c:count <number>",
    "s?str   <string>",
    "x|xmode",
    NULL
  } ;

Now you can iterate over your options from the command-line as follows:

  #include <stdlib.h>
  #include <options.h>

  main(int argc, char *argv[]) {
    Options  opts(*argv, optv);
    OptArgvIter  iter(--argc, ++argv);
    const char *optarg, *str = NULL;
    int  errors = 0, xflag = 0, count = 1;

    while( char optchar = opts(iter, optarg) ) {
      switch (optchar) {
        case 's' :
          str = optarg; break;
        case 'x' :
          ++xflag; break;
        case 'c' :
          if (optarg == NULL)  ++errors;
          else  count = (int) atol(optarg);
          break;
        default :  ++errors; break;
      } //switch
    }
  ...  // process the rest of the arguments in "iter"
  }

Option Specification Syntax

Note the character (one of ':', '?', '|', '*', or '+') between the short and long name of the option in "c:count <number>". It specifies the option type:

The remainder of the string must be the long-option name. Please note that long-option names are matched case-insensitive and only a unique prefix of the name needs to be specified. By default, option-name characters are case-sensitive (but this may be changed by turning on a run-time flag).

If desired, the long-option name may be followed by one or more spaces and then by the name of the option value. This name will be used when printing usage messages. If the option-value-name is not given then the string "<value>" will be used in usage messages.

One may use a space to indicate that a particular option does not have a corresponding long-option. For example, "c: " (or "c:") means the -c option takes a value and has no corresponding long-option.

To specify a long-option that has no corresponding single-character option is a bit trickier: Options::operator() still needs an "option-character" to return when that option is matched. One may use a whitespace character or a non-printable character as the single-character option in such a case. (hence " |hello" would only match "--hello").

Using the previous example, optv[] now corresponds to the following command-line syntax:

  progname [-c <number>] [-s [<string>]] [-x]

Using long-options, optv[] corresponds to the following ("-" or "+" may be used instead of "--" as the prefix):

  progname [--count <number>] [--str [<string>]] [--xmode]

Noteworthy Exceptions:

If the 1st character of the string is '-', then the rest of the string must correspond to the above format, and the option is considered to be a hidden-option. This means it will be parsed when actually matching options from the command-line, but will not show-up if a usage message is printed using the usage() member function. Such an example might be "-h|hidden". If you want to use any "dummy" options (options that are not parsed, but that to show up in the usage message), you can specify them along with any positional parameters to the usage() member function.

If the 2nd character of the string is '\0' then it is assumed that there is no corresponding long-option and that the option takes no argument (hence "f", and "f| " are equivalent).

Examples:

  const char * optv[] = {
    "c:count   <number>",
    "s?str     <string>",
    "x",
    " |hello",
    "g+groups  <newsgroup>",
    NULL
  } ;

Now optv[] corresponds to the following:

  usage: cmdname [-c|--count <number>] [-s|--str [<string>]]
                 [-x] [--hello] [-g|--groups <newsgroup> ...]

Intermixing options with positional parameters

If you want Options to parse your positional arguments too (in case they are intermingled in with your options) then you can do that by turning on a special control-flag:

  #include <options.h>

  main(int argc, char *argv[]) {
     Options  opts(*argv, optv);
     OptArgvIter  iter(--argc, ++argv);
     char *optarg, *str = NULL;
     int  errors = 0, xflag = 0, count = 1;
  
     opts.ctrls(Options::PARSE_POS);
     while( char optchar = opts(iter, optarg) ) {
        switch (optchar) {
        case 's' :
           str = optarg; break;
        case 'x' :
           ++xflag; break;
        case 'c' :
           if (optarg == NULL)  ++errors;
           else  count = (int) atol(optarg);
           break;
        case Options::POSITIONAL :
           // handle positional arguments here ...
           // (optarg points to the positional argument)
           break;
        default :  ++errors; break;
        } //switch
     }
  }

Options makes use of an abstract argument-iterator object to access your command-line arguments. Pre-defined iterator types are provided for parsing options from an array (typically argv[]), an input stream, and a field-delimited string (using strtok(3C)). This is convenient for parsing configurations options from a start-up file or an environment variable. A side-effect of this however is that Options cannot assume the argument source is necessarily argv[] (which is the normal case). This means that it cannot permute the argument list (like GNU getoptlong) to process all options ahead of positional arguments when the two are intermixed on the command-line. This can easily be accomodated however by rearranging the argv[] yourself as follows:

  #include <stdlib.h>
  #include <options.h>

  main(int argc, char *argv[]) {
     Options  opts(*argv, optv);
     OptArgvIter  iter(--argc, ++argv);
     char *optarg, *str = NULL;
     int  errors = 0, xflag = 0, count = 1;
     int  npos = 0;
  
     opts.ctrls(Options::PARSE_POS);
     while( char optchar = opts(iter, optarg) ) {
        switch (optchar) {
        case 's' :
           str = optarg; break;
        case 'x' :
           ++xflag; break;
        case 'c' :
           if (optarg == NULL)  ++errors;
           else  count = (int) atol(optarg);
           break;
        case Options::POSITIONAL :
            // Push all positional arguments to the front. Note that
            // we could swap argv[npos] with argv[iter.index() - 1]
            // (assuming we have made sure they arent in fact one and
            // the same) if we dont want to lose its previous value.
           argv[npos++] = optarg;
           break;
        default :  ++errors; break;
        } //switch
     }

     // Now argv[0] .. argv[npos - 1] contains the positional arguments
     for (int i = 0; i < npos; ++i) {
        // handle positional argument in argv[i] ...
     }
  }

The above simply replaces the beginning elements in argv[] with an argument that have already been parsed, thus moving all the positional parameters to the front. If you prefer not to lose the already parsed options, you could do a number of different things. You could simply perform a swap instead of a replacement (as is mentioned in the comment above) or you could allocate a new array to hold the positional arguments, or you could go to the effort of truly permuting argv[] yourself.

An Extended Example

The following is the C++ code for a simple program that uses Options to parse the command-line and then print out each command-line option and argument that was specified:

  #include <stdlib.h>
  #include <options.h>

  // Specify options and their syntax
  static const char * optv[] = {
    "H|help",
    "c:count   <number>",
    "s?str     <string>",
    "x",
    " |hello",
    "g+groups  <newsgroup>",
    NULL
  } ;

  main(int argc, char * argv[]) {
    // Declare storage for the option-letter and its argument (if any).
    int  optchar;
    const char * optarg;

    // Set default option values.
    const char * str = "default_string";
    int  count = 0, xflag = 0, hello = 0;
    int  errors = 0, ngroups = 0;

    // Declare Options object and its iterator
    Options  opts(*argv, optv);
    OptArgvIter  iter(--argc, ++argv);

    // Iterate over options and handle each one as appropriate
    while( optchar = opts(iter, optarg) ) {
      switch (optchar) {
        case 'H' :
          opts.usage(cout, "files ...");
          exit(0);
          break;
        case 'g' :
          ++ngroups; break;  // the groupname is in "optarg"
        case 's' :
          str = optarg; break;
        case 'x' :
          ++xflag; break;
        case ' ' :
          ++hello; break;
        case 'c' :
          if (optarg == NULL)  ++errors;
          else  count = (int) atol(optarg);
          break;
        default :  ++errors; break; // unknown option
      } //switch
    }

    // Print an error message if bad syntax was used, or if no
    // filenames were specified on the command-line.
    if (errors || (iter.index() == argc)) {
      if (! errors) {
        cerr << opts.name() << ": no filenames given." << endl ;
      }
      opts.usage(cerr, "files ...");
      exit(1);
    }

    // Print out the option values that were specified.
    cout << "xflag=" << ((xflag) ? "ON"  : "OFF") << endl
         << "hello=" << ((hello) ? "YES" : "NO") << endl
         << "count=" << count << endl
         << "str=\"" << ((str) ? str : "No value given!") << "\"" << endl
         << "ngroups=" << ngroups << endl ;

    // Print out the remaining positional arguments on the command-line
    if (iter.index() < argc) {
      cout << "files=" ;
      for (int i = iter.index() ; i < argc ; i++) {
        cout << "\"" << argv[i] << "\" " ;
      }
      cout << endl ;
    }
  }


back to Brad Appleton's Home Page