// Abstract class to iterate through options/arguments // class OptIter { public: OptIter(void); virtual ~OptIter(void); // curr() returns the current item in the iterator without // advancing on to the next item. If we are at the end of items // then NULL is returned. virtual const char * curr(void) = 0; // next() advances to the next item. virtual void next(void) = 0; // operator() returns the current item in the iterator and then // advances on to the next item. If we are at the end of items // then NULL is returned. virtual const char * operator()(void); } ; // Abstract class for a rewindable OptIter // class OptIterRwd : public OptIter { public: OptIterRwd(void); virtual ~OptIterRwd(void); virtual const char * curr(void) = 0; virtual void next(void) = 0; virtual const char * operator()(void) = 0; // rewind() resets the "current-element" to the first one in the "list" virtual void rewind(void) = 0; } ; // Class to iterate through an array of tokens. The array may be terminated // by NULL or a count containing the number of tokens may be given. // class OptArgvIter : public OptIterRwd { public: OptArgvIter(const char * const argv[]); OptArgvIter(int argc, const char * const argv[]); virtual ~OptArgvIter(void); virtual const char * curr(void); virtual void next(void); virtual const char * operator()(void); virtual void rewind(void); // index returns the current index to use for argv[] int index(void); } ; // Class to iterate through a string containing delimiter-separated tokens // class OptStrTokIter : public OptIterRwd { public: OptStrTokIter(const char * tokens, const char * delimiters =0); virtual ~OptStrTokIter(void); virtual const char * curr(void); virtual void next(void); virtual const char * operator()(void); virtual void rewind(void); // delimiters() with NO arguments returns the current set of delimiters, // If an argument is given then it is used as the new set of delimiters. const char * delimiters(void); void delimiters(const char * delims); } ; // OptIstreamIter is a class for iterating over arguments that come // from an input stream. Each line of the input stream is considered // to be a set of white-space separated tokens. If the the first // non-white character on a line is '#' ('!' for VMS systems) then // the line is considered a comment and is ignored. // // *Note:: If a line is more than 1022 characters in length then we // treat it as if it were several lines of length 1022 or less. // // *Note:: The string tokens returned by this iterator are pointers // to temporary buffers which may not necessarily stick around // for too long after the call to curr() or operator(), hence // if you need the string value to persist - you will need to // make a copy. // class OptIstreamIter : public OptIter { private: istream & is ; OptStrTokIter * tok_iter ; void fill(void); public: static const unsigned MAX_LINE_LEN ; #ifdef vms enum { c_COMMENT = '!' } ; #else enum { c_COMMENT = '#' } ; #endif OptIstreamIter(istream & input); virtual ~OptIstreamIter(void); virtual const char * curr(void); virtual void next(void); virtual const char * operator()(void); } ; // Class to hold option-specifications and parsing-specifications // class Options { public: // Flags to control parsing behavior enum OptCtrl { DEFAULT = 0x00, // Default setting ANYCASE = 0x01, // Ignore case when matching short-options QUIET = 0x02, // Dont print error messages PLUS = 0x04, // Allow "+" as a long-option prefix SHORT_ONLY = 0x08, // Dont accept long-options LONG_ONLY = 0x10, // Dont accept short-options // (also allows "-" as a long-option prefix). NOGUESSING = 0x20, // Normally, when we see a short (long) option // on the command line that doesnt match any // known short (long) options, then we try to // "guess" by seeing if it will match any known // long (short) option. Setting this mask prevents // this "guessing" from occurring. } ; // Error return values for operator() enum OptRC { ENDOPTS = 0, BADCHAR = -1, BADKWD = -2, AMBIGUOUS = -3 } ; Options(const char * name, const char * const optv[]); virtual ~Options(void); // name() returns the command name const char * name(void) const; // ctrls() (with 1 argument) sets new control settings unsigned ctrls(void) const; // ctrls() (with no arguments) returns the existing control settings. void ctrls(unsigned newctrls); // usage() prints options usage (followed by any positional arguments // given) on the given outstream void usage(ostream & os, const char * positionals) const ; // operator() iterates through the arguments as necessary (using the // given iterator) and returns the character value of the option // (or long-option) that it matched. If the option has a value // then the value given may be found in optarg (otherwise optarg // will be NULL). // // 0 is returned upon end-of-options. At this point, "iter" may // be used to process any remaining positional parameters. // // If an invalid option is found then BADCHAR is returned and *optarg // is the unrecognized option character. // // If an invalid long-option is found then BADKWD is returned and optarg // points to the bad long-option. // // If an ambiguous long-option is found then AMBIGUOUS is returned and // optarg points to the ambiguous long-option. // // Unless Options::QUIET is used, missing option-arguments and // invalid options (and the like) will automatically cause error // messages to be issued to cerr. int operator()(OptIter & iter, const char * & optarg) ; // Call this member function after operator() has returned 0 // if you want to know whether or not options were explicitly // terminated because "--" appeared on the command-line. // int explicit_endopts() const ; } ;
The Options constructor expects a command-name (usually argv[0]) and a pointer to an array of strings. The last element in this array must be NULL. Each non-NULL string in the array must have the following format:
The 1st character must be the option-name (`c' for a -c option).
The 2nd character must be one of `|', `?', `:', `*', or `+'.
`|' indicates that the option takes no argument;
`?' indicates that the option takes an optional argument;
`:' indicates that the option takes a required argument;
`*' indicates that the option takes zero or more arguments;
`+' indicates that the option takes one or more arguments;
The remainder of the string must be the long-option name.
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).
If the second 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).
const char * optv[] = { "c:count <number>", "s?str <string>", "x", " |hello", "g+groups <newsgroup>", NULL } ;
optv[] now corresponds to the following:
Long-option names are matched case-insensitive and only a unique prefix of the name needs to be specified.
Option-name characters are case-sensitive!
#include <stdlib.h> #include <iostream.h> #include <options.h> static const char * optv[] = { "H|help", "c:count <number>", "s?str <string>", "x", " |hello", "g+groups <newsgroup>", NULL } ; main(int argc, char * argv[]) { int optchar; const char * optarg; const char * str = "default_string"; int count = 0, xflag = 0, hello = 0; int errors = 0, ngroups = 0; Options opts(*argv, optv); OptArgvIter iter(--argc, ++argv); while( optchar = opts(iter, optarg) ) { switch (optchar) { case 'H' : opts.usage("files ...", cout); 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; } //switch } if (errors || (iter.index() == argc)) { if (! errors) { cerr << opts.name() << ": no filenames given." << endl ; } opts.usage("files ...", cerr); exit(1); } cout << "xflag=" << ((xflag) ? "ON" : "OFF") << endl << "hello=" << ((hello) ? "YES" : "NO") << endl << "count=" << count << endl << "str= << "ngroups=" << ngroups << endl ; if (iter.index() < argc) { cout << "files=" ; for (int i = iter.index() ; i < argc ; i++) { cout << " } cout << endl ; } }
If short-option processing is disabled, then the prefix ``-'' may be used to indicate a long-option (the ``--'' prefix will still be accepted).
A multi-valued option is terminated by another option or by the end-of options. The following are all equivalent (if -l is a multi-valued option and -x is an option that takes no value):
cmdname -x -l item1 item2 item3 -- arg1 arg2 arg3 cmdname -x -litem1 -litem2 -litem3 -- arg1 arg2 arg3 cmdname -l item1 item2 item3 -x arg1 arg2 arg3
When compiling your source (on Unix systems), you may need to use the -I/usr/local/include option. When linking your objects, you may need to use the -L/usr/local/lib option in conjunction with the -loptions option.