home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume31 / options / part01 < prev    next >
Encoding:
Text File  |  1992-07-26  |  55.6 KB  |  1,797 lines

  1. Newsgroups: comp.sources.misc
  2. From: brad@hcx1.ssd.csd.harris.com (Brad Appleton)
  3. Subject:  v31i045:  options - C++ library for parsing Unix-style command-lines, Part01/02
  4. Message-ID: <csm-v31i045=options.205059@sparky.IMD.Sterling.COM>
  5. X-Md4-Signature: ea98ddf612be7937155e18e49d25efd5
  6. Date: Mon, 27 Jul 1992 01:52:21 GMT
  7. Approved: kent@sparky.imd.sterling.com
  8.  
  9. Submitted-by: brad@hcx1.ssd.csd.harris.com (Brad Appleton)
  10. Posting-number: Volume 31, Issue 45
  11. Archive-name: options/part01
  12. Environment: C++
  13.  
  14.     What follows are two different C++ packages for parsing command-line
  15.     arguments: "Options" and "CmdLine".  Options is a "bare-bones" package
  16.     that does just the basics with very few bells and whistles thrown in.
  17.     CmdLine on the other hand is a fully object-oriented approach to
  18.     command-line parsing that does more than most of you could ever want.
  19.     CmdLine also comes with a program named "cmdparse" (which is similar
  20.     to Parseargs) to parse arguments for your shell-scripts (including
  21.     perl and tcl).
  22.  
  23.     If you are the least bit interested (or in need of) C++ command-line
  24.     parsing, then please take a look at one (or both) of these packages.
  25.     Although I have gone through extensive alpha and beta testing, I am
  26.     very anxious to get feedback and comments (constructive of course)
  27.     from the C++ programming community at large.
  28.  
  29.  ============================================================================
  30.  
  31.  This is "Options", a C++ library for parsing Unix-style command-line options.
  32.  Options understands options and gnu-long-options and the parsing behavior is
  33.  somewhat configurable. See the documentation (or the file <options.h>) for a
  34.  complete description.
  35.  
  36.  You "declare" your options by declaring an array of strings like so:
  37.  
  38.         const char * optv[] = {
  39.             "c:count <number>",
  40.             "s?str   <string>",
  41.             "x|xmode",
  42.             NULL
  43.         } ;
  44.   
  45.  Note the character (one of ':', '?', '|', '*', or '+') between the short
  46.  and long name of the option. It specifies the option type:
  47.  
  48.         '|' -- indicates that the option takes NO argument;
  49.         '?' -- indicates that the option takes an OPTIONAL argument;
  50.         ':' -- indicates that the option takes a REQUIRED argument;
  51.         '*' -- indicates that the option takes 0 or more arguments;
  52.         '+' -- indicates that the option takes 1 or more arguments;
  53.  
  54.  Using the above example, optv[] now corresponds to the following:
  55.  
  56.         progname [-c <number>] [-s [<string>]] [-x]
  57.  
  58.  Using long-options, optv corresponds to the following ("-" or "+" may
  59.  be used instead of "--" as the prefix):
  60.   
  61.         progname [--count <number>] [--str [<string>]] [--xmode]
  62.  
  63.  Now you can iterate over your options like so:
  64.  
  65.       #include <stdlib.h>
  66.       #include <options.h>
  67.  
  68.       main(int argc, char *argv[]) {
  69.          Options  opts(*argv, optv);
  70.          OptArgvIter  iter(--argc, ++argv);
  71.          const char *optarg, *str = NULL;
  72.          int  errors = 0, xflag = 0, count = 1;
  73.       
  74.          while( char optchar = opts(iter, optarg) ) {
  75.             switch (optchar) {
  76.             case 's' :
  77.                str = optarg; break;
  78.             case 'x' :
  79.                ++xflag; break;
  80.             case 'c' :
  81.                if (optarg == NULL)  ++errors;
  82.                else  count = (int) atol(optarg);
  83.                break;
  84.             default :  ++errors; break;
  85.             } //switch
  86.          }
  87.          ...  // process the rest of the arguments in "iter"
  88.       }
  89.  
  90.  
  91.  
  92. Please look at the README file for further information!
  93.  
  94.  ______________________ "And miles to go before I sleep." ______________________
  95.  Brad Appleton                         Harris Corp., Computer Systems Division
  96.    Senior Software Engineer            2101 West Cypress Creek Road,  M/S 161 
  97.      brad@ssd.csd.harris.com           Fort Lauderdale, FL  33309-1892  USA
  98.        ...!uunet!travis!brad                 Phone: (305) 973-5190
  99.  ~~~~~~~~~~~~~~~~~~~~ Disclaimer: I said it, not my company! ~~~~~~~~~~~~~~~~~~~
  100. #! /bin/sh
  101. # This is a shell archive.  Remove anything before this line, then feed it
  102. # into a shell via "sh file" or similar.  To overwrite existing files,
  103. # type "sh file -c".
  104. # The tool that generated this appeared in the comp.sources.unix newsgroup;
  105. # send mail to comp-sources-unix@uunet.uu.net if you want that tool.
  106. # Contents:  README options.3 options.C testopts.C
  107. # Wrapped by kent@sparky on Sun Jul 26 20:47:43 1992
  108. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  109. echo If this archive is complete, you will see the following message:
  110. echo '          "shar: End of archive 1 (of 2)."'
  111. if test -f 'README' -a "${1}" != "-c" ; then 
  112.   echo shar: Will not clobber existing file \"'README'\"
  113. else
  114.   echo shar: Extracting \"'README'\" \(5133 characters\)
  115.   sed "s/^X//" >'README' <<'END_OF_FILE'
  116. X
  117. X WHAT IS THIS?
  118. X =============
  119. X This is "Options", a C++ library for parsing Unix-style command-line options.
  120. X Options understands options and gnu-long-options and the parsing behavior is
  121. X somewhat configurable. See the documentation (or the file <options.h>) for a
  122. X complete description.
  123. X
  124. X You "declare" your options by declaring an array of strings like so:
  125. X
  126. X        const char * optv[] = {
  127. X            "c:count <number>",
  128. X            "s?str   <string>",
  129. X            "x|xmode",
  130. X            NULL
  131. X        } ;
  132. X  
  133. X Note the character (one of ':', '?', '|', '*', or '+') between the short
  134. X and long name of the option. It specifies the option type:
  135. X
  136. X        '|' -- indicates that the option takes NO argument;
  137. X        '?' -- indicates that the option takes an OPTIONAL argument;
  138. X        ':' -- indicates that the option takes a REQUIRED argument;
  139. X        '*' -- indicates that the option takes 0 or more arguments;
  140. X        '+' -- indicates that the option takes 1 or more arguments;
  141. X
  142. X Using the above example, optv[] now corresponds to the following:
  143. X
  144. X        progname [-c <number>] [-s [<string>]] [-x]
  145. X
  146. X Using long-options, optv corresponds to the following ("-" or "+" may
  147. X be used instead of "--" as the prefix):
  148. X  
  149. X        progname [--count <number>] [--str [<string>]] [--xmode]
  150. X
  151. X Now you can iterate over your options like so:
  152. X
  153. X      #include <stdlib.h>
  154. X      #include <options.h>
  155. X
  156. X      main(int argc, char *argv[]) {
  157. X         Options  opts(*argv, optv);
  158. X         OptArgvIter  iter(--argc, ++argv);
  159. X         const char *optarg, *str = NULL;
  160. X         int  errors = 0, xflag = 0, count = 1;
  161. X      
  162. X         while( char optchar = opts(iter, optarg) ) {
  163. X            switch (optchar) {
  164. X            case 's' :
  165. X               str = optarg; break;
  166. X            case 'x' :
  167. X               ++xflag; break;
  168. X            case 'c' :
  169. X               if (optarg == NULL)  ++errors;
  170. X               else  count = (int) atol(optarg);
  171. X               break;
  172. X            default :  ++errors; break;
  173. X            } //switch
  174. X         }
  175. X         ...  // process the rest of the arguments in "iter"
  176. X      }
  177. X
  178. X
  179. X AUTHOR
  180. X ======
  181. X Brad Appleton                     Harris Corp., Computer Systems Division
  182. X   Senior Software Engineer        2101 West Cypress Creek Road,  M/S 161 
  183. X     brad@ssd.csd.harris.com       Fort Lauderdale, FL  33309-1892  USA
  184. X       ...!uunet!travis!brad              Phone: (305) 973-5190
  185. X
  186. X
  187. X COPY/REUSE POLICY
  188. X =================
  189. X Permission is hereby granted to freely copy and redistribute this
  190. X software, provided that the author is clearly credited in all copies
  191. X and derivations. Neither the names of the authors nor that of their
  192. X employers may be used to endorse or promote products derived from this
  193. X software without specific written permission.
  194. X
  195. X
  196. X DISCLAIMER
  197. X ==========
  198. X This software is provided ``As Is'' and without any express or implied
  199. X warranties.  Neither the authors nor any of their employers (including
  200. X any of their subsidiaries and subdivisions) are responsible for maintaining
  201. X or supporting this software or for any consequences resulting from the
  202. X use of this software, no matter how awful, even if they arise from flaws
  203. X in the software.
  204. X
  205. X
  206. X CONTENTS
  207. X ========
  208. X This release should contain the following files:
  209. X     Makefile -- the makefile
  210. X     README -- this file
  211. X     options.3 -- Unix manual page(s) for the Options class
  212. X     options.h -- include file and documentation for the Options class
  213. X     options.C -- the C++ source for the Options class
  214. X     testopts.C -- a test program for the Options class
  215. X
  216. X
  217. X REQUIREMENTS
  218. X ============
  219. X This software should compile on most Unix platforms with a C++ compiler
  220. X with little or no difficulty.
  221. X
  222. X
  223. X PORTING
  224. X =======
  225. X Options uses the AT&T C++ iostream library.  Beyond that, all the
  226. X #include files it uses are assumed to have the contents specified by
  227. X the ANSI-C standard and are assumed to have #ifdef __cplusplus statements
  228. X for when they are being included by C++ files.  Options assumes the
  229. X existence of the following system header files:
  230. X
  231. X         <stdarg.h>
  232. X         <stdlib.h>
  233. X         <string.h>
  234. X         <ctype.h>
  235. X         <iostream.h>
  236. X
  237. X  Other porting problems you may experience are as follows:
  238. X
  239. X    - you may need to use <varargs.h> instead of <stdarg.h>
  240. X
  241. X You will need to tweak the Makefile a tad in order to make it work
  242. X for your C++ compiler.
  243. X
  244. X
  245. X BUGS
  246. X ====
  247. X Please send all bug reports to Brad Appleton <brad@ssd.csd.harris.com>.
  248. X Dont forget to mention which version of Options you have and which
  249. X operating system and C++ compiler you are using.
  250. X
  251. X
  252. X ACKNOWLEDGEMENTS
  253. X ================
  254. X Options is a complete C++ re-write of an ANSI-C package named "getopts"
  255. X that I wrote for the FSF that was posted on one of the "gnu" newsgroups
  256. X (I think it was the group gnu.utils.bug) in February of 1992.  Many thanks
  257. X to David J. MacKenzie for his input.  Options provides may more features
  258. X than "getopts" did.
  259. X
  260. X
  261. X PATCHLEVEL
  262. X ==========
  263. X The is release 1 of Options at patchlevel 0.
  264. X
  265. X
  266. X HISTORY
  267. X =======
  268. X
  269. X 07/21/92        Brad Appleton        <brad@ssd.csd.harris.com>
  270. X -----------------------------------------------------------------------------
  271. X First release.
  272. X
  273. END_OF_FILE
  274.   if test 5133 -ne `wc -c <'README'`; then
  275.     echo shar: \"'README'\" unpacked with wrong size!
  276.   fi
  277.   # end of 'README'
  278. fi
  279. if test -f 'options.3' -a "${1}" != "-c" ; then 
  280.   echo shar: Will not clobber existing file \"'options.3'\"
  281. else
  282.   echo shar: Extracting \"'options.3'\" \(14308 characters\)
  283.   sed "s/^X//" >'options.3' <<'END_OF_FILE'
  284. X.\"---------- TO PRINT, USE: {n,t}roff -man file ----------
  285. X.if n .po 1
  286. X.if n .ll 78
  287. X.\"-----------------------------------
  288. X.ds NM \f4Options\fP
  289. X.de XS
  290. X. RS
  291. X. nf
  292. X. ft 4
  293. X..
  294. X.de XE
  295. X. ft R
  296. X. fi
  297. X. RE
  298. X..
  299. X.ds -- \-\|\-
  300. X.ds C+ C\s-2\v'-1.5p'\(pl\(pl\v'+1.5p'\s+2
  301. X.ds .. .\^.\^.
  302. X.\"-------------------------------------------
  303. X.\" The following "strings" should correspond to where the CmdLine files
  304. X.\" have been installed.  "i" is where the include files where installed
  305. X.\" and "l" is where the object-library is installed. You should change
  306. X.\" the definition of these macros if they are not installed in the 
  307. X.\" places indicated below.
  308. X.\"-------------------------------------------
  309. X.ds i /usr/local/include
  310. X.ds l /usr/local/lib
  311. X.\"-----------------------------------
  312. X.TH Options 3\*(C+
  313. X.\"-----------------------------------
  314. X.SH NAME
  315. XOptions  \- A \*(C+ class for parsing command-line options
  316. X.\"-----------------------------------
  317. X.SH SYNOPSIS
  318. XThe following are all defined in the include file \f4<options.h>\fP:
  319. X
  320. X.nf
  321. X.ft 4
  322. X.ps -2
  323. X// Abstract class to iterate through options/arguments
  324. X//
  325. Xclass OptIter {
  326. Xpublic:
  327. X   OptIter(void);
  328. X   virtual ~OptIter(void);
  329. X
  330. X.vs -2
  331. X      // curr() returns the current item in the iterator without
  332. X      // advancing on to the next item. If we are at the end of items
  333. X      // then NULL is returned.
  334. X.vs +2
  335. X   virtual const char * curr(void) = 0;
  336. X
  337. X      // next() advances to the next item.
  338. X   virtual void next(void) = 0;
  339. X
  340. X.vs -2
  341. X      // operator() returns the current item in the iterator and then
  342. X      // advances on to the next item. If we are at the end of items
  343. X      // then NULL is returned.
  344. X.vs +2
  345. X   virtual const char * operator()(void);
  346. X} ;
  347. X
  348. X// Abstract class for a rewindable OptIter
  349. X//
  350. Xclass OptIterRwd : public OptIter {
  351. Xpublic:
  352. X   OptIterRwd(void);
  353. X   virtual ~OptIterRwd(void);
  354. X
  355. X   virtual const char * curr(void) = 0;
  356. X   virtual void next(void) = 0;
  357. X   virtual const char * operator()(void) = 0;
  358. X
  359. X      // rewind() resets the "current-element" to the first one in the "list"
  360. X   virtual void rewind(void) = 0;
  361. X} ;
  362. X
  363. X.vs -2
  364. X// Class to iterate through an array of tokens. The array may be terminated
  365. X// by NULL or a count containing the number of tokens may be given.
  366. X//
  367. X.vs +2
  368. Xclass OptArgvIter : public OptIterRwd {
  369. Xpublic:
  370. X   OptArgvIter(const char * const argv[]);
  371. X   OptArgvIter(int argc, const char * const argv[]);
  372. X   virtual ~OptArgvIter(void);
  373. X   virtual const char * curr(void);
  374. X   virtual void next(void);
  375. X   virtual const char * operator()(void);
  376. X   virtual void rewind(void);
  377. X
  378. X      // index returns the current index to use for argv[]
  379. X   int index(void);
  380. X} ;
  381. X
  382. X
  383. X// Class to iterate through a string containing delimiter-separated tokens
  384. X//
  385. Xclass OptStrTokIter : public OptIterRwd {
  386. Xpublic:
  387. X   OptStrTokIter(const char * tokens, const char * delimiters =0);
  388. X   virtual ~OptStrTokIter(void);
  389. X   virtual const char * curr(void);
  390. X   virtual void next(void);
  391. X   virtual const char * operator()(void);
  392. X   virtual void rewind(void);
  393. X
  394. X.vs -2
  395. X      // delimiters() with NO arguments returns the current set of delimiters,
  396. X      // If an argument is given then it is used as the new set of delimiters.
  397. X.vs +2
  398. X   const char * delimiters(void);
  399. X   void delimiters(const char * delims);
  400. X} ;
  401. X
  402. X
  403. X.vs -2
  404. X// Class to hold option-specifications and parsing-specifications
  405. X//
  406. X.vs +2
  407. Xclass Options {
  408. Xpublic:
  409. X      // Flags to control parsing behavior
  410. X   enum OptCtrl {
  411. X      DEFAULT    = 0x00,  // Default setting
  412. X      ANYCASE    = 0x01,  // Ignore case when matching short-options
  413. X      QUIET      = 0x02,  // Dont print error messages
  414. X      PLUS       = 0x04,  // Allow "+" as a long-option prefix
  415. X      SHORT_ONLY = 0x08,  // Dont accept long-options
  416. X      LONG_ONLY  = 0x10,  // Dont accept short-options
  417. X.vs -2
  418. X                            // (also allows "-" as a long-option prefix).
  419. X.vs +2
  420. X      NOGUESSING = 0x20,  // Normally, when we see a short (long) option
  421. X.vs -2
  422. X                            // on the command line that doesnt match any
  423. X                            // known short (long) options, then we try to
  424. X                            // "guess" by seeing if it will match any known
  425. X                            // long (short) option. Setting this mask prevents
  426. X                            // this "guessing" from occurring.
  427. X.vs +2
  428. X   } ;
  429. X
  430. X      // Error return values for operator()
  431. X   enum OptRC { ENDOPTS = 0, BADCHAR = -1, BADKWD = -2, AMBIGUOUS = -3 } ;
  432. X
  433. X   Options(const char * name, const char * const optv[]);
  434. X
  435. X   virtual ~Options(void);
  436. X
  437. X      // name() returns the command name
  438. X   const char * name(void) const;
  439. X
  440. X      // ctrls() (with 1 argument) sets new control settings
  441. X   unsigned ctrls(void) const;
  442. X
  443. X      // ctrls() (with no arguments) returns the existing control settings.
  444. X   void ctrls(unsigned newctrls);
  445. X
  446. X.vs -2
  447. X      // usage() prints options usage (followed by any positional arguments
  448. X      // given) on the given outstream
  449. X.vs +2
  450. X   void usage(ostream & os, const char * positionals) const ;
  451. X
  452. X.vs -2
  453. X      // operator() iterates through the arguments as necessary (using the
  454. X      // given iterator) and returns the character value of the option
  455. X      // (or long-option) that it matched. If the option has a value
  456. X      // then the value given may be found in optarg (otherwise optarg
  457. X      // will be NULL).
  458. X      //
  459. X      // 0 is returned upon end-of-options. At this point, "iter" may
  460. X      // be used to process any remaining positional parameters.
  461. X      //
  462. X      // If an invalid option is found then BADCHAR is returned and *optarg
  463. X      // is the unrecognized option character.
  464. X      //
  465. X      // If an invalid long-option is found then BADKWD is returned and optarg
  466. X      // points to the bad long-option.
  467. X      //
  468. X      // If an ambiguous long-option is found then AMBIGUOUS is returned and
  469. X      // optarg points to the ambiguous long-option.
  470. X      //
  471. X      // Unless Options::QUIET is used, missing option-arguments and
  472. X      // invalid options (and the like) will automatically cause error
  473. X      // messages to be issued to cerr.
  474. X.vs +2
  475. X   int operator()(OptIter & iter, const char * & optarg) ;
  476. X
  477. X.vs -2
  478. X      // Call this member function after operator() has returned 0
  479. X      // if you want to know whether or not options were explicitly
  480. X      // terminated because "--" appeared on the command-line.
  481. X      //
  482. X.vs +2
  483. X   int
  484. X   explicit_endopts() const ;
  485. X} ;
  486. X
  487. X.ps +2
  488. X.ft R
  489. X.fi
  490. X.\"-----------------------------------
  491. X.SH DESCRIPTION
  492. X.PP
  493. XThe Options constructor expects a command-name (usually \f4argv[0]\fP) and
  494. Xa pointer to an array of strings.  The last element in this array \fImust\fP
  495. Xbe NULL. Each non-NULL string in the array must have the following format:
  496. X
  497. X.in +5
  498. XThe 1st character must be the option-name (`c' for a \fB\-c\fP option).
  499. X
  500. XThe 2nd character must be one of `\f4|\fP', `?', `:', `*', or `+'.
  501. X
  502. X.in +3
  503. X`\f4|\fP'    \tindicates that the option takes \fIno\fP argument;
  504. X.br
  505. X`?'    \tindicates that the option takes an \fIoptional\fP argument;
  506. X.br
  507. X`:'    \tindicates that the option takes a \fIrequired\fP argument;
  508. X.br
  509. X`*'    \tindicates that the option takes \fIzero or more\fP arguments;
  510. X.br
  511. X`+'    \tindicates that the option takes \fIone or more\fP arguments;
  512. X.br
  513. X.in -3
  514. X
  515. XThe remainder of the string must be the long-option name.
  516. X
  517. X.PP
  518. XIf desired, the long-option name may be followed by one or more
  519. Xspaces and then by the name of the option value. This name will
  520. Xbe used when printing usage messages. If the option-value-name
  521. Xis not given then the string "<value>" will be used in usage
  522. Xmessages.
  523. X
  524. X.PP
  525. XOne may use a space to indicate that a particular option does not
  526. Xhave a corresponding long-option.  For example, \f4"c: "\fP (or \f4"c:"\fP)
  527. Xmeans the \fB\-c\fP option takes a value and has \fIno\fP corresponding
  528. Xlong-option.
  529. X
  530. X.PP
  531. XTo specify a long-option that has no corresponding single-character
  532. Xoption is a bit trickier: \f4Options::operator()\fP still needs an
  533. X``\fIoption character\fP'' to return when that option is matched. One may
  534. Xuse a whitespace character or a non-printable character as the
  535. Xsingle-character option in such a case. (hence \f4" |hello"\fP would
  536. Xonly match \fB\-\|\-hello\fP).
  537. X.in -5
  538. X
  539. X.SS Exceptions to the Above:
  540. X.PP
  541. XIf the second character of the string is \f4'\\0'\fP then it is assumed
  542. Xthat there is no corresponding long-option and that the option
  543. Xtakes no argument (hence \f4"f"\fP, and \f4"f| "\fP are equivalent).
  544. X
  545. X.SS Examples:
  546. X.br
  547. X.nf
  548. X.ft 4
  549. X.in +5
  550. Xconst char * optv[] = {
  551. X    "c:count   <number>",
  552. X    "s?str     <string>",
  553. X    "x",
  554. X    " |hello",
  555. X    "g+groups  <newsgroup>",
  556. X    NULL
  557. X} ;
  558. X.in -5
  559. X.ft R
  560. X.fi
  561. X
  562. X\f4optv[]\fP now corresponds to the following:
  563. X
  564. X.na
  565. X.TP 18
  566. XUsage: \fBcmdname\fP
  567. X[\fB\-c\fP\f4|\fP\fB\-\|\-count\fP\ \fInumber\fP]
  568. X[\fB\-s\fP\f4|\fP\fB\-\|\-str\fP\ [\fIstring\fP]]
  569. X[\fB\-x\fP]
  570. X[\fB\-\|\-hello\fP]
  571. X[\fB\-g\fP\f4|\fP\fB\-\|\-groups\fP\ \fInewsgroup\fP\ ...]
  572. X.ad
  573. X
  574. X.PP
  575. XLong-option names are matched case-insensitive and only a unique prefix
  576. Xof the name needs to be specified.
  577. X
  578. XOption-name characters are case-sensitive!
  579. X
  580. X.\"-----------------------------------
  581. X.SH EXAMPLE
  582. X.nf
  583. X.ft 4
  584. X#include <stdlib.h>
  585. X#include <iostream.h>
  586. X#include <options.h>
  587. X
  588. Xstatic const char * optv[] = {
  589. X   "H|help",
  590. X   "c:count   <number>",
  591. X   "s?str     <string>",
  592. X   "x",
  593. X   " |hello",
  594. X   "g+groups  <newsgroup>",
  595. X   NULL
  596. X} ;
  597. X
  598. Xmain(int argc, char * argv[]) {
  599. X   int  optchar;
  600. X   const char * optarg;
  601. X   const char * str = "default_string";
  602. X   int  count = 0, xflag = 0, hello = 0;
  603. X   int  errors = 0, ngroups = 0;
  604. X
  605. X   Options  opts(*argv, optv);
  606. X   OptArgvIter  iter(--argc, ++argv);
  607. X
  608. X   while( optchar = opts(iter, optarg) ) {
  609. X      switch (optchar) {
  610. X      case 'H' :
  611. X         opts.usage("files ...", cout);
  612. X         exit(0);
  613. X         break;
  614. X      case 'g' :
  615. X         ++ngroups; break;  // the groupname is in "optarg"
  616. X      case 's' :
  617. X         str = optarg; break;
  618. X      case 'x' :
  619. X         ++xflag; break;
  620. X      case ' ' :
  621. X         ++hello; break;
  622. X      case 'c' :
  623. X         if (optarg == NULL)  ++errors;
  624. X         else  count = (int) atol(optarg);
  625. X         break;
  626. X      default :  ++errors; break;
  627. X      } //switch
  628. X   }
  629. X
  630. X   if (errors || (iter.index() == argc)) {
  631. X      if (! errors) {
  632. X         cerr << opts.name() << ": no filenames given." << endl ;
  633. X      }
  634. X      opts.usage("files ...", cerr);
  635. X      exit(1);
  636. X   }
  637. X
  638. X   cout << "xflag=" << ((xflag) ? "ON"  : "OFF") << endl
  639. X        << "hello=" << ((hello) ? "YES" : "NO") << endl
  640. X        << "count=" << count << endl
  641. X        << "str=\"" << ((str) ? str : "No value given!") << "\"" << endl
  642. X        << "ngroups=" << ngroups << endl ;
  643. X
  644. X   if (iter.index() < argc) {
  645. X      cout << "files=" ;
  646. X      for (int i = iter.index() ; i < argc ; i++) {
  647. X         cout << "\"" << argv[i] << "\" " ;
  648. X      }
  649. X      cout << endl ;
  650. X   }
  651. X}
  652. X
  653. X.ft R
  654. X.fi
  655. X
  656. X.\"-----------------------------------
  657. X.SH PARSING
  658. XAlthough, much of the parsing behavior of \*(NM can be configured at run-time
  659. Xthere are some \fIcommon rules\fP that are used when parsing command-line
  660. Xarguments.  Many of these so called \fIrules\fP are just a formalization
  661. Xof things that have become an informal standard over the years.
  662. X
  663. X.SS "LONG AND SHORT OPTIONS"
  664. X.RS
  665. XBy default, \*(NM will allow both single-character options \fIand\fP
  666. Xkeywords (long-options) to be matched on the command-line.
  667. XUnder Unix, a single character option is prefixed by the string ``\-''.
  668. Xand a long-option is prefixed by the string ``\*(--'' (although the prefix
  669. X``+'' may also be used if desired).  If a token on the command-line exactly
  670. Xmatches the string ``\*(--'', then all further matching of options (both
  671. Xlong and short) are disabled and any remaining arguments are considered to
  672. Xbe positional parameters (even if they look like options).
  673. X
  674. XIf short-option processing is disabled, then the prefix ``\-'' may be used
  675. Xto indicate a long-option (the ``\*(--'' prefix will still be accepted).
  676. X
  677. X.SS "OPTION MATCHING"
  678. X.RS
  679. XShort-option matching is case-sensitive. Long-options are always matched 
  680. Xcase-insensitive and only a unique prefix of the long-option name needs
  681. Xto be given in order for it to be matched successfully.
  682. X.RE
  683. X
  684. X.SS "SPECIFYING VALUES TO OPTIONS"
  685. X.RS
  686. XBy default, \*(NM will allow the value for an option to be in the
  687. Xsame command-line token as the option itself, or in a separate
  688. Xcommand-line token.  For short-options, specifying the value in
  689. Xthe same token simply means immediately following the option
  690. Xcharacter with the intended value as in "\fB\-c\fIvalue\fR".
  691. XFor long-options, specifying the value in the same token requires
  692. Xthat the value be separated from the keyword name by an equal-sign
  693. X(`=') or by a colon (`:') as in ``\fB\*(--keyword\fP=\fIvalue\fP''
  694. Xor ``\fB\*(--keyword\fP:\fIvalue\fP''.
  695. X.RE
  696. X
  697. X.\"-----------------------------------
  698. X.SH CAVEAT
  699. XBecause of the way in which multi-valued options and options with optional
  700. Xvalues are handled, it is \fInot\fP possible to supply a value to an
  701. Xoption in a separate argument (different \f4argv[]\fP element) if the
  702. Xvalue is \fIoptional\fP and begins with a `\-'. What this means is that
  703. Xif an option \fB\-s\fP takes an optional value value and you wish to supply
  704. Xa value of ``\f4-foo\fP'' then you must specify this on the command-line as
  705. X``\f4\-s\-foo\fP'' instead of ``\f4\-s \-foo\fP'' because the latter
  706. Xwill be considered to be two separate sets of options.
  707. X
  708. XA multi-valued option is terminated by another option or by the end-of
  709. Xoptions. The following are all equivalent (if \fB\-l\fP is a multi-valued
  710. Xoption and \fB\-x\fP is an option that takes no value):
  711. X
  712. X.nf
  713. X.in +5
  714. X.ft 4
  715. Xcmdname -x -l item1 item2 item3 -- arg1 arg2 arg3
  716. Xcmdname -x -litem1 -litem2 -litem3 -- arg1 arg2 arg3
  717. Xcmdname -l item1 item2 item3 -x arg1 arg2 arg3
  718. X.ft R
  719. X.in -5
  720. X.fi
  721. X
  722. X.\"-----------------------------------
  723. X.SH FILES
  724. X.PP
  725. X.IP \f4\*i/options.h\fP
  726. X\*(C+ include file which contains the definition of an option-iterator class,
  727. Xand argument-iterator classes.
  728. X
  729. X.IP \f4\*l/liboptions.a\fP
  730. XThe object library for \*(NM(3\*(C+).
  731. X
  732. X.PP
  733. XWhen compiling your source (on Unix systems), you may need to use
  734. Xthe \fB\-I\f4\*i\fR option. When linking your objects,
  735. Xyou may need to use the \fB\-L\f4\*l\fR option in conjunction
  736. Xwith the \fB\-l\f4options\fR option.
  737. X
  738. X.\"-----------------------------------
  739. X.SH SEE ALSO
  740. X\f4<options.h>\fP
  741. X
  742. X.\"-----------------------------------
  743. X.SH AUTHOR
  744. XBrad Appleton, Harris Computer Systems, <\f4brad@ssd.csd.harris.com\fP>.
  745. END_OF_FILE
  746.   if test 14308 -ne `wc -c <'options.3'`; then
  747.     echo shar: \"'options.3'\" unpacked with wrong size!
  748.   fi
  749.   # end of 'options.3'
  750. fi
  751. if test -f 'options.C' -a "${1}" != "-c" ; then 
  752.   echo shar: Will not clobber existing file \"'options.C'\"
  753. else
  754.   echo shar: Extracting \"'options.C'\" \(26208 characters\)
  755.   sed "s/^X//" >'options.C' <<'END_OF_FILE'
  756. X// ****************************************************************************
  757. X// ^FILE: options.c - implement the functions defined in <options.h>
  758. X//
  759. X// ^HISTORY:
  760. X//    01/16/92    Brad Appleton    <brad@ssd.csd.harris.com>    Created
  761. X// ^^**************************************************************************
  762. X
  763. X#include <stdlib.h>
  764. X#include <iostream.h>
  765. X#include <ctype.h>
  766. X#include <string.h>
  767. X
  768. X#include "options.h"
  769. X
  770. Xextern "C" {
  771. X   void  exit(int);
  772. X}
  773. X
  774. Xstatic const char ident[] = "@(#)Options  1.00" ;
  775. X
  776. X   // I need a portable version of "tolower" that does NOT modify
  777. X   // non-uppercase characters.
  778. X   //
  779. X#define  TOLOWER(c)  (isupper(c) ? tolower(c) : c)
  780. X
  781. X// ************************************************************** OptIter
  782. X
  783. XOptIter::~OptIter(void) {}
  784. X
  785. Xconst char *
  786. XOptIter::operator()(void)  {
  787. X   const char * elt = curr();
  788. X   (void) next();
  789. X   return  elt;
  790. X}
  791. X
  792. X// ************************************************************** OptIterRwd
  793. X
  794. XOptIterRwd::OptIterRwd(void) {}
  795. X
  796. XOptIterRwd::~OptIterRwd(void) {}
  797. X
  798. X// ************************************************************** OptArgvIter
  799. X
  800. XOptArgvIter::~OptArgvIter(void) {}
  801. X
  802. Xconst char *
  803. XOptArgvIter::curr(void) {
  804. X   return ((ndx == ac) || (av[ndx] == NULL)) ? NULL : av[ndx];
  805. X}
  806. X
  807. Xvoid
  808. XOptArgvIter::next(void) {
  809. X   if ((ndx != ac) && av[ndx]) ++ndx;
  810. X}
  811. X
  812. Xconst char *
  813. XOptArgvIter::operator()(void) {
  814. X   return ((ndx == ac) || (av[ndx] == NULL)) ? NULL : av[ndx++];
  815. X}
  816. X
  817. Xvoid
  818. XOptArgvIter::rewind(void) { ndx = 0; }
  819. X
  820. X// ************************************************************** OptStrTokIter
  821. X
  822. Xstatic const char WHITESPACE[] = " \t\n\r\v\f" ;
  823. Xconst char * OptStrTokIter::default_delims = WHITESPACE ;
  824. X
  825. XOptStrTokIter::OptStrTokIter(const char * tokens, const char * delimiters)
  826. X   : len(unsigned(strlen(tokens))), str(tokens), seps(delimiters),
  827. X     cur(NULL), tokstr(NULL)
  828. X{
  829. X   if (seps == NULL)  seps = default_delims;
  830. X   tokstr = new char[len + 1];
  831. X   (void) ::strcpy(tokstr, str);
  832. X   cur = ::strtok(tokstr, seps);
  833. X}
  834. X
  835. X
  836. XOptStrTokIter::~OptStrTokIter(void) { delete [] tokstr; }
  837. X
  838. Xconst char *
  839. XOptStrTokIter::curr(void) { return cur; }
  840. X
  841. Xvoid
  842. XOptStrTokIter::next(void) { if (cur) cur = ::strtok(NULL, seps); }
  843. X
  844. Xconst char *
  845. XOptStrTokIter::operator()(void) {
  846. X   const char * elt = cur;
  847. X   if (cur) cur = ::strtok(NULL, seps);
  848. X   return  elt;
  849. X}
  850. X
  851. Xvoid
  852. XOptStrTokIter::rewind(void) {
  853. X   (void) ::strcpy(tokstr, str);
  854. X   cur = ::strtok(tokstr, seps);
  855. X}
  856. X
  857. X// ******************************************************************* Options
  858. X
  859. XOptions::Options(const char * name, const char * const optv[])
  860. X   : cmdname(name), optvec(optv), explicit_end(0), optctrls(DEFAULT),
  861. X     nextchar(NULL), listopt(NULL)
  862. X{
  863. X   const char * basename = ::strrchr(cmdname, '/');
  864. X   if (basename)  cmdname = basename + 1;
  865. X}
  866. X
  867. XOptions::~Options(void) {}
  868. X
  869. X   // return values for a keyword matching function
  870. Xenum kwdmatch_t { NO_MATCH, PARTIAL_MATCH, EXACT_MATCH } ;
  871. X
  872. X   // Get the option-char of an option-spec.
  873. Xinline static char
  874. XOptChar(const char * optspec) { return  *optspec; }
  875. X
  876. X   // Get the long-option of an option-spec.
  877. Xinline static const char *
  878. XLongOpt(const char * optspec) {
  879. X    return  ((optspec)[1] && (optspec)[2] &&
  880. X             (! isspace((optspec)[2]))) ? ((optspec) + 2) : NULL ;
  881. X}
  882. X
  883. X   // Is the option-char null?
  884. Xinline static int
  885. XisNullOpt(char optchar) {
  886. X   return  ((! optchar) || isspace(optchar) || (! isprint(optchar))) ;
  887. X}
  888. X
  889. X   // Does this option require an argument?
  890. Xinline static int
  891. XisRequired(const char * optspec) {
  892. X   return  (((optspec)[1] == ':') || ((optspec)[1] == '+')) ;
  893. X}
  894. X
  895. X   // Does this option take an optional argument?
  896. Xinline static int
  897. XisOptional(const char * optspec) {
  898. X   return  (((optspec)[1] == '?') || ((optspec)[1] == '*')) ;
  899. X}
  900. X
  901. X   // Does this option take no arguments?
  902. Xinline static int
  903. XisNoArg(const char * optspec) {
  904. X   return  (((optspec)[1] == '|') || (! (optspec)[1])) ;
  905. X}
  906. X
  907. X   // Can this option take more than one argument?
  908. Xinline static int
  909. XisList(const char * optspec) {
  910. X   return  (((optspec)[1] == '+') || ((optspec)[1] == '*')) ;
  911. X}
  912. X
  913. X   // Does this option take any arguments?
  914. Xinline static int
  915. XisValTaken(const char * optspec) {
  916. X   return  (isRequired(optspec) || isOptional(optspec)) ;
  917. X}
  918. X
  919. X   // Check for explicit "end-of-options"
  920. Xinline static int
  921. XisEndOpts(const char * token)
  922. X{
  923. X   return ((token == NULL) || (! strcmp(token, "--"))) ;
  924. X}
  925. X
  926. X   // See if an argument is an option
  927. Xinline static int
  928. XisOption(unsigned  flags, const char * arg)
  929. X{
  930. X   return  (((*arg != '\0') || (arg[1] != '\0')) &&
  931. X            ((*arg == '-')  || ((flags & Options::PLUS) && (*arg == '+')))) ;
  932. X}
  933. X
  934. X// ---------------------------------------------------------------------------
  935. X// ^FUNCTION: verify - verify the syntax of each option-spec
  936. X//
  937. X// ^SYNOPSIS:
  938. X//    static void verify(optv[])
  939. X//
  940. X// ^PARAMETERS:
  941. X//    const char * const optv[] - the vector of option-specs to inspect.
  942. X//
  943. X// ^DESCRIPTION:
  944. X//    All we have to do is iterate through the option vector and make sure
  945. X//    That each option-spec is of the proper format.
  946. X//
  947. X// ^REQUIREMENTS:
  948. X//    - optv[] should be non-NULL and terminated by a NULL pointer.
  949. X//
  950. X// ^SIDE-EFFECTS:
  951. X//    If an invalid option-spec is found, prints a message on cerr and
  952. X//    exits with a status of 127.
  953. X//
  954. X// ^RETURN-VALUE:
  955. X//    None.
  956. X//
  957. X// ^ALGORITHM:
  958. X//    For each option-spec
  959. X//       - ensure (length > 0)
  960. X//       - verify the the second character is one of "|?:*+"
  961. X//    end-for
  962. X// ^^-------------------------------------------------------------------------
  963. Xstatic void
  964. Xverify(const char * const optv[])
  965. X{
  966. X   int errors = 0;
  967. X   if ((optv == NULL) || (! *optv))  return;
  968. X
  969. X   for (; *optv ; optv++) {
  970. X      char *p;
  971. X      if (! **optv) {
  972. X         cerr << "invalid option spec \"" << *optv << "\"." << endl ;
  973. X          cerr << "\tmust be at least 1 character long." << endl ;
  974. X         ++errors;
  975. X      }
  976. X      if ((**optv) && ((*optv)[1]) &&
  977. X          ((p = strchr("|?:*+", (*optv)[1])) == NULL)) {
  978. X         cerr << "invalid option spec \"" << *optv << "\"." << endl ;
  979. X         cerr << "\t2nd character must be in the set \"|?:*+\"." << endl ;
  980. X         ++errors;
  981. X      }
  982. X   }/*for*/
  983. X
  984. X   if (errors)  exit(127);
  985. X}
  986. X
  987. X// ---------------------------------------------------------------------------
  988. X// ^FUNCTION: kwdmatch - match a keyword
  989. X//
  990. X// ^SYNOPSIS:
  991. X//    static kwdmatch_t kwdmatch(src, attempt, len)
  992. X//
  993. X// ^PARAMETERS:
  994. X//    char * src -- the actual keyword to match
  995. X//    char * attempt -- the possible keyword to compare against "src"
  996. X//    int len -- number of character of "attempt" to consider
  997. X//               (if 0 then we should use all of "attempt")
  998. X//
  999. X// ^DESCRIPTION:
  1000. X//    See if "attempt" matches some prefix of "src" (case insensitive).
  1001. X//
  1002. X// ^REQUIREMENTS:
  1003. X//    - attempt should be non-NULL and non-empty
  1004. X//
  1005. X// ^SIDE-EFFECTS:
  1006. X//    None.
  1007. X//
  1008. X// ^RETURN-VALUE:
  1009. X//    An enumeration value of type kwdmatch_t corresponding to whether
  1010. X//    We had an exact match, a partial match, or no match.
  1011. X//
  1012. X// ^ALGORITHM:
  1013. X//    Trivial
  1014. X// ^^-------------------------------------------------------------------------
  1015. Xstatic kwdmatch_t
  1016. Xkwdmatch(const char * src, const char * attempt, int len =0)
  1017. X{
  1018. X   unsigned  i;
  1019. X
  1020. X   if (src == attempt)  return  EXACT_MATCH ;
  1021. X   if ((src == NULL) || (attempt == NULL))  return  NO_MATCH ;
  1022. X   if ((! *src) && (! *attempt))  return  EXACT_MATCH ;
  1023. X   if ((! *src) || (! *attempt))  return  NO_MATCH ;
  1024. X
  1025. X   for (i = 0 ; ((i < len) || (len == 0)) &&
  1026. X                (attempt[i]) && (attempt[i] != ' ') ; i++) {
  1027. X      if (TOLOWER(src[i]) != TOLOWER(attempt[i]))  return  NO_MATCH ;
  1028. X   }
  1029. X
  1030. X   return  (src[i]) ? PARTIAL_MATCH : EXACT_MATCH ;
  1031. X}
  1032. X
  1033. X// ---------------------------------------------------------------------------
  1034. X// ^FUNCTION: match_opt - match an option
  1035. X//
  1036. X// ^SYNOPSIS:
  1037. X//    static const char * match_opt(optv, opt)
  1038. X//
  1039. X// ^PARAMETERS:
  1040. X//    char * optv[] -- vector of option-specifications
  1041. X//    char opt -- the option-character to match
  1042. X//    int  ignore_case -- should we ignore character-case?
  1043. X//
  1044. X// ^DESCRIPTION:
  1045. X//    See if "opt" is found in "optv"
  1046. X//
  1047. X// ^REQUIREMENTS:
  1048. X//    - optv should be non-NULL and terminated by a NULL pointer.
  1049. X//
  1050. X// ^SIDE-EFFECTS:
  1051. X//    None.
  1052. X//
  1053. X// ^RETURN-VALUE:
  1054. X//    NULL if no match is found,
  1055. X//    otherwise a pointer to the matching option-spec.
  1056. X//
  1057. X// ^ALGORITHM:
  1058. X//    foreach option-spec
  1059. X//       - see if "opt" is a match, if so return option-spec
  1060. X//    end-for
  1061. X// ^^-------------------------------------------------------------------------
  1062. Xstatic const char *
  1063. Xmatch_opt(const char * const optv[], char opt, int ignore_case =0)
  1064. X{
  1065. X   if ((optv == NULL) || (! *optv))  return  NULL;
  1066. X
  1067. X   for (; *optv ; ++optv) {
  1068. X     char optchar = OptChar(*optv);
  1069. X     if (isNullOpt(optchar))  continue;
  1070. X     if (opt == optchar) {
  1071. X        return *optv;
  1072. X     } else if (ignore_case && (TOLOWER(opt) == TOLOWER(optchar))) {
  1073. X        return *optv;
  1074. X     }
  1075. X   }
  1076. X
  1077. X   return  NULL;  // not found
  1078. X}
  1079. X
  1080. X// ---------------------------------------------------------------------------
  1081. X// ^FUNCTION: match_longopt - match a long-option
  1082. X//
  1083. X// ^SYNOPSIS:
  1084. X//   static const char * match_longopt(optv, opt, len, ambiguous)
  1085. X//
  1086. X// ^PARAMETERS:
  1087. X//    char * optv[] -- the vector of option-specs
  1088. X//    char * opt -- the long-option to match
  1089. X//    int len -- the number of character of "opt" to match
  1090. X//    int & ambiguous -- set by this routine before returning.
  1091. X//
  1092. X// ^DESCRIPTION:
  1093. X//    Try to match "opt" against some unique prefix of a long-option
  1094. X//    (case insensitive).
  1095. X//
  1096. X// ^REQUIREMENTS:
  1097. X//    - optvec should be non-NULL and terminated by a NULL pointer.
  1098. X//
  1099. X// ^SIDE-EFFECTS:
  1100. X//    - *ambiguous is set to '1' if "opt" matches >1 long-option
  1101. X//      (otherwise it is set to 0).
  1102. X//
  1103. X// ^RETURN-VALUE:
  1104. X//    NULL if no match is found,
  1105. X//    otherwise a pointer to the matching option-spec.
  1106. X//
  1107. X// ^ALGORITHM:
  1108. X//    ambiguous is FALSE
  1109. X//    foreach option-spec
  1110. X//       if we have an EXACT-MATCH, return the option-spec
  1111. X//       if we have a partial-match then
  1112. X//          if we already had a previous partial match then 
  1113. X//             set ambiguous = TRUE and retrun NULL
  1114. X//          else
  1115. X//             remember this options spec and continue matching
  1116. X//          end-if
  1117. X//       end-if
  1118. X//    end-for
  1119. X//    if we had exactly 1 partial match return it, else return NULL
  1120. X// ^^-------------------------------------------------------------------------
  1121. Xstatic const char *
  1122. Xmatch_longopt(const char * const optv[],
  1123. X              const char       * opt,
  1124. X              int                len,
  1125. X              int              & ambiguous)
  1126. X{
  1127. X   kwdmatch_t  result;
  1128. X   const char * matched = NULL ;
  1129. X
  1130. X   ambiguous = 0;
  1131. X   if ((optv == NULL) || (! *optv))  return  NULL;
  1132. X
  1133. X   for (; *optv ; ++optv) {
  1134. X      const char * longopt = LongOpt(*optv) ;
  1135. X      if (longopt == NULL)  continue;
  1136. X      result = kwdmatch(longopt, opt, len);
  1137. X      if (result == EXACT_MATCH) {
  1138. X         return  *optv;
  1139. X      } else if (result == PARTIAL_MATCH) {
  1140. X         if (matched) {
  1141. X            ++ambiguous;
  1142. X            return  NULL;
  1143. X         } else {
  1144. X            matched = *optv;
  1145. X         }
  1146. X      }
  1147. X   }/*for*/
  1148. X
  1149. X   return  matched;
  1150. X}
  1151. X
  1152. X// ---------------------------------------------------------------------------
  1153. X// ^FUNCTION: Options::parse_opt - parse an option
  1154. X//
  1155. X// ^SYNOPSIS:
  1156. X//    int Options::parse_opt(iter, optarg)
  1157. X//
  1158. X// ^PARAMETERS:
  1159. X//    OptIter & iter -- option iterator
  1160. X//    const char * & optarg -- where to store any option-argument
  1161. X//
  1162. X// ^DESCRIPTION:
  1163. X//    Parse the next option in iter (advancing as necessary).
  1164. X//    Make sure we update the nextchar pointer along the way. Any option
  1165. X//    we find should be returned and optarg should point to its argument.
  1166. X//
  1167. X// ^REQUIREMENTS:
  1168. X//    - nextchar must point to the prospective option character
  1169. X//
  1170. X// ^SIDE-EFFECTS:
  1171. X//    - iter is advanced when an argument completely parsed
  1172. X//    - optarg is modified to point to any option argument
  1173. X//    - if Options::QUIET is not set, error messages are printed on cerr
  1174. X//
  1175. X// ^RETURN-VALUE:
  1176. X//    'c' if the -c option was matched (optarg points to its argument)
  1177. X//    -1 if the option is invalid (optarg points to the bad option-character).
  1178. X//
  1179. X// ^ALGORITHM:
  1180. X//    It gets complicated -- follow the comments in the source.
  1181. X// ^^-------------------------------------------------------------------------
  1182. Xint
  1183. XOptions::parse_opt(OptIter & iter, const char * & optarg)
  1184. X{
  1185. X   listopt = NULL;  // reset the list pointer
  1186. X
  1187. X   if ((optvec == NULL) || (! *optvec))  return  NULL;
  1188. X
  1189. X      // Try to match a known option
  1190. X   const char * optspec = match_opt(optvec, *(nextchar++));
  1191. X
  1192. X      // Check for an unknown option
  1193. X   if (optspec == NULL) {
  1194. X      // See if this was a long-option in disguise
  1195. X      if (! (optctrls & NOGUESSING)) {
  1196. X         unsigned  save_ctrls = optctrls;
  1197. X         const char * save_nextchar = nextchar;
  1198. X         nextchar -= 1;
  1199. X         optctrls |= (QUIET | NOGUESSING);
  1200. X         int  optchar = parse_longopt(iter, optarg);
  1201. X         optctrls = save_ctrls;
  1202. X         if (optchar > 0) {
  1203. X            return  optchar;
  1204. X         } else {
  1205. X            nextchar = save_nextchar;
  1206. X         }
  1207. X      }
  1208. X      if (! (optctrls & QUIET)) {
  1209. X         cerr << cmdname << ": unknown option -"
  1210. X              << *(nextchar - 1) << "." << endl ;
  1211. X      }
  1212. X      optarg = (nextchar - 1);  // record the bad option in optarg
  1213. X      return  BADCHAR;
  1214. X   }
  1215. X
  1216. X      // If no argument is taken, then leave now
  1217. X   if (isNoArg(optspec)) {
  1218. X      optarg = NULL;
  1219. X      return  OptChar(optspec);
  1220. X   }
  1221. X
  1222. X      // Check for argument in this arg
  1223. X   if (*nextchar) {
  1224. X      optarg = nextchar; // the argument is right here
  1225. X      nextchar = NULL;   // we've exhausted this arg
  1226. X      if (isList(optspec))  listopt = optspec ;  // save the list-spec
  1227. X      return  OptChar(optspec);
  1228. X   }
  1229. X
  1230. X      // Check for argument in next arg
  1231. X   const char * nextarg = iter.curr();
  1232. X   if ((nextarg != NULL)  &&
  1233. X       (isRequired(optspec) || (! isOption(optctrls, nextarg)))) {
  1234. X      optarg = nextarg;    // the argument is here
  1235. X      iter.next();         // end of arg - advance
  1236. X      if (isList(optspec))  listopt = optspec ;  // save the list-spec
  1237. X      return  OptChar(optspec);
  1238. X   }
  1239. X
  1240. X     // No argument given - if its required, thats an error
  1241. X   optarg = NULL;
  1242. X   if (isRequired(optspec) &&  !(optctrls & QUIET)) {
  1243. X      cerr << cmdname << ": argument required for -" << OptChar(optspec)
  1244. X           << " option." << endl ;
  1245. X   }
  1246. X   return  OptChar(optspec);
  1247. X}
  1248. X
  1249. X// ---------------------------------------------------------------------------
  1250. X// ^FUNCTION: Options::parse_longopt - parse a long-option
  1251. X//
  1252. X// ^SYNOPSIS:
  1253. X//    int Options::parse_longopt(iter, optarg)
  1254. X//
  1255. X// ^PARAMETERS:
  1256. X//    OptIter & iter -- option iterator
  1257. X//    const char * & optarg -- where to store any option-argument
  1258. X//
  1259. X// ^DESCRIPTION:
  1260. X//    Parse the next long-option in iter (advancing as necessary).
  1261. X//    Make sure we update the nextchar pointer along the way. Any option
  1262. X//    we find should be returned and optarg should point to its argument.
  1263. X//
  1264. X// ^REQUIREMENTS:
  1265. X//    - nextchar must point to the prospective option character
  1266. X//
  1267. X// ^SIDE-EFFECTS:
  1268. X//    - iter is advanced when an argument completely parsed
  1269. X//    - optarg is modified to point to any option argument
  1270. X//    - if Options::QUIET is not set, error messages are printed on cerr
  1271. X//
  1272. X// ^RETURN-VALUE:
  1273. X//    'c' if the the long-option corresponding to the -c option was matched
  1274. X//         (optarg points to its argument)
  1275. X//    -2 if the option is invalid (optarg points to the bad long-option name).
  1276. X//
  1277. X// ^ALGORITHM:
  1278. X//    It gets complicated -- follow the comments in the source.
  1279. X// ^^-------------------------------------------------------------------------
  1280. Xint
  1281. XOptions::parse_longopt(OptIter & iter, const char * & optarg)
  1282. X{
  1283. X   int  len = 0, ambiguous = 0;
  1284. X
  1285. X   listopt = NULL ;  // reset the list-spec
  1286. X
  1287. X   if ((optvec == NULL) || (! *optvec))  return  NULL;
  1288. X
  1289. X      // if a value is supplied in this argv element, get it now
  1290. X   const char * val = strpbrk(nextchar, ":=") ;
  1291. X   if (val) {
  1292. X      len = val - nextchar ;
  1293. X      ++val ;
  1294. X   }
  1295. X
  1296. X      // Try to match a known long-option
  1297. X   const char * optspec = match_longopt(optvec, nextchar, len, ambiguous);
  1298. X
  1299. X      // Check for an unknown long-option
  1300. X   if (optspec == NULL) {
  1301. X      // See if this was a short-option in disguise
  1302. X      if ((! ambiguous) && (! (optctrls & NOGUESSING))) {
  1303. X         unsigned  save_ctrls = optctrls;
  1304. X         const char * save_nextchar = nextchar;
  1305. X         optctrls |= (QUIET | NOGUESSING);
  1306. X         int  optchar = parse_opt(iter, optarg);
  1307. X         optctrls = save_ctrls;
  1308. X         if (optchar > 0) {
  1309. X            return  optchar;
  1310. X         } else {
  1311. X            nextchar = save_nextchar;
  1312. X         }
  1313. X      }
  1314. X      if (! (optctrls & QUIET)) {
  1315. X         cerr << cmdname << ": " << ((ambiguous) ? "ambiguous" : "unknown")
  1316. X              << " option "
  1317. X              << ((optctrls & LONG_ONLY) ? "-" : "--")
  1318. X              << nextchar << "." << endl ;
  1319. X      }
  1320. X      optarg = nextchar;  // record the bad option in optarg
  1321. X      nextchar = NULL;    // we've exhausted this argument
  1322. X      return  (ambiguous) ? AMBIGUOUS : BADKWD;
  1323. X   }
  1324. X
  1325. X      // If no argument is taken, then leave now
  1326. X   if (isNoArg(optspec)) {
  1327. X      if ((val) && ! (optctrls & QUIET)) {
  1328. X         cerr << cmdname << ": option "
  1329. X              << ((optctrls & LONG_ONLY) ? "-" : "--")
  1330. X              << LongOpt(optspec) << " does NOT take an argument." << endl ;
  1331. X      }
  1332. X      optarg = val;     // record the unexpected argument
  1333. X      nextchar = NULL;  // we've exhausted this argument
  1334. X      return  OptChar(optspec);
  1335. X   }
  1336. X
  1337. X      // Check for argument in this arg
  1338. X   if (val) {
  1339. X      optarg = val;      // the argument is right here
  1340. X      nextchar = NULL;   // we exhausted the rest of this arg
  1341. X      if (isList(optspec))  listopt = optspec ;  // save the list-spec
  1342. X      return  OptChar(optspec);
  1343. X   }
  1344. X
  1345. X      // Check for argument in next arg
  1346. X   const char * nextarg = iter.curr();  // find the next argument to parse
  1347. X   if ((nextarg != NULL)  &&
  1348. X       (isRequired(optspec) || (! isOption(optctrls, nextarg)))) {
  1349. X      optarg = nextarg;        // the argument is right here
  1350. X      iter.next();             // end of arg - advance
  1351. X      nextchar = NULL;         // we exhausted the rest of this arg
  1352. X      if (isList(optspec))  listopt = optspec ;  // save the list-spec
  1353. X      return  OptChar(optspec);
  1354. X   }
  1355. X
  1356. X     // No argument given - if its required, thats an error
  1357. X   optarg = NULL;
  1358. X   if (isRequired(optspec) &&  !(optctrls & QUIET)) {
  1359. X      const char * longopt = LongOpt(optspec);
  1360. X      const char * spc = ::strchr(longopt, ' ');
  1361. X      int  longopt_len;
  1362. X      if (spc) {
  1363. X         longopt_len = spc - longopt;
  1364. X      } else {
  1365. X         longopt_len = ::strlen(longopt);
  1366. X      }
  1367. X      cerr << cmdname << ": argument required for "
  1368. X           << ((optctrls & LONG_ONLY) ? "-" : "--");
  1369. X      cerr.write(longopt, longopt_len) << " option." << endl ;
  1370. X   }
  1371. X   nextchar = NULL;           // we exhausted the rest of this arg
  1372. X   return  OptChar(optspec);
  1373. X}
  1374. X
  1375. X// ---------------------------------------------------------------------------
  1376. X// ^FUNCTION: Options::fmt_opt - format an option-spec for a usage message
  1377. X//
  1378. X// ^SYNOPSIS:
  1379. X//    unsigned Options::fmt_opt(optspec, buf)
  1380. X//
  1381. X// ^PARAMETERS:
  1382. X//    char * optspec -- the option-specification
  1383. X//    char * buf -- where to print the formatted option
  1384. X//
  1385. X// ^DESCRIPTION:
  1386. X//    Self-explanatory.
  1387. X//
  1388. X// ^REQUIREMENTS:
  1389. X//    - optspec must be a valid option-spec.
  1390. X//    - buf must be large enough to hold the result
  1391. X//
  1392. X// ^SIDE-EFFECTS:
  1393. X//    - writes to buf.
  1394. X//
  1395. X// ^RETURN-VALUE:
  1396. X//    Number of characters written to buf.
  1397. X//
  1398. X// ^ALGORITHM:
  1399. X//    Trivial.
  1400. X// ^^-------------------------------------------------------------------------
  1401. Xunsigned
  1402. XOptions::fmt_opt(const char * optspec, char * buf) const
  1403. X{
  1404. X   static  char default_value[] = "<value>";
  1405. X   char optchar = OptChar(optspec);
  1406. X   const char * longopt = LongOpt(optspec);
  1407. X   char * p = buf ;
  1408. X
  1409. X   const char * value = NULL;
  1410. X   int    longopt_len = 0;
  1411. X   int    value_len = 0;
  1412. X
  1413. X   if (longopt) {
  1414. X      value = ::strchr(longopt, ' ');
  1415. X      longopt_len = (value) ? (value - longopt) : ::strlen(longopt);
  1416. X   } else {
  1417. X      value = ::strchr(optspec + 1, ' ');
  1418. X   }
  1419. X   while (value && (*value == ' '))  ++value;
  1420. X   if (value && *value) {
  1421. X      value_len = ::strlen(value);
  1422. X   } else {
  1423. X      value = default_value;
  1424. X      value_len = sizeof(default_value) - 1;
  1425. X   }
  1426. X   
  1427. X   if ((optctrls & SHORT_ONLY) &&
  1428. X       ((! isNullOpt(optchar)) || (optctrls & NOGUESSING))) {
  1429. X      longopt = NULL;
  1430. X   }
  1431. X   if ((optctrls & LONG_ONLY) && (longopt || (optctrls & NOGUESSING))) {
  1432. X      optchar = '\0';
  1433. X   }
  1434. X   if (isNullOpt(optchar) && (longopt == NULL)) {
  1435. X      *buf = '\0';
  1436. X      return  0;
  1437. X   }
  1438. X
  1439. X   *(p++) = '[';
  1440. X
  1441. X   // print the single character option
  1442. X   if (! isNullOpt(optchar)) {
  1443. X      *(p++) = '-';
  1444. X      *(p++) = optchar;
  1445. X   }
  1446. X
  1447. X   if ((! isNullOpt(optchar)) && (longopt))  *(p++) = '|';
  1448. X
  1449. X   // print the long option
  1450. X   if (longopt) {
  1451. X      *(p++) = '-';
  1452. X      if (! (optctrls & (LONG_ONLY | SHORT_ONLY))) {
  1453. X         *(p++) = '-';
  1454. X      }
  1455. X      strncpy(p, longopt, longopt_len);
  1456. X      p += longopt_len;
  1457. X   }
  1458. X
  1459. X   // print any argument the option takes
  1460. X   if (isValTaken(optspec)) {
  1461. X      *(p++) = ' ' ;
  1462. X      if (isOptional(optspec))  *(p++) = '[' ;
  1463. X      strcpy(p, value);
  1464. X      p += value_len;
  1465. X      if (isList(optspec)) {
  1466. X         strcpy(p, " ...");
  1467. X         p += 4;
  1468. X      }
  1469. X      if (isOptional(optspec))  *(p++) = ']' ;
  1470. X   }
  1471. X
  1472. X   *(p++) = ']';
  1473. X   *p = '\0';
  1474. X
  1475. X   return  (unsigned) strlen(buf);
  1476. X}
  1477. X
  1478. X// ---------------------------------------------------------------------------
  1479. X// ^FUNCTION: Options::usage - print usage
  1480. X//
  1481. X// ^SYNOPSIS:
  1482. X//    void Options::usage(os, positionals)
  1483. X//
  1484. X// ^PARAMETERS:
  1485. X//    ostream & os -- where to print the usage
  1486. X//    char * positionals -- command-line syntax for any positional args
  1487. X//
  1488. X// ^DESCRIPTION:
  1489. X//    Print command-usage (using either option or long-option syntax) on os.
  1490. X//
  1491. X// ^REQUIREMENTS:
  1492. X//    os should correspond to an open output file.
  1493. X//
  1494. X// ^SIDE-EFFECTS:
  1495. X//    Prints on os
  1496. X//
  1497. X// ^RETURN-VALUE:
  1498. X//    None.
  1499. X//
  1500. X// ^ALGORITHM:
  1501. X//    Print usage on os, wrapping long lines where necessary.
  1502. X// ^^-------------------------------------------------------------------------
  1503. Xvoid
  1504. XOptions::usage(ostream & os, const char * positionals) const
  1505. X{
  1506. X   const char ** optv = optvec;
  1507. X   unsigned  cols = 79;
  1508. X   int  first, nloop;
  1509. X   char  buf[256] ;
  1510. X
  1511. X   if ((optv == NULL) || (! *optv))  return;
  1512. X
  1513. X      // print first portion "usage: progname"
  1514. X   os << "usage: " << cmdname ;
  1515. X   unsigned  ll = strlen(cmdname) + 7;
  1516. X
  1517. X      // save the current length so we know how much space to skip for
  1518. X      // subsequent lines.
  1519. X      //
  1520. X   unsigned  margin = ll + 1;
  1521. X
  1522. X      // print the options and the positional arguments
  1523. X   for (nloop = 0, first = 1 ; !nloop ; optv++, first = 0) {
  1524. X      unsigned  len;
  1525. X
  1526. X         // figure out how wide this parameter is (for printing)
  1527. X      if (! *optv) {
  1528. X         len = strlen(positionals);
  1529. X         ++nloop;  // terminate this loop
  1530. X      } else {
  1531. X         len = fmt_opt(*optv, buf);
  1532. X      }
  1533. X
  1534. X      //  Will this fit?
  1535. X      if ((ll + len + 1) > (cols - first)) {
  1536. X         os << '\n' ;  // No - start a new line;
  1537. X         os.width(margin);
  1538. X         os << "" ;
  1539. X         ll = margin;
  1540. X      } else {
  1541. X         os << ' ' ;  // Yes - just throw in a space
  1542. X         ++ll;
  1543. X      }
  1544. X      ll += len;
  1545. X      os << ((nloop) ? positionals : buf) ;
  1546. X   }// for each ad
  1547. X
  1548. X   os << endl ;
  1549. X}
  1550. X
  1551. X
  1552. X// ---------------------------------------------------------------------------
  1553. X// ^FUNCTION: Options::operator() - get options from the command-line
  1554. X//
  1555. X// ^SYNOPSIS:
  1556. X//   int Options::operator()(iter, optarg)
  1557. X//
  1558. X// ^PARAMETERS:
  1559. X//    OptIter & iter -- option iterator
  1560. X//    const char * & optarg -- where to store any option-argument
  1561. X//
  1562. X// ^DESCRIPTION:
  1563. X//    Parse the next option in iter (advancing as necessary).
  1564. X//    Make sure we update the nextchar pointer along the way. Any option
  1565. X//    we find should be returned and optarg should point to its argument.
  1566. X//
  1567. X// ^REQUIREMENTS:
  1568. X//    None.
  1569. X//
  1570. X// ^SIDE-EFFECTS:
  1571. X//    - iter is advanced when an argument is completely parsed
  1572. X//    - optarg is modified to point to any option argument
  1573. X//    - if Options::QUIET is not set, error messages are printed on cerr
  1574. X//
  1575. X// ^RETURN-VALUE:
  1576. X//     0 if all options have been parsed.
  1577. X//    'c' if the the option or long-option corresponding to the -c option was
  1578. X//         matched (optarg points to its argument).
  1579. X//    -1 if the option is invalid (optarg points to the bad option character).
  1580. X//    -2 if the option is invalid (optarg points to the bad long-option name).
  1581. X//
  1582. X// ^ALGORITHM:
  1583. X//    It gets complicated -- follow the comments in the source.
  1584. X// ^^-------------------------------------------------------------------------
  1585. Xint
  1586. XOptions::operator()(OptIter & iter, const char * & optarg)
  1587. X{
  1588. X   explicit_end = 0;
  1589. X
  1590. X      // See if we have an option left over from before ...
  1591. X   if ((nextchar) && *nextchar) {
  1592. X      return  parse_opt(iter, optarg);
  1593. X   }
  1594. X
  1595. X      // Check for end-of-options ...
  1596. X   const char * arg = iter.curr();
  1597. X   if (arg == NULL) {
  1598. X      listopt = NULL;
  1599. X      return  ENDOPTS;
  1600. X   } else if (isEndOpts(arg)) {
  1601. X      iter.next();   // advance past end-of-options arg
  1602. X      listopt = NULL;
  1603. X      explicit_end = 1;
  1604. X      return  ENDOPTS;
  1605. X   }
  1606. X
  1607. X      // Do we have a positional arg?
  1608. X   if (! listopt) {
  1609. X      if ((! *arg) || (! arg[1])) {
  1610. X         return  ENDOPTS;
  1611. X      } else if ((*arg != '-') &&
  1612. X                 ((! (optctrls & PLUS)) || (*arg != '+'))) {
  1613. X         return  ENDOPTS;
  1614. X      }
  1615. X   }
  1616. X
  1617. X   iter.next();  // pass the argument that arg already points to
  1618. X
  1619. X      // See if we have a long option ...
  1620. X   if (! (optctrls & SHORT_ONLY)) {
  1621. X      if ((*arg == '-') && (arg[1] == '-')) {
  1622. X         nextchar = arg + 2;
  1623. X         return  parse_longopt(iter, optarg);
  1624. X      } else if ((optctrls & PLUS) && (*arg == '+')) {
  1625. X         nextchar = arg + 1;
  1626. X         return  parse_longopt(iter, optarg);
  1627. X      }
  1628. X   }
  1629. X   if (*arg == '-') {
  1630. X      nextchar = arg + 1;
  1631. X      if (optctrls & LONG_ONLY) {
  1632. X         return  parse_longopt(iter, optarg);
  1633. X      } else {
  1634. X         return  parse_opt(iter, optarg);
  1635. X      }
  1636. X   }
  1637. X
  1638. X      // If we get here - it is because we have a list value
  1639. X   optarg = arg ;        // record the list value
  1640. X   return  OptChar(listopt) ;
  1641. X}
  1642. X
  1643. END_OF_FILE
  1644.   if test 26208 -ne `wc -c <'options.C'`; then
  1645.     echo shar: \"'options.C'\" unpacked with wrong size!
  1646.   fi
  1647.   # end of 'options.C'
  1648. fi
  1649. if test -f 'testopts.C' -a "${1}" != "-c" ; then 
  1650.   echo shar: Will not clobber existing file \"'testopts.C'\"
  1651. else
  1652.   echo shar: Extracting \"'testopts.C'\" \(3205 characters\)
  1653.   sed "s/^X//" >'testopts.C' <<'END_OF_FILE'
  1654. X#include <stdlib.h>
  1655. X#include <iostream.h>
  1656. X#include <string.h>
  1657. X#include <ctype.h>
  1658. X
  1659. X#include <options.h>
  1660. X
  1661. Xextern "C" {
  1662. X   void exit(int);
  1663. X   long  atol(const char *);
  1664. X}
  1665. X
  1666. Xstatic const char *optv[] = {
  1667. X   "?|?",
  1668. X   "H|help",
  1669. X   "f?flags",
  1670. X   "g+groups <name>",
  1671. X   "c:count <int>",
  1672. X   "s?str <string>",
  1673. X   "x",
  1674. X   " |hello",
  1675. X   NULL
  1676. X} ;
  1677. X
  1678. X// I use the following routine when I see the -f option.
  1679. X// It changes the opt_ctrls used by getopts() to take effect
  1680. X// for the next option parsed. The -f option is used to test
  1681. X// the Options::XXXXX flags and should be first on the command-line.
  1682. X//
  1683. Xvoid
  1684. Xsetflags(const char * flags_str, Options & opts) {
  1685. X   if (flags_str && *flags_str) {
  1686. X      unsigned  flags = opts.ctrls();
  1687. X      if (::strchr(flags_str, '+'))  flags |= Options::PLUS;
  1688. X      if (::strchr(flags_str, 'A'))  flags |= Options::ANYCASE;
  1689. X      if (::strchr(flags_str, 'a'))  flags |= Options::ANYCASE;
  1690. X      if (::strchr(flags_str, 'L'))  flags |= Options::LONG_ONLY;
  1691. X      if (::strchr(flags_str, 'l'))  flags |= Options::LONG_ONLY;
  1692. X      if (::strchr(flags_str, 'S'))  flags |= Options::SHORT_ONLY;
  1693. X      if (::strchr(flags_str, 's'))  flags |= Options::SHORT_ONLY;
  1694. X      if (::strchr(flags_str, 'Q'))  flags |= Options::QUIET;
  1695. X      if (::strchr(flags_str, 'q'))  flags |= Options::QUIET;
  1696. X      if (::strchr(flags_str, 'n'))  flags |= Options::NOGUESSING;
  1697. X      if (::strchr(flags_str, 'N'))  flags |= Options::NOGUESSING;
  1698. X      opts.ctrls(flags);
  1699. X   }
  1700. X}
  1701. X
  1702. Xmain(int argc, char * argv[])
  1703. X{
  1704. X   int  optchar;
  1705. X   const char * optarg;
  1706. X   const char * str = "default_string";
  1707. X   int  count = 0, xflag = 0, hello = 0;
  1708. X   int  errors = 0;
  1709. X   int  ngroups = 0;
  1710. X
  1711. X   Options  opts(*argv, optv);
  1712. X   OptArgvIter  iter(--argc, ++argv);
  1713. X
  1714. X   while( optchar = opts(iter, optarg) ) {
  1715. X      switch (optchar) {
  1716. X      case '?' :
  1717. X      case 'H' :
  1718. X         opts.usage(cout, "files ...");
  1719. X         ::exit(0);
  1720. X         break;
  1721. X
  1722. X      case 'f' : setflags(optarg, opts); break;
  1723. X
  1724. X      case 'g' : ++ngroups; break;
  1725. X
  1726. X      case 's' : str = optarg; break;
  1727. X
  1728. X      case 'x' : ++xflag; break;
  1729. X
  1730. X      case ' ' : ++hello; break;
  1731. X
  1732. X      case 'c' :
  1733. X         if (optarg == NULL) {
  1734. X            ++errors;
  1735. X         } else {
  1736. X            count = (int) ::atol(optarg);
  1737. X         }
  1738. X         break;
  1739. X
  1740. X      case Options::BADCHAR :  // bad option ("-%c", *optarg)
  1741. X      case Options::BADKWD  :  // bad long-option ("--%s", optarg)
  1742. X      case Options::AMBIGUOUS  :  // ambiguous long-option ("--%s", optarg)
  1743. X      default :
  1744. X         ++errors; break;
  1745. X      } /*switch*/
  1746. X   }
  1747. X
  1748. X   int index = iter.index();
  1749. X
  1750. X   if (errors || (index == argc)) {
  1751. X      if (! errors) {
  1752. X         cerr << opts.name() << ": no filenames given." << endl ;
  1753. X      }
  1754. X      opts.usage(cerr, "files ...");
  1755. X      ::exit(1);
  1756. X   }
  1757. X
  1758. X   cout << "xflag=" << ((xflag) ? "ON"  : "OFF") << '\n'
  1759. X        << "hello=" << ((hello) ? "YES" : "NO") << '\n'
  1760. X        << "count=" << count << '\n'
  1761. X        << "string=\"" << ((str) ? str : "No value given!") << "\"" << '\n'
  1762. X        << "ngroups=" << ngroups << endl ;
  1763. X
  1764. X   if (index < argc) {
  1765. X      cout << "files=" ;
  1766. X      for (int i = index ; i < argc ; i++) {
  1767. X         cout << "\"" << argv[i] << "\" " ;
  1768. X      }
  1769. X      cout << endl ;
  1770. X   }
  1771. X
  1772. X   return  0;
  1773. X}
  1774. END_OF_FILE
  1775.   if test 3205 -ne `wc -c <'testopts.C'`; then
  1776.     echo shar: \"'testopts.C'\" unpacked with wrong size!
  1777.   fi
  1778.   # end of 'testopts.C'
  1779. fi
  1780. echo shar: End of archive 1 \(of 2\).
  1781. cp /dev/null ark1isdone
  1782. MISSING=""
  1783. for I in 1 2 ; do
  1784.     if test ! -f ark${I}isdone ; then
  1785.     MISSING="${MISSING} ${I}"
  1786.     fi
  1787. done
  1788. if test "${MISSING}" = "" ; then
  1789.     echo You have unpacked both archives.
  1790.     rm -f ark[1-9]isdone
  1791. else
  1792.     echo You still must unpack the following archives:
  1793.     echo "        " ${MISSING}
  1794. fi
  1795. exit 0
  1796. exit 0 # Just in case...
  1797.