| | 1 | /* This file defines classes used to parse command-line options. |
| | 2 | * Options may be parsed from an array of strings, or from any structure |
| | 3 | * for which a corresponding option-iterator exists. |
| | 4 | * |
| | 5 | * This file is part of Brad Appleton's "Options" library, slightly |
| | 6 | * modified to be more portable. Everything in this file is subject to |
| | 7 | * the following legal blurb: |
| | 8 | * |
| | 9 | * Copyright (C) Brad Appleton. |
| | 10 | * |
| | 11 | * AUTHOR |
| | 12 | * ====== |
| | 13 | * Brad Appleton, Software Tools Developer |
| | 14 | * mailto:bradapp@enteract.com |
| | 15 | * http://www.enteract.com/~bradapp |
| | 16 | * |
| | 17 | * COPY/REUSE POLICY |
| | 18 | * ================= |
| | 19 | * Permission is hereby granted to freely copy and redistribute |
| | 20 | * this software, provided that the author is clearly credited in |
| | 21 | * all copies and derivations. Neither the names of the authors nor |
| | 22 | * that of their employers may be used to endorse or promote |
| | 23 | * products derived from this software without specific written |
| | 24 | * permission. |
| | 25 | * |
| | 26 | * DISCLAIMER |
| | 27 | * ========== |
| | 28 | * This software is provided "As Is" and without any express or |
| | 29 | * implied warranties. Neither the authors nor any of their |
| | 30 | * employers (including any of their subsidiaries and subdivisions) |
| | 31 | * are responsible for maintaining or supporting this software or |
| | 32 | * for any consequences resulting from the use of this software, no |
| | 33 | * matter how awful, even if they arise from flaws in the software. |
| | 34 | */ |
| | 35 | #ifndef OPTIONS_H |
| | 36 | #define OPTIONS_H |
| | 37 | |
| | 38 | class istream; |
| | 39 | |
| | 40 | class ostream { |
| | 41 | public: |
| | 42 | ostream(FILE * fileptr) : fp(fileptr) {} |
| | 43 | |
| | 44 | ostream & |
| | 45 | operator<<(char ch); |
| | 46 | |
| | 47 | ostream & |
| | 48 | operator<<(const char * str); |
| | 49 | |
| | 50 | ostream & |
| | 51 | write(const char * buf, unsigned bufsize); |
| | 52 | |
| | 53 | private: |
| | 54 | FILE * fp; |
| | 55 | } ; |
| | 56 | |
| | 57 | // Abstract class to iterate through options/arguments |
| | 58 | // |
| | 59 | class OptIter { |
| | 60 | public: |
| | 61 | OptIter(void) {} |
| | 62 | virtual ~OptIter(void); |
| | 63 | |
| | 64 | // curr() returns the current item in the iterator without |
| | 65 | // advancing on to the next item. If we are at the end of items |
| | 66 | // then NULL is returned. |
| | 67 | virtual const char * |
| | 68 | curr(void) = 0; |
| | 69 | |
| | 70 | // next() advances to the next item. |
| | 71 | virtual void |
| | 72 | next(void) = 0; |
| | 73 | |
| | 74 | // operator() returns the current item in the iterator and then |
| | 75 | // advances on to the next item. If we are at the end of items |
| | 76 | // then NULL is returned. |
| | 77 | virtual const char * |
| | 78 | operator()(void); |
| | 79 | } ; |
| | 80 | |
| | 81 | // Abstract class for a rewindable OptIter |
| | 82 | // |
| | 83 | class OptIterRwd : public OptIter { |
| | 84 | public: |
| | 85 | OptIterRwd(void); |
| | 86 | |
| | 87 | virtual ~OptIterRwd(void); |
| | 88 | |
| | 89 | virtual const char * |
| | 90 | curr(void) = 0; |
| | 91 | |
| | 92 | virtual void |
| | 93 | next(void) = 0; |
| | 94 | |
| | 95 | virtual const char * |
| | 96 | operator()(void) = 0; |
| | 97 | |
| | 98 | // rewind() resets the "current-element" to the first one in the "list" |
| | 99 | virtual void |
| | 100 | rewind(void) = 0; |
| | 101 | } ; |
| | 102 | |
| | 103 | // Class to iterate through an array of tokens. The array may be terminated |
| | 104 | // by NULL or a count containing the number of tokens may be given. |
| | 105 | // |
| | 106 | class OptArgvIter : public OptIterRwd { |
| | 107 | private: |
| | 108 | int ndx; // index of current arg |
| | 109 | int ac; // arg count |
| | 110 | const char * const * av; // arg vector |
| | 111 | |
| | 112 | public: |
| | 113 | OptArgvIter(const char * const argv[]) |
| | 114 | : ndx(0), ac(-1), av(argv) {} |
| | 115 | |
| | 116 | OptArgvIter(int argc, const char * const argv[]) |
| | 117 | : ndx(0), ac(argc), av(argv) {} |
| | 118 | |
| | 119 | virtual |
| | 120 | ~OptArgvIter(void); |
| | 121 | |
| | 122 | virtual const char * |
| | 123 | curr(void); |
| | 124 | |
| | 125 | virtual void |
| | 126 | next(void); |
| | 127 | |
| | 128 | virtual const char * |
| | 129 | operator()(void); |
| | 130 | |
| | 131 | virtual void |
| | 132 | rewind(void); |
| | 133 | |
| | 134 | // index returns the current index to use for argv[] |
| | 135 | int index(void) { return ndx; } |
| | 136 | } ; |
| | 137 | |
| | 138 | |
| | 139 | // Class to iterate through a string containing delimiter-separated tokens |
| | 140 | // |
| | 141 | class OptStrTokIter : public OptIterRwd { |
| | 142 | private: |
| | 143 | unsigned len; // length of token-string |
| | 144 | const char * str; // the token-string |
| | 145 | const char * seps; // delimiter-set (separator-characters) |
| | 146 | const char * cur; // current token |
| | 147 | char * tokstr; // our copy of the token-string |
| | 148 | |
| | 149 | static const char * default_delims; // default delimiters = whitespace |
| | 150 | |
| | 151 | public: |
| | 152 | OptStrTokIter(const char * tokens, const char * delimiters =0); |
| | 153 | |
| | 154 | virtual |
| | 155 | ~OptStrTokIter(void); |
| | 156 | |
| | 157 | virtual const char * |
| | 158 | curr(void); |
| | 159 | |
| | 160 | virtual void |
| | 161 | next(void); |
| | 162 | |
| | 163 | virtual const char * |
| | 164 | operator()(void); |
| | 165 | |
| | 166 | virtual void |
| | 167 | rewind(void); |
| | 168 | |
| | 169 | // delimiters() with NO arguments returns the current set of delimiters, |
| | 170 | // If an argument is given then it is used as the new set of delimiters. |
| | 171 | const char * |
| | 172 | delimiters(void) { return seps; } |
| | 173 | |
| | 174 | void |
| | 175 | delimiters(const char * delims) { |
| | 176 | seps = (delims) ? delims : default_delims ; |
| | 177 | } |
| | 178 | } ; |
| | 179 | |
| | 180 | |
| | 181 | // OptIstreamIter is a class for iterating over arguments that come |
| | 182 | // from an input stream. Each line of the input stream is considered |
| | 183 | // to be a set of white-space separated tokens. If the the first |
| | 184 | // non-white character on a line is '#' ('!' for VMS systems) then |
| | 185 | // the line is considered a comment and is ignored. |
| | 186 | // |
| | 187 | // *Note:: If a line is more than 1022 characters in length then we |
| | 188 | // treat it as if it were several lines of length 1022 or less. |
| | 189 | // |
| | 190 | // *Note:: The string tokens returned by this iterator are pointers |
| | 191 | // to temporary buffers which may not necessarily stick around |
| | 192 | // for too long after the call to curr() or operator(), hence |
| | 193 | // if you need the string value to persist - you will need to |
| | 194 | // make a copy. |
| | 195 | // |
| | 196 | class OptIstreamIter : public OptIter { |
| | 197 | private: |
| | 198 | istream & is ; |
| | 199 | OptStrTokIter * tok_iter ; |
| | 200 | |
| | 201 | void |
| | 202 | fill(void); |
| | 203 | |
| | 204 | public: |
| | 205 | static const unsigned MAX_LINE_LEN ; |
| | 206 | |
| | 207 | OptIstreamIter(istream & input); |
| | 208 | |
| | 209 | virtual |
| | 210 | ~OptIstreamIter(void); |
| | 211 | |
| | 212 | virtual const char * |
| | 213 | curr(void); |
| | 214 | |
| | 215 | virtual void |
| | 216 | next(void); |
| | 217 | |
| | 218 | virtual const char * |
| | 219 | operator()(void); |
| | 220 | } ; |
| | 221 | |
| | 222 | |
| | 223 | // Now we are ready to define a class to declare and parse command-options |
| | 224 | // |
| | 225 | // |
| | 226 | // CLASS |
| | 227 | // ===== |
| | 228 | // Options -- parse command-line options |
| | 229 | // |
| | 230 | // SYNOPSIS |
| | 231 | // ======== |
| | 232 | // #include <options.h> |
| | 233 | // |
| | 234 | // Options opts(cmdname, optv); |
| | 235 | // char cmdname[], *optv[]; |
| | 236 | // |
| | 237 | // DESCRIPTION |
| | 238 | // =========== |
| | 239 | // The Options constructor expects a command-name (usually argv[0]) and |
| | 240 | // a pointer to an array of strings. The last element in this array MUST |
| | 241 | // be NULL. Each non-NULL string in the array must have the following format: |
| | 242 | // |
| | 243 | // The 1st character must be the option-name ('c' for a -c option). |
| | 244 | // |
| | 245 | // The 2nd character must be one of '|', '?', ':', '*', or '+'. |
| | 246 | // '|' -- indicates that the option takes NO argument; |
| | 247 | // '?' -- indicates that the option takes an OPTIONAL argument; |
| | 248 | // ':' -- indicates that the option takes a REQUIRED argument; |
| | 249 | // '*' -- indicates that the option takes 0 or more arguments; |
| | 250 | // '+' -- indicates that the option takes 1 or more arguments; |
| | 251 | // |
| | 252 | // The remainder of the string must be the long-option name. |
| | 253 | // |
| | 254 | // If desired, the long-option name may be followed by one or more |
| | 255 | // spaces and then by the name of the option value. This name will |
| | 256 | // be used when printing usage messages. If the option-value-name |
| | 257 | // is not given then the string "<value>" will be used in usage |
| | 258 | // messages. |
| | 259 | // |
| | 260 | // One may use a space to indicate that a particular option does not |
| | 261 | // have a corresponding long-option. For example, "c: " (or "c:") |
| | 262 | // means the -c option takes a value & has NO corresponding long-option. |
| | 263 | // |
| | 264 | // To specify a long-option that has no corresponding single-character |
| | 265 | // option is a bit trickier: Options::operator() still needs an "option- |
| | 266 | // character" to return when that option is matched. One may use a whitespace |
| | 267 | // character or a non-printable character as the single-character option |
| | 268 | // in such a case. (hence " |hello" would only match "--hello"). |
| | 269 | // |
| | 270 | // EXCEPTIONS TO THE ABOVE: |
| | 271 | // ------------------------ |
| | 272 | // If the 1st character of the string is '-', then the rest of the |
| | 273 | // string must correspond to the above format, and the option is |
| | 274 | // considered to be a hidden-option. This means it will be parsed |
| | 275 | // when actually matching options from the command-line, but will |
| | 276 | // NOT show-up if a usage message is printed using the usage() member |
| | 277 | // function. Such an example might be "-h|hidden". If you want to |
| | 278 | // use any "dummy" options (options that are not parsed, but that |
| | 279 | // to show up in the usage message), you can specify them along with |
| | 280 | // any positional parameters to the usage() member function. |
| | 281 | // |
| | 282 | // If the 2nd character of the string is '\0' then it is assumed |
| | 283 | // that there is no corresponding long-option and that the option |
| | 284 | // takes no argument (hence "f", and "f| " are equivalent). |
| | 285 | // |
| | 286 | // Examples: |
| | 287 | // const char * optv[] = { |
| | 288 | // "c:count <number>", |
| | 289 | // "s?str <string>", |
| | 290 | // "x", |
| | 291 | // " |hello", |
| | 292 | // "g+groups <newsgroup>", |
| | 293 | // NULL |
| | 294 | // } ; |
| | 295 | // |
| | 296 | // optv[] now corresponds to the following: |
| | 297 | // |
| | 298 | // usage: cmdname [-c|--count <number>] [-s|--str [<string>]] |
| | 299 | // [-x] [--hello] [-g|--groups <newsgroup> ...] |
| | 300 | // |
| | 301 | // Long-option names are matched case-insensitive and only a unique prefix |
| | 302 | // of the name needs to be specified. |
| | 303 | // |
| | 304 | // Option-name characters are case-sensitive! |
| | 305 | // |
| | 306 | // CAVEAT |
| | 307 | // ====== |
| | 308 | // Because of the way in which multi-valued options and options with optional |
| | 309 | // values are handled, it is NOT possible to supply a value to an option in |
| | 310 | // a separate argument (different argv[] element) if the value is OPTIONAL |
| | 311 | // and begins with a '-'. What this means is that if an option "-s" takes an |
| | 312 | // optional value value and you wish to supply a value of "-foo" then you must |
| | 313 | // specify this on the command-line as "-s-foo" instead of "-s -foo" because |
| | 314 | // "-s -foo" will be considered to be two separate sets of options. |
| | 315 | // |
| | 316 | // A multi-valued option is terminated by another option or by the end-of |
| | 317 | // options. The following are all equivalent (if "-l" is a multi-valued |
| | 318 | // option and "-x" is an option that takes no value): |
| | 319 | // |
| | 320 | // cmdname -x -l item1 item2 item3 -- arg1 arg2 arg3 |
| | 321 | // cmdname -x -litem1 -litem2 -litem3 -- arg1 arg2 arg3 |
| | 322 | // cmdname -l item1 item2 item3 -x arg1 arg2 arg3 |
| | 323 | // |
| | 324 | // |
| | 325 | // EXAMPLE |
| | 326 | // ======= |
| | 327 | // #include <options.h> |
| | 328 | // |
| | 329 | // static const char * optv[] = { |
| | 330 | // "H|help", |
| | 331 | // "c:count <number>", |
| | 332 | // "s?str <string>", |
| | 333 | // "x", |
| | 334 | // " |hello", |
| | 335 | // "g+groups <newsgroup>", |
| | 336 | // NULL |
| | 337 | // } ; |
| | 338 | // |
| | 339 | // main(int argc, char * argv[]) { |
| | 340 | // int optchar; |
| | 341 | // const char * optarg; |
| | 342 | // const char * str = "default_string"; |
| | 343 | // int count = 0, xflag = 0, hello = 0; |
| | 344 | // int errors = 0, ngroups = 0; |
| | 345 | // |
| | 346 | // Options opts(*argv, optv); |
| | 347 | // OptArgvIter iter(--argc, ++argv); |
| | 348 | // |
| | 349 | // while( optchar = opts(iter, optarg) ) { |
| | 350 | // switch (optchar) { |
| | 351 | // case 'H' : |
| | 352 | // opts.usage(cout, "files ..."); |
| | 353 | // exit(0); |
| | 354 | // break; |
| | 355 | // case 'g' : |
| | 356 | // ++ngroups; break; // the groupname is in "optarg" |
| | 357 | // case 's' : |
| | 358 | // str = optarg; break; |
| | 359 | // case 'x' : |
| | 360 | // ++xflag; break; |
| | 361 | // case ' ' : |
| | 362 | // ++hello; break; |
| | 363 | // case 'c' : |
| | 364 | // if (optarg == NULL) ++errors; |
| | 365 | // else count = (int) atol(optarg); |
| | 366 | // break; |
| | 367 | // default : ++errors; break; |
| | 368 | // } //switch |
| | 369 | // } |
| | 370 | // |
| | 371 | // if (errors || (iter.index() == argc)) { |
| | 372 | // if (! errors) { |
| | 373 | // cerr << opts.name() << ": no filenames given." << endl ; |
| | 374 | // } |
| | 375 | // opts.usage(cerr, "files ..."); |
| | 376 | // exit(1); |
| | 377 | // } |
| | 378 | // |
| | 379 | // cout << "xflag=" << ((xflag) ? "ON" : "OFF") << endl |
| | 380 | // << "hello=" << ((hello) ? "YES" : "NO") << endl |
| | 381 | // << "count=" << count << endl |
| | 382 | // << "str=\"" << ((str) ? str : "No value given!") << "\"" << endl |
| | 383 | // << "ngroups=" << ngroups << endl ; |
| | 384 | // |
| | 385 | // if (iter.index() < argc) { |
| | 386 | // cout << "files=" ; |
| | 387 | // for (int i = iter.index() ; i < argc ; i++) { |
| | 388 | // cout << "\"" << argv[i] << "\" " ; |
| | 389 | // } |
| | 390 | // cout << endl ; |
| | 391 | // } |
| | 392 | // } |
| | 393 | // |
| | 394 | class Options { |
| | 395 | private: |
| | 396 | unsigned explicit_end : 1; // were we terminated because of "--"? |
| | 397 | unsigned optctrls : 7; // control settings (a set of OptCtrl masks) |
| | 398 | const char * const * optvec; // vector of option-specifications (last=NULL) |
| | 399 | const char * nextchar; // next option-character to process |
| | 400 | const char * listopt; // last list-option we matched |
| | 401 | const char * cmdname; // name of the command |
| | 402 | |
| | 403 | void |
| | 404 | check_syntax(void) const; |
| | 405 | |
| | 406 | const char * |
| | 407 | match_opt(char opt, int ignore_case =0) const; |
| | 408 | |
| | 409 | const char * |
| | 410 | match_longopt(const char * opt, int len, int & ambiguous) const; |
| | 411 | |
| | 412 | int |
| | 413 | parse_opt(OptIter & iter, const char * & optarg); |
| | 414 | |
| | 415 | int |
| | 416 | parse_longopt(OptIter & iter, const char * & optarg); |
| | 417 | |
| | 418 | public: |
| | 419 | enum OptCtrl { |
| | 420 | DEFAULT = 0x00, // Default setting |
| | 421 | ANYCASE = 0x01, // Ignore case when matching short-options |
| | 422 | QUIET = 0x02, // Dont print error messages |
| | 423 | PLUS = 0x04, // Allow "+" as a long-option prefix |
| | 424 | SHORT_ONLY = 0x08, // Dont accept long-options |
| | 425 | LONG_ONLY = 0x10, // Dont accept short-options |
| | 426 | // (also allows "-" as a long-option prefix). |
| | 427 | NOGUESSING = 0x20, // Normally, when we see a short (long) option |
| | 428 | // on the command line that doesnt match any |
| | 429 | // known short (long) options, then we try to |
| | 430 | // "guess" by seeing if it will match any known |
| | 431 | // long (short) option. Setting this mask prevents |
| | 432 | // this "guessing" from occurring. |
| | 433 | PARSE_POS = 0x40 // By default, Options will not present positional |
| | 434 | // command-line arguments to the user and will |
| | 435 | // instead stop parsing when the first positonal |
| | 436 | // argument has been encountered. If this flag |
| | 437 | // is given, Options will present positional |
| | 438 | // arguments to the user with a return code of |
| | 439 | // POSITIONAL; ENDOPTS will be returned only |
| | 440 | // when the end of the argument list is reached. |
| | 441 | } ; |
| | 442 | |
| | 443 | // Error return values for operator() |
| | 444 | // |
| | 445 | enum OptRC { |
| | 446 | ENDOPTS = 0, |
| | 447 | BADCHAR = -1, |
| | 448 | BADKWD = -2, |
| | 449 | AMBIGUOUS = -3, |
| | 450 | POSITIONAL = -4 |
| | 451 | } ; |
| | 452 | |
| | 453 | Options(const char * name, const char * const optv[]); |
| | 454 | |
| | 455 | virtual |
| | 456 | ~Options(void); |
| | 457 | |
| | 458 | // name() returns the command name |
| | 459 | const char * |
| | 460 | name(void) const { return cmdname; } |
| | 461 | |
| | 462 | // ctrls() (with no arguments) returns the existing control settings |
| | 463 | unsigned |
| | 464 | ctrls(void) const { return optctrls; } |
| | 465 | |
| | 466 | // ctrls() (with 1 argument) sets new control settings |
| | 467 | void |
| | 468 | ctrls(unsigned newctrls) { optctrls = newctrls; } |
| | 469 | |
| | 470 | // reset for another pass to parse for options |
| | 471 | void |
| | 472 | reset(void) { nextchar = listopt = NULL; } |
| | 473 | |
| | 474 | // usage() prints options usage (followed by any positional arguments |
| | 475 | // listed in the parameter "positionals") on the given outstream |
| | 476 | void |
| | 477 | usage(ostream & os, const char * positionals) const ; |
| | 478 | |
| | 479 | // operator() iterates through the arguments as necessary (using the |
| | 480 | // given iterator) and returns the character value of the option |
| | 481 | // (or long-option) that it matched. If the option has a value |
| | 482 | // then the value given may be found in optarg (otherwise optarg |
| | 483 | // will be NULL). |
| | 484 | // |
| | 485 | // 0 is returned upon end-of-options. At this point, "iter" may |
| | 486 | // be used to process any remaining positional parameters. If the |
| | 487 | // PARSE_POS control-flag is set then 0 is returned only when all |
| | 488 | // arguments in "iter" have been exhausted. |
| | 489 | // |
| | 490 | // If an invalid option is found then BADCHAR is returned and *optarg |
| | 491 | // is the unrecognized option character. |
| | 492 | // |
| | 493 | // If an invalid long-option is found then BADKWD is returned and optarg |
| | 494 | // points to the bad long-option. |
| | 495 | // |
| | 496 | // If an ambiguous long-option is found then AMBIGUOUS is returned and |
| | 497 | // optarg points to the ambiguous long-option. |
| | 498 | // |
| | 499 | // If the PARSE_POS control-flag is set then POSITIONAL is returned |
| | 500 | // when a positional argument is encountered and optarg points to |
| | 501 | // the positonal argument (and "iter" is advanced to the next argument |
| | 502 | // in the iterator). |
| | 503 | // |
| | 504 | // Unless Options::QUIET is used, missing option-arguments and |
| | 505 | // invalid options (and the like) will automatically cause error |
| | 506 | // messages to be issued to cerr. |
| | 507 | int |
| | 508 | operator()(OptIter & iter, const char * & optarg) ; |
| | 509 | |
| | 510 | // Call this member function after operator() has returned 0 |
| | 511 | // if you want to know whether or not options were explicitly |
| | 512 | // terminated because "--" appeared on the command-line. |
| | 513 | // |
| | 514 | int |
| | 515 | explicit_endopts() const { return explicit_end; } |
| | 516 | } ; |
| | 517 | |
| | 518 | #endif /* OPTIONS_H */ |