457 lines
16 KiB
C++
457 lines
16 KiB
C++
// ****************************************************************************
|
|
// ^FILE: options.h - option parsing classes
|
|
//
|
|
// ^DESCRIPTION:
|
|
// This file defines classes used to parse command-line options.
|
|
// Options may be parsed from an array of strings, or from any structure
|
|
// for which a corresponding option-iterator exists.
|
|
//
|
|
// ^HISTORY:
|
|
// 03/06/92 Brad Appleton <bradapp@enteract.com> Created
|
|
//
|
|
// 03/23/93 Brad Appleton <bradapp@enteract.com>
|
|
// - Added OptIstreamIter class
|
|
//
|
|
// 03/08/94 Brad Appleton <bradapp@enteract.com>
|
|
// - Added Options::reset() member function
|
|
//
|
|
// 07/31/97 Brad Appleton <bradapp@enteract.com>
|
|
// - Added PARSE_POS control flag and POSITIONAL return value
|
|
//
|
|
// 04/30/06 Chris Reed
|
|
// - Updated to modern C++ and STL
|
|
// - Converted comments to doxygen style
|
|
// ^^**************************************************************************
|
|
|
|
#ifndef _options_h
|
|
#define _options_h
|
|
|
|
#ifdef USE_STDIO
|
|
#include <stdio.h>
|
|
#else
|
|
#include <iostream>
|
|
#endif
|
|
|
|
//! 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
|
|
{
|
|
private:
|
|
int ndx; // index of current arg
|
|
int ac; // arg count
|
|
const char *const *av; // arg vector
|
|
|
|
public:
|
|
OptArgvIter(const char *const argv[])
|
|
: av(argv)
|
|
, ac(-1)
|
|
, ndx(0)
|
|
{
|
|
}
|
|
|
|
OptArgvIter(int argc, const char *const argv[])
|
|
: av(argv)
|
|
, ac(argc)
|
|
, ndx(0)
|
|
{
|
|
}
|
|
|
|
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) { return ndx; }
|
|
};
|
|
|
|
//! Class to iterate through a string containing delimiter-separated tokens
|
|
//!
|
|
class OptStrTokIter : public OptIterRwd
|
|
{
|
|
private:
|
|
unsigned len; // length of token-string
|
|
const char *str; // the token-string
|
|
const char *seps; // delimiter-set (separator-characters)
|
|
const char *cur; // current token
|
|
char *tokstr; // our copy of the token-string
|
|
|
|
static const char *default_delims; // default delimiters = whitespace
|
|
|
|
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) { return seps; }
|
|
void delimiters(const char *delims) { seps = (delims) ? delims : default_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:
|
|
std::istream &is;
|
|
OptStrTokIter *tok_iter;
|
|
|
|
void fill(void);
|
|
|
|
public:
|
|
static const unsigned MAX_LINE_LEN;
|
|
|
|
OptIstreamIter(std::istream &input);
|
|
|
|
virtual ~OptIstreamIter(void);
|
|
|
|
virtual const char *curr(void);
|
|
|
|
virtual void next(void);
|
|
|
|
virtual const char *operator()(void);
|
|
};
|
|
|
|
//! \brief parse command-line options
|
|
//!
|
|
//! \section Synopsis
|
|
//! \code
|
|
//! #include <options.h>
|
|
//!
|
|
//! Options opts(cmdname, optv);
|
|
//! char cmdname[], *optv[];
|
|
//! \endcode
|
|
//! \section Description
|
|
//! 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 0 or more arguments;
|
|
//! '+' -- indicates that the option takes 1 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 & 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").
|
|
//!
|
|
//! \section Exceptions Exceptions to the above
|
|
//! 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).
|
|
//!
|
|
//! \code
|
|
//! const char * optv[] = {
|
|
//! "c:count <number>",
|
|
//! "s?str <string>",
|
|
//! "x",
|
|
//! " |hello",
|
|
//! "g+groups <newsgroup>",
|
|
//! NULL
|
|
//! } ;
|
|
//! \endcode
|
|
//! optv[] now corresponds to the following:
|
|
//!
|
|
//! usage: cmdname [-c|--count <number>] [-s|--str [<string>]]
|
|
//! [-x] [--hello] [-g|--groups <newsgroup> ...]
|
|
//!
|
|
//! 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!
|
|
//!
|
|
//! \section Caveat
|
|
//! Because of the way in which multi-valued options and options with optional
|
|
//! values are handled, it is NOT possible to supply a value to an option in
|
|
//! a separate argument (different argv[] element) if the value is OPTIONAL
|
|
//! and begins with a '-'. What this means is that if an option "-s" takes an
|
|
//! optional value value and you wish to supply a value of "-foo" then you must
|
|
//! specify this on the command-line as "-s-foo" instead of "-s -foo" because
|
|
//! "-s -foo" will be considered to be two separate sets of options.
|
|
//!
|
|
//! 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
|
|
//!
|
|
//!
|
|
//! \code
|
|
//! #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(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;
|
|
//! } //!switch
|
|
//! }
|
|
//!
|
|
//! if (errors || (iter.index() == argc)) {
|
|
//! if (! errors) {
|
|
//! cerr << opts.name() << ": no filenames given." << endl ;
|
|
//! }
|
|
//! opts.usage(cerr, "files ...");
|
|
//! exit(1);
|
|
//! }
|
|
//!
|
|
//! cout << "xflag=" << ((xflag) ? "ON" : "OFF") << endl
|
|
//! << "hello=" << ((hello) ? "YES" : "NO") << endl
|
|
//! << "count=" << count << endl
|
|
//! << "str=\"" << ((str) ? str : "No value given!") << "\"" << endl
|
|
//! << "ngroups=" << ngroups << endl ;
|
|
//!
|
|
//! if (iter.index() < argc) {
|
|
//! cout << "files=" ;
|
|
//! for (int i = iter.index() ; i < argc ; i++) {
|
|
//! cout << "\"" << argv[i] << "\" " ;
|
|
//! }
|
|
//! cout << endl ;
|
|
//! }
|
|
//! }
|
|
//! \endcode
|
|
class Options
|
|
{
|
|
private:
|
|
unsigned explicit_end : 1; //!< were we terminated because of "--"?
|
|
unsigned optctrls : 7; //!< control settings (a set of OptCtrl masks)
|
|
const char *const *optvec; //!< vector of option-specifications (last=NULL)
|
|
const char *nextchar; //!< next option-character to process
|
|
const char *listopt; //!< last list-option we matched
|
|
const char *cmdname; //!< name of the command
|
|
|
|
void check_syntax(void) const;
|
|
|
|
const char *match_opt(char opt, int ignore_case = 0) const;
|
|
|
|
const char *match_longopt(const char *opt, int len, int &ambiguous) const;
|
|
|
|
int parse_opt(OptIter &iter, const char *&optarg);
|
|
|
|
int parse_longopt(OptIter &iter, const char *&optarg);
|
|
|
|
public:
|
|
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.
|
|
PARSE_POS = 0x40 //!< By default, Options will not present positional
|
|
//!< command-line arguments to the user and will
|
|
//!< instead stop parsing when the first positonal
|
|
//!< argument has been encountered. If this flag
|
|
//!< is given, Options will present positional
|
|
//!< arguments to the user with a return code of
|
|
//!< POSITIONAL; ENDOPTS will be returned only
|
|
//!< when the end of the argument list is reached.
|
|
};
|
|
|
|
//! Error return values for operator()
|
|
//!
|
|
enum OptRC
|
|
{
|
|
ENDOPTS = 0,
|
|
BADCHAR = -1,
|
|
BADKWD = -2,
|
|
AMBIGUOUS = -3,
|
|
POSITIONAL = -4
|
|
};
|
|
|
|
Options(const char *name, const char *const optv[]);
|
|
|
|
virtual ~Options(void);
|
|
|
|
//! name() returns the command name
|
|
const char *name(void) const { return cmdname; }
|
|
//! ctrls() (with no arguments) returns the existing control settings
|
|
unsigned ctrls(void) const { return optctrls; }
|
|
//! ctrls() (with 1 argument) sets new control settings
|
|
void ctrls(unsigned newctrls) { optctrls = newctrls; }
|
|
//! reset for another pass to parse for options
|
|
void reset(void) { nextchar = listopt = NULL; }
|
|
//! usage() prints options usage (followed by any positional arguments
|
|
//! listed in the parameter "positionals") on the given outstream
|
|
void usage(std::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 the
|
|
//! PARSE_POS control-flag is set then 0 is returned only when all
|
|
//! arguments in "iter" have been exhausted.
|
|
//!
|
|
//! 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.
|
|
//!
|
|
//! If the PARSE_POS control-flag is set then POSITIONAL is returned
|
|
//! when a positional argument is encountered and optarg points to
|
|
//! the positonal argument (and "iter" is advanced to the next argument
|
|
//! in the iterator).
|
|
//!
|
|
//! 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 { return explicit_end; }
|
|
};
|
|
|
|
#endif /* _options_h */
|