]> git.cworth.org Git - vogl/blob - src/common/SimpleOpt.h
Make voglgen accept specdir as option
[vogl] / src / common / SimpleOpt.h
1 /*! @file SimpleOpt.h
2
3     @version 3.6
4
5     @brief A cross-platform command line library which can parse almost any
6     of the standard command line formats in use today. It is designed 
7     explicitly to be portable to any platform and has been tested on Windows 
8     and Linux. See CSimpleOptTempl for the class definition.
9
10     @section features FEATURES
11     -   MIT Licence allows free use in all software (including GPL 
12         and commercial)
13     -   multi-platform (Windows 95/98/ME/NT/2K/XP, Linux, Unix)
14     -   supports all lengths of option names:
15         <table width="60%">
16             <tr><td width="30%"> - 
17                 <td>switch character only (e.g. use stdin for input)
18             <tr><td> -o          
19                 <td>short (single character)
20             <tr><td> -long       
21                 <td>long (multiple character, single switch character)
22             <tr><td> --longer    
23                 <td>long (multiple character, multiple switch characters)
24         </table>
25     -   supports all types of arguments for options:
26         <table width="60%">
27             <tr><td width="30%"> --option        
28                 <td>short/long option flag (no argument)
29             <tr><td> --option ARG    
30                 <td>short/long option with separate required argument
31             <tr><td> --option=ARG    
32                 <td>short/long option with combined required argument
33             <tr><td> --option[=ARG]  
34                 <td>short/long option with combined optional argument
35             <tr><td> -oARG           
36                 <td>short option with combined required argument
37             <tr><td> -o[ARG]         
38                 <td>short option with combined optional argument
39         </table>
40     -   supports options with multiple or variable numbers of arguments:
41         <table width="60%">
42             <tr><td width="30%"> --multi ARG1 ARG2      
43                 <td>Multiple arguments
44             <tr><td> --multi N ARG-1 ARG-2 ... ARG-N    
45                 <td>Variable number of arguments
46         </table>
47     -   supports case-insensitive option matching on short, long and/or 
48         word arguments.
49     -   supports options which do not use a switch character. i.e. a special 
50         word which is construed as an option. 
51         e.g. "foo.exe open /directory/file.txt" 
52     -   supports clumping of multiple short options (no arguments) in a string 
53         e.g. "foo.exe -abcdef file1" <==> "foo.exe -a -b -c -d -e -f file1"
54     -   automatic recognition of a single slash as equivalent to a single 
55         hyphen on Windows, e.g. "/f FILE" is equivalent to "-f FILE".
56     -   file arguments can appear anywhere in the argument list:
57         "foo.exe file1.txt -a ARG file2.txt --flag file3.txt file4.txt"
58         files will be returned to the application in the same order they were 
59         supplied on the command line
60     -   short-circuit option matching: "--man" will match "--mandate"
61         invalid options can be handled while continuing to parse the command 
62         line valid options list can be changed dynamically during command line
63         processing, i.e. accept different options depending on an option 
64         supplied earlier in the command line.
65     -   implemented with only a single C++ header file
66     -   optionally use no C runtime or OS functions
67     -   char, wchar_t and Windows TCHAR in the same program
68     -   complete working examples included
69     -   compiles cleanly at warning level 4 (Windows/VC.NET 2003), warning 
70         level 3 (Windows/VC6) and -Wall (Linux/gcc)
71
72     @section usage USAGE
73     The SimpleOpt class is used by following these steps:
74 <ol>
75     <li> Include the SimpleOpt.h header file
76         <pre>
77         \#include "SimpleOpt.h"
78         </pre>
79     <li> Define an array of valid options for your program.
80 <pre>
81 @link CSimpleOptTempl::SOption CSimpleOpt::SOption @endlink g_rgOptions[] = {
82     { OPT_FLAG, _T("-a"),     SO_NONE    }, // "-a"
83     { OPT_FLAG, _T("-b"),     SO_NONE    }, // "-b"
84     { OPT_ARG,  _T("-f"),     SO_REQ_SEP }, // "-f ARG"
85     { OPT_HELP, _T("-?"),     SO_NONE    }, // "-?"
86     { OPT_HELP, _T("--help"), SO_NONE    }, // "--help"
87     SO_END_OF_OPTIONS                       // END
88 };
89 </pre>
90         Note that all options must start with a hyphen even if the slash will
91         be accepted. This is because the slash character is automatically
92         converted into a hyphen to test against the list of options. 
93         For example, the following line matches both "-?" and "/?" 
94         (on Windows).
95 <pre>
96     { OPT_HELP, _T("-?"),     SO_NONE    }, // "-?"
97 </pre>
98    <li> Instantiate a CSimpleOpt object supplying argc, argv and the option 
99         table
100 <pre>
101 @link CSimpleOptTempl CSimpleOpt @endlink args(argc, argv, g_rgOptions);
102 </pre>
103    <li> Process the arguments by calling Next() until it returns false. 
104         On each call, first check for an error by calling LastError(), then 
105         either handle the error or process the argument.
106 <pre>
107 while (args.Next()) {
108     if (args.LastError() == SO_SUCCESS) {
109         handle option: use OptionId(), OptionText() and OptionArg()
110     }
111     else {
112         handle error: see ESOError enums
113     }
114 }
115 </pre>
116    <li> Process all non-option arguments with File(), Files() and FileCount()
117 <pre>
118 ShowFiles(args.FileCount(), args.Files());
119 </pre>
120     </ol>
121
122     @section notes NOTES
123     -   In MBCS mode, this library is guaranteed to work correctly only when
124         all option names use only ASCII characters.
125     -   Note that if case-insensitive matching is being used then the first
126         matching option in the argument list will be returned.
127
128     @section licence MIT LICENCE
129 <pre>
130     The licence text below is the boilerplate "MIT Licence" used from:
131     http://www.opensource.org/licenses/mit-license.php
132
133     Copyright (c) 2006-2013, Brodie Thiesfield
134
135     Permission is hereby granted, free of charge, to any person obtaining a
136     copy of this software and associated documentation files (the "Software"),
137     to deal in the Software without restriction, including without limitation
138     the rights to use, copy, modify, merge, publish, distribute, sublicense,
139     and/or sell copies of the Software, and to permit persons to whom the
140     Software is furnished to do so, subject to the following conditions:
141
142     The above copyright notice and this permission notice shall be included
143     in all copies or substantial portions of the Software.
144
145     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
146     OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
147     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
148     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
149     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
150     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
151     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
152 </pre>
153 */
154
155 /*! @mainpage
156
157     <table>
158         <tr><th>Library     <td>SimpleOpt
159         <tr><th>Author      <td>Brodie Thiesfield [code at jellycan dot com]
160         <tr><th>Source      <td>http://code.jellycan.com/simpleopt/
161     </table>
162
163     @section SimpleOpt SimpleOpt
164
165     A cross-platform library providing a simple method to parse almost any of
166     the standard command-line formats in use today.
167
168     See the @link SimpleOpt.h SimpleOpt @endlink documentation for full 
169     details.
170
171     @section SimpleGlob SimpleGlob
172
173     A cross-platform file globbing library providing the ability to
174     expand wildcards in command-line arguments to a list of all matching 
175     files.
176
177     See the @link SimpleGlob.h SimpleGlob @endlink documentation for full 
178     details.
179 */
180
181 #ifndef INCLUDED_SimpleOpt
182 #define INCLUDED_SimpleOpt
183
184 // Default the max arguments to a fixed value. If you want to be able to
185 // handle any number of arguments, then predefine this to 0 and it will
186 // use an internal dynamically allocated buffer instead.
187 #ifdef SO_MAX_ARGS
188 #define SO_STATICBUF SO_MAX_ARGS
189 #else
190 #include <stdlib.h> // malloc, free
191 #include <string.h> // memcpy
192 #define SO_STATICBUF 50
193 #endif
194
195 //! Error values
196 typedef enum _ESOError
197 {
198     //! No error
199     SO_SUCCESS = 0,
200
201     /*! It looks like an option (it starts with a switch character), but 
202         it isn't registered in the option table. */
203     SO_OPT_INVALID = -1,
204
205     /*! Multiple options matched the supplied option text. 
206         Only returned when NOT using SO_O_EXACT. */
207     SO_OPT_MULTIPLE = -2,
208
209     /*! Option doesn't take an argument, but a combined argument was 
210         supplied. */
211     SO_ARG_INVALID = -3,
212
213     /*! SO_REQ_CMB style-argument was supplied to a SO_REQ_SEP option
214         Only returned when using SO_O_PEDANTIC. */
215     SO_ARG_INVALID_TYPE = -4,
216
217     //! Required argument was not supplied
218     SO_ARG_MISSING = -5,
219
220     /*! Option argument looks like another option. 
221         Only returned when NOT using SO_O_NOERR. */
222     SO_ARG_INVALID_DATA = -6
223 } ESOError;
224
225 //! Option flags
226 enum _ESOFlags
227 {
228     /*! Disallow partial matching of option names */
229     SO_O_EXACT = 0x0001,
230
231     /*! Disallow use of slash as an option marker on Windows. 
232         Un*x only ever recognizes a hyphen. */
233     SO_O_NOSLASH = 0x0002,
234
235     /*! Permit arguments on single letter options with no equals sign. 
236         e.g. -oARG or -o[ARG] */
237     SO_O_SHORTARG = 0x0004,
238
239     /*! Permit single character options to be clumped into a single 
240         option string. e.g. "-a -b -c" <==> "-abc" */
241     SO_O_CLUMP = 0x0008,
242
243     /*! Process the entire argv array for options, including the 
244         argv[0] entry. */
245     SO_O_USEALL = 0x0010,
246
247     /*! Do not generate an error for invalid options. errors for missing 
248         arguments will still be generated. invalid options will be 
249         treated as files. invalid options in clumps will be silently 
250         ignored. */
251     SO_O_NOERR = 0x0020,
252
253     /*! Validate argument type pedantically. Return an error when a 
254         separated argument "-opt arg" is supplied by the user as a 
255         combined argument "-opt=arg". By default this is not considered 
256         an error. */
257     SO_O_PEDANTIC = 0x0040,
258
259     /*! Case-insensitive comparisons for short arguments */
260     SO_O_ICASE_SHORT = 0x0100,
261
262     /*! Case-insensitive comparisons for long arguments */
263     SO_O_ICASE_LONG = 0x0200,
264
265     /*! Case-insensitive comparisons for word arguments 
266         i.e. arguments without any hyphens at the start. */
267     SO_O_ICASE_WORD = 0x0400,
268
269     /*! Case-insensitive comparisons for all arg types */
270     SO_O_ICASE = 0x0700
271 };
272
273 /*! Types of arguments that options may have. Note that some of the _ESOFlags
274     are not compatible with all argument types. SO_O_SHORTARG requires that
275     relevant options use either SO_REQ_CMB or SO_OPT. SO_O_CLUMP requires 
276     that relevant options use only SO_NONE.
277  */
278 typedef enum _ESOArgType
279 {
280     /*! No argument. Just the option flags.
281         e.g. -o         --opt */
282     SO_NONE,
283
284     /*! Required separate argument.  
285         e.g. -o ARG     --opt ARG */
286     SO_REQ_SEP,
287
288     /*! Required combined argument.  
289         e.g. -oARG      -o=ARG      --opt=ARG  */
290     SO_REQ_CMB,
291
292     /*! Optional combined argument.  
293         e.g. -o[ARG]    -o[=ARG]    --opt[=ARG] */
294     SO_OPT,
295
296     /*! Multiple separate arguments. The actual number of arguments is
297         determined programatically at the time the argument is processed.
298         e.g. -o N ARG1 ARG2 ... ARGN    --opt N ARG1 ARG2 ... ARGN */
299     SO_MULTI
300 } ESOArgType;
301
302 //! this option definition must be the last entry in the table
303 #define SO_END_OF_OPTIONS \
304     {                     \
305         -1, NULL, SO_NONE \
306     }
307
308 #ifdef _DEBUG
309 #ifdef _MSC_VER
310 #include <crtdbg.h>
311 #define SO_ASSERT(b) _ASSERTE(b)
312 #else
313 #include <assert.h>
314 #define SO_ASSERT(b) assert(b)
315 #endif
316 #else
317 #define SO_ASSERT(b) //!< assertion used to test input data
318 #endif
319
320 // ---------------------------------------------------------------------------
321 //                              MAIN TEMPLATE CLASS
322 // ---------------------------------------------------------------------------
323
324 /*! @brief Implementation of the SimpleOpt class */
325 template <class SOCHAR>
326 class CSimpleOptTempl
327 {
328 public:
329     /*! @brief Structure used to define all known options. */
330     struct SOption
331     {
332         /*! ID to return for this flag. Optional but must be >= 0 */
333         int nId;
334
335         /*! arg string to search for, e.g.  "open", "-", "-f", "--file" 
336             Note that on Windows the slash option marker will be converted
337             to a hyphen so that "-f" will also match "/f". */
338         const SOCHAR *pszArg;
339
340         /*! type of argument accepted by this option */
341         ESOArgType nArgType;
342     };
343
344     /*! @brief Initialize the class. Init() must be called later. */
345     CSimpleOptTempl()
346         : m_rgShuffleBuf(NULL)
347     {
348         Init(0, NULL, NULL, 0);
349     }
350
351     /*! @brief Initialize the class in preparation for use. */
352     CSimpleOptTempl(
353         int argc,
354         SOCHAR *argv[],
355         const SOption *a_rgOptions,
356         int a_nFlags = 0)
357         : m_rgShuffleBuf(NULL)
358     {
359         Init(argc, argv, a_rgOptions, a_nFlags);
360     }
361
362 #ifndef SO_MAX_ARGS
363     /*! @brief Deallocate any allocated memory. */
364     ~CSimpleOptTempl()
365     {
366         if (m_rgShuffleBuf)
367             free(m_rgShuffleBuf);
368     }
369 #endif
370
371     /*! @brief Initialize the class in preparation for calling Next.
372
373         The table of options pointed to by a_rgOptions does not need to be
374         valid at the time that Init() is called. However on every call to
375         Next() the table pointed to must be a valid options table with the
376         last valid entry set to SO_END_OF_OPTIONS.
377
378         NOTE: the array pointed to by a_argv will be modified by this
379         class and must not be used or modified outside of member calls to
380         this class.
381
382         @param a_argc       Argument array size
383         @param a_argv       Argument array
384         @param a_rgOptions  Valid option array
385         @param a_nFlags     Optional flags to modify the processing of 
386                             the arguments
387
388         @return true        Successful 
389         @return false       if SO_MAX_ARGC > 0:  Too many arguments
390                             if SO_MAX_ARGC == 0: Memory allocation failure
391     */
392     bool Init(
393         int a_argc,
394         SOCHAR *a_argv[],
395         const SOption *a_rgOptions,
396         int a_nFlags = 0);
397
398     /*! @brief Change the current options table during option parsing.
399
400         @param a_rgOptions  Valid option array
401      */
402     inline void SetOptions(const SOption *a_rgOptions)
403     {
404         m_rgOptions = a_rgOptions;
405     }
406
407     /*! @brief Change the current flags during option parsing.
408
409         Note that changing the SO_O_USEALL flag here will have no affect.
410         It must be set using Init() or the constructor.
411
412         @param a_nFlags     Flags to modify the processing of the arguments
413      */
414     inline void SetFlags(int a_nFlags) {
415         m_nFlags = a_nFlags; }
416
417     /*! @brief Query if a particular flag is set */
418     inline bool HasFlag(int a_nFlag) const
419     {
420         return (m_nFlags & a_nFlag) == a_nFlag;
421     }
422
423     /*! @brief Advance to the next option if available.
424
425         When all options have been processed it will return false. When true
426         has been returned, you must check for an invalid or unrecognized
427         option using the LastError() method. This will be return an error 
428         value other than SO_SUCCESS on an error. All standard data 
429         (e.g. OptionText(), OptionArg(), OptionId(), etc) will be available
430         depending on the error.
431
432         After all options have been processed, the remaining files from the
433         command line can be processed in same order as they were passed to
434         the program.
435
436         @return true    option or error available for processing
437         @return false   all options have been processed
438     */
439     bool Next();
440
441     /*! Stops processing of the command line and returns all remaining
442         arguments as files. The next call to Next() will return false.
443      */
444     void Stop();
445
446     /*! @brief Return the last error that occurred.
447
448         This function must always be called before processing the current 
449         option. This function is available only when Next() has returned true.
450      */
451     inline ESOError LastError() const {
452         return m_nLastError; }
453
454     /*! @brief Return the nId value from the options array for the current
455         option.
456
457         This function is available only when Next() has returned true.
458      */
459     inline int OptionId() const {
460         return m_nOptionId; }
461
462     /*! @brief Return the pszArg from the options array for the current 
463         option.
464
465         This function is available only when Next() has returned true.
466      */
467     inline const SOCHAR *OptionText() const {
468         return m_pszOptionText; }
469
470     /*! @brief Return the argument for the current option where one exists.
471
472         If there is no argument for the option, this will return NULL.
473         This function is available only when Next() has returned true.
474      */
475     inline SOCHAR *OptionArg() const {
476         return m_pszOptionArg; }
477
478     /*! @brief Validate and return the desired number of arguments.
479
480         This is only valid when OptionId() has return the ID of an option
481         that is registered as SO_MULTI. It may be called multiple times
482         each time returning the desired number of arguments. Previously
483         returned argument pointers are remain valid.
484
485         If an error occurs during processing, NULL will be returned and
486         the error will be available via LastError().
487
488         @param n    Number of arguments to return.
489      */
490     SOCHAR **MultiArg(int n);
491
492     /*! @brief Returned the number of entries in the Files() array.
493
494         After Next() has returned false, this will be the list of files (or
495         otherwise unprocessed arguments).
496      */
497     inline int FileCount() const {
498         return m_argc - m_nLastArg; }
499
500     /*! @brief Return the specified file argument.
501
502         @param n    Index of the file to return. This must be between 0
503                     and FileCount() - 1;
504      */
505     inline SOCHAR *File(int n) const
506     {
507         SO_ASSERT(n >= 0 && n < FileCount());
508         return m_argv[m_nLastArg + n];
509     }
510
511     /*! @brief Return the array of files. */
512     inline SOCHAR **Files() const {
513         return &m_argv[m_nLastArg]; }
514
515 private:
516     CSimpleOptTempl(const CSimpleOptTempl &);            // disabled
517     CSimpleOptTempl &operator=(const CSimpleOptTempl &); // disabled
518
519     SOCHAR PrepareArg(SOCHAR *a_pszString) const;
520     bool NextClumped();
521     void ShuffleArg(int a_nStartIdx, int a_nCount);
522     int LookupOption(const SOCHAR *a_pszOption) const;
523     int CalcMatch(const SOCHAR *a_pszSource, const SOCHAR *a_pszTest) const;
524
525     // Find the '=' character within a string.
526     inline SOCHAR *FindEquals(SOCHAR *s) const
527     {
528         while (*s && *s != (SOCHAR)'=')
529             ++s;
530         return *s ? s : NULL;
531     }
532     bool IsEqual(SOCHAR a_cLeft, SOCHAR a_cRight, int a_nArgType) const;
533
534     inline void Copy(SOCHAR **ppDst, SOCHAR **ppSrc, int nCount) const
535     {
536 #ifdef SO_MAX_ARGS
537         // keep our promise of no CLIB usage
538         while (nCount-- > 0)
539             *ppDst++ = *ppSrc++;
540 #else
541         memcpy(ppDst, ppSrc, nCount * sizeof(SOCHAR *));
542 #endif
543     }
544
545 private:
546     const SOption *m_rgOptions;    //!< pointer to options table
547     int m_nFlags;                  //!< flags
548     int m_nOptionIdx;              //!< current argv option index
549     int m_nOptionId;               //!< id of current option (-1 = invalid)
550     int m_nNextOption;             //!< index of next option
551     int m_nLastArg;                //!< last argument, after this are files
552     int m_argc;                    //!< argc to process
553     SOCHAR **m_argv;               //!< argv
554     const SOCHAR *m_pszOptionText; //!< curr option text, e.g. "-f"
555     SOCHAR *m_pszOptionArg;        //!< curr option arg, e.g. "c:\file.txt"
556     SOCHAR *m_pszClump;            //!< clumped single character options
557     SOCHAR m_szShort[3];           //!< temp for clump and combined args
558     ESOError m_nLastError;         //!< error status from the last call
559     SOCHAR **m_rgShuffleBuf;       //!< shuffle buffer for large argc
560 };
561
562 // ---------------------------------------------------------------------------
563 //                                  IMPLEMENTATION
564 // ---------------------------------------------------------------------------
565
566 template <class SOCHAR>
567 bool
568 CSimpleOptTempl<SOCHAR>::Init(
569     int a_argc,
570     SOCHAR *a_argv[],
571     const SOption *a_rgOptions,
572     int a_nFlags)
573 {
574     m_argc = a_argc;
575     m_nLastArg = a_argc;
576     m_argv = a_argv;
577     m_rgOptions = a_rgOptions;
578     m_nLastError = SO_SUCCESS;
579     m_nOptionIdx = 0;
580     m_nOptionId = -1;
581     m_pszOptionText = NULL;
582     m_pszOptionArg = NULL;
583     m_nNextOption = (a_nFlags & SO_O_USEALL) ? 0 : 1;
584     m_szShort[0] = (SOCHAR)'-';
585     m_szShort[2] = (SOCHAR)'\0';
586     m_nFlags = a_nFlags;
587     m_pszClump = NULL;
588
589 #ifdef SO_MAX_ARGS
590     if (m_argc > SO_MAX_ARGS)
591     {
592         m_nLastError = SO_ARG_INVALID_DATA;
593         m_nLastArg = 0;
594         return false;
595     }
596 #else
597     if (m_rgShuffleBuf)
598     {
599         free(m_rgShuffleBuf);
600     }
601     if (m_argc > SO_STATICBUF)
602     {
603         m_rgShuffleBuf = (SOCHAR **)malloc(sizeof(SOCHAR *) * m_argc);
604         if (!m_rgShuffleBuf)
605         {
606             return false;
607         }
608     }
609 #endif
610
611     return true;
612 }
613
614 template <class SOCHAR>
615 bool
616 CSimpleOptTempl<SOCHAR>::Next()
617 {
618 #ifdef SO_MAX_ARGS
619     if (m_argc > SO_MAX_ARGS)
620     {
621         SO_ASSERT(!"Too many args! Check the return value of Init()!");
622         return false;
623     }
624 #endif
625
626     // process a clumped option string if appropriate
627     if (m_pszClump && *m_pszClump)
628     {
629         // silently discard invalid clumped option
630         bool bIsValid = NextClumped();
631         while (*m_pszClump && !bIsValid && HasFlag(SO_O_NOERR))
632         {
633             bIsValid = NextClumped();
634         }
635
636         // return this option if valid or we are returning errors
637         if (bIsValid || !HasFlag(SO_O_NOERR))
638         {
639             return true;
640         }
641     }
642     SO_ASSERT(!m_pszClump || !*m_pszClump);
643     m_pszClump = NULL;
644
645     // init for the next option
646     m_nOptionIdx = m_nNextOption;
647     m_nOptionId = -1;
648     m_pszOptionText = NULL;
649     m_pszOptionArg = NULL;
650     m_nLastError = SO_SUCCESS;
651
652     // find the next option
653     SOCHAR cFirst;
654     int nTableIdx = -1;
655     int nOptIdx = m_nOptionIdx;
656     while (nTableIdx < 0 && nOptIdx < m_nLastArg)
657     {
658         SOCHAR *pszArg = m_argv[nOptIdx];
659         m_pszOptionArg = NULL;
660
661         // find this option in the options table
662         cFirst = PrepareArg(pszArg);
663         if (pszArg[0] == (SOCHAR)'-')
664         {
665             // find any combined argument string and remove equals sign
666             m_pszOptionArg = FindEquals(pszArg);
667             if (m_pszOptionArg)
668             {
669                 *m_pszOptionArg++ = (SOCHAR)'\0';
670             }
671         }
672         nTableIdx = LookupOption(pszArg);
673
674         // if we didn't find this option but if it is a short form
675         // option then we try the alternative forms
676         if (nTableIdx < 0 && !m_pszOptionArg && pszArg[0] == (SOCHAR)'-' && pszArg[1] && pszArg[1] != (SOCHAR)'-' && pszArg[2])
677         {
678             // test for a short-form with argument if appropriate
679             if (HasFlag(SO_O_SHORTARG))
680             {
681                 m_szShort[1] = pszArg[1];
682                 int nIdx = LookupOption(m_szShort);
683                 if (nIdx >= 0 && (m_rgOptions[nIdx].nArgType == SO_REQ_CMB || m_rgOptions[nIdx].nArgType == SO_OPT))
684                 {
685                     m_pszOptionArg = &pszArg[2];
686                     pszArg = m_szShort;
687                     nTableIdx = nIdx;
688                 }
689             }
690
691             // test for a clumped short-form option string and we didn't
692             // match on the short-form argument above
693             if (nTableIdx < 0 && HasFlag(SO_O_CLUMP))
694             {
695                 m_pszClump = &pszArg[1];
696                 ++m_nNextOption;
697                 if (nOptIdx > m_nOptionIdx)
698                 {
699                     ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx);
700                 }
701                 return Next();
702             }
703         }
704
705         // The option wasn't found. If it starts with a switch character
706         // and we are not suppressing errors for invalid options then it
707         // is reported as an error, otherwise it is data.
708         if (nTableIdx < 0)
709         {
710             if (!HasFlag(SO_O_NOERR) && pszArg[0] == (SOCHAR)'-')
711             {
712                 m_pszOptionText = pszArg;
713                 break;
714             }
715
716             pszArg[0] = cFirst;
717             ++nOptIdx;
718             if (m_pszOptionArg)
719             {
720                 *(--m_pszOptionArg) = (SOCHAR)'=';
721             }
722         }
723     }
724
725     // end of options
726     if (nOptIdx >= m_nLastArg)
727     {
728         if (nOptIdx > m_nOptionIdx)
729         {
730             ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx);
731         }
732         return false;
733     }
734     ++m_nNextOption;
735
736     // get the option id
737     ESOArgType nArgType = SO_NONE;
738     if (nTableIdx < 0)
739     {
740         m_nLastError = (ESOError)nTableIdx; // error code
741     }
742     else
743     {
744         m_nOptionId = m_rgOptions[nTableIdx].nId;
745         m_pszOptionText = m_rgOptions[nTableIdx].pszArg;
746
747         // ensure that the arg type is valid
748         nArgType = m_rgOptions[nTableIdx].nArgType;
749         switch (nArgType)
750         {
751             case SO_NONE:
752                 if (m_pszOptionArg)
753                 {
754                     m_nLastError = SO_ARG_INVALID;
755                 }
756                 break;
757
758             case SO_REQ_SEP:
759                 if (m_pszOptionArg)
760                 {
761                     // they wanted separate args, but we got a combined one,
762                     // unless we are pedantic, just accept it.
763                     if (HasFlag(SO_O_PEDANTIC))
764                     {
765                         m_nLastError = SO_ARG_INVALID_TYPE;
766                     }
767                 }
768                 // more processing after we shuffle
769                 break;
770
771             case SO_REQ_CMB:
772                 if (!m_pszOptionArg)
773                 {
774                     m_nLastError = SO_ARG_MISSING;
775                 }
776                 break;
777
778             case SO_OPT:
779                 // nothing to do
780                 break;
781
782             case SO_MULTI:
783                 // nothing to do. Caller must now check for valid arguments
784                 // using GetMultiArg()
785                 break;
786         }
787     }
788
789     // shuffle the files out of the way
790     if (nOptIdx > m_nOptionIdx)
791     {
792         ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx);
793     }
794
795     // we need to return the separate arg if required, just re-use the
796     // multi-arg code because it all does the same thing
797     if (nArgType == SO_REQ_SEP && !m_pszOptionArg && m_nLastError == SO_SUCCESS)
798     {
799         SOCHAR **ppArgs = MultiArg(1);
800         if (ppArgs)
801         {
802             m_pszOptionArg = *ppArgs;
803         }
804     }
805
806     return true;
807 }
808
809 template <class SOCHAR>
810 void
811 CSimpleOptTempl<SOCHAR>::Stop()
812 {
813     if (m_nNextOption < m_nLastArg)
814     {
815         ShuffleArg(m_nNextOption, m_nLastArg - m_nNextOption);
816     }
817 }
818
819 template <class SOCHAR>
820 SOCHAR
821 CSimpleOptTempl<SOCHAR>::PrepareArg(
822     SOCHAR *a_pszString) const
823 {
824 #ifdef _WIN32
825     // On Windows we can accept the forward slash as a single character
826     // option delimiter, but it cannot replace the '-' option used to
827     // denote stdin. On Un*x paths may start with slash so it may not
828     // be used to start an option.
829     if (!HasFlag(SO_O_NOSLASH) && a_pszString[0] == (SOCHAR)'/' && a_pszString[1] && a_pszString[1] != (SOCHAR)'-')
830     {
831         a_pszString[0] = (SOCHAR)'-';
832         return (SOCHAR)'/';
833     }
834 #endif
835     return a_pszString[0];
836 }
837
838 template <class SOCHAR>
839 bool
840 CSimpleOptTempl<SOCHAR>::NextClumped()
841 {
842     // prepare for the next clumped option
843     m_szShort[1] = *m_pszClump++;
844     m_nOptionId = -1;
845     m_pszOptionText = NULL;
846     m_pszOptionArg = NULL;
847     m_nLastError = SO_SUCCESS;
848
849     // lookup this option, ensure that we are using exact matching
850     int nSavedFlags = m_nFlags;
851     m_nFlags = SO_O_EXACT;
852     int nTableIdx = LookupOption(m_szShort);
853     m_nFlags = nSavedFlags;
854
855     // unknown option
856     if (nTableIdx < 0)
857     {
858         m_pszOptionText = m_szShort;        // invalid option
859         m_nLastError = (ESOError)nTableIdx; // error code
860         return false;
861     }
862
863     // valid option
864     m_pszOptionText = m_rgOptions[nTableIdx].pszArg;
865     ESOArgType nArgType = m_rgOptions[nTableIdx].nArgType;
866     if (nArgType == SO_NONE)
867     {
868         m_nOptionId = m_rgOptions[nTableIdx].nId;
869         return true;
870     }
871
872     if (nArgType == SO_REQ_CMB && *m_pszClump)
873     {
874         m_nOptionId = m_rgOptions[nTableIdx].nId;
875         m_pszOptionArg = m_pszClump;
876         while (*m_pszClump)
877             ++m_pszClump; // must point to an empty string
878         return true;
879     }
880
881     // invalid option as it requires an argument
882     m_nLastError = SO_ARG_MISSING;
883     return true;
884 }
885
886 // Shuffle arguments to the end of the argv array.
887 //
888 // For example:
889 //      argv[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8" };
890 //
891 //  ShuffleArg(1, 1) = { "0", "2", "3", "4", "5", "6", "7", "8", "1" };
892 //  ShuffleArg(5, 2) = { "0", "1", "2", "3", "4", "7", "8", "5", "6" };
893 //  ShuffleArg(2, 4) = { "0", "1", "6", "7", "8", "2", "3", "4", "5" };
894 template <class SOCHAR>
895 void
896 CSimpleOptTempl<SOCHAR>::ShuffleArg(
897     int a_nStartIdx,
898     int a_nCount)
899 {
900     SOCHAR *staticBuf[SO_STATICBUF];
901     SOCHAR **buf = m_rgShuffleBuf ? m_rgShuffleBuf : staticBuf;
902     int nTail = m_argc - a_nStartIdx - a_nCount;
903
904     // make a copy of the elements to be moved
905     Copy(buf, m_argv + a_nStartIdx, a_nCount);
906
907     // move the tail down
908     Copy(m_argv + a_nStartIdx, m_argv + a_nStartIdx + a_nCount, nTail);
909
910     // append the moved elements to the tail
911     Copy(m_argv + a_nStartIdx + nTail, buf, a_nCount);
912
913     // update the index of the last unshuffled arg
914     m_nLastArg -= a_nCount;
915 }
916
917 // match on the long format strings. partial matches will be
918 // accepted only if that feature is enabled.
919 template <class SOCHAR>
920 int
921 CSimpleOptTempl<SOCHAR>::LookupOption(
922     const SOCHAR *a_pszOption) const
923 {
924     int nBestMatch = -1;   // index of best match so far
925     int nBestMatchLen = 0; // matching characters of best match
926     int nLastMatchLen = 0; // matching characters of last best match
927
928     for (int n = 0; m_rgOptions[n].nId >= 0; ++n)
929     {
930         // the option table must use hyphens as the option character,
931         // the slash character is converted to a hyphen for testing.
932         SO_ASSERT(m_rgOptions[n].pszArg[0] != (SOCHAR)'/');
933
934         int nMatchLen = CalcMatch(m_rgOptions[n].pszArg, a_pszOption);
935         if (nMatchLen == -1)
936         {
937             return n;
938         }
939         if (nMatchLen > 0 && nMatchLen >= nBestMatchLen)
940         {
941             nLastMatchLen = nBestMatchLen;
942             nBestMatchLen = nMatchLen;
943             nBestMatch = n;
944         }
945     }
946
947     // only partial matches or no match gets to here, ensure that we
948     // don't return a partial match unless it is a clear winner
949     if (HasFlag(SO_O_EXACT) || nBestMatch == -1)
950     {
951         return SO_OPT_INVALID;
952     }
953     return (nBestMatchLen > nLastMatchLen) ? nBestMatch : SO_OPT_MULTIPLE;
954 }
955
956 // calculate the number of characters that match (case-sensitive)
957 // 0 = no match, > 0 == number of characters, -1 == perfect match
958 template <class SOCHAR>
959 int
960 CSimpleOptTempl<SOCHAR>::CalcMatch(
961     const SOCHAR *a_pszSource,
962     const SOCHAR *a_pszTest) const
963 {
964     if (!a_pszSource || !a_pszTest)
965     {
966         return 0;
967     }
968
969     // determine the argument type
970     int nArgType = SO_O_ICASE_LONG;
971     if (a_pszSource[0] != '-')
972     {
973         nArgType = SO_O_ICASE_WORD;
974     }
975     else if (a_pszSource[1] != '-' && !a_pszSource[2])
976     {
977         nArgType = SO_O_ICASE_SHORT;
978     }
979
980     // match and skip leading hyphens
981     while (*a_pszSource == (SOCHAR)'-' && *a_pszSource == *a_pszTest)
982     {
983         ++a_pszSource;
984         ++a_pszTest;
985     }
986     if (*a_pszSource == (SOCHAR)'-' || *a_pszTest == (SOCHAR)'-')
987     {
988         return 0;
989     }
990
991     // find matching number of characters in the strings
992     int nLen = 0;
993     while (*a_pszSource && IsEqual(*a_pszSource, *a_pszTest, nArgType))
994     {
995         ++a_pszSource;
996         ++a_pszTest;
997         ++nLen;
998     }
999
1000     // if we have exhausted the source...
1001     if (!*a_pszSource)
1002     {
1003         // and the test strings, then it's a perfect match
1004         if (!*a_pszTest)
1005         {
1006             return -1;
1007         }
1008
1009         // otherwise the match failed as the test is longer than
1010         // the source. i.e. "--mant" will not match the option "--man".
1011         return 0;
1012     }
1013
1014     // if we haven't exhausted the test string then it is not a match
1015     // i.e. "--mantle" will not best-fit match to "--mandate" at all.
1016     if (*a_pszTest)
1017     {
1018         return 0;
1019     }
1020
1021     // partial match to the current length of the test string
1022     return nLen;
1023 }
1024
1025 template <class SOCHAR>
1026 bool
1027 CSimpleOptTempl<SOCHAR>::IsEqual(
1028     SOCHAR a_cLeft,
1029     SOCHAR a_cRight,
1030     int a_nArgType) const
1031 {
1032     // if this matches then we are doing case-insensitive matching
1033     if (m_nFlags & a_nArgType)
1034     {
1035         if (a_cLeft >= 'A' && a_cLeft <= 'Z')
1036             a_cLeft += 'a' - 'A';
1037         if (a_cRight >= 'A' && a_cRight <= 'Z')
1038             a_cRight += 'a' - 'A';
1039     }
1040     return a_cLeft == a_cRight;
1041 }
1042
1043 // calculate the number of characters that match (case-sensitive)
1044 // 0 = no match, > 0 == number of characters, -1 == perfect match
1045 template <class SOCHAR>
1046 SOCHAR **
1047 CSimpleOptTempl<SOCHAR>::MultiArg(
1048     int a_nCount)
1049 {
1050     // ensure we have enough arguments
1051     if (m_nNextOption + a_nCount > m_nLastArg)
1052     {
1053         m_nLastError = SO_ARG_MISSING;
1054         return NULL;
1055     }
1056
1057     // our argument array
1058     SOCHAR **rgpszArg = &m_argv[m_nNextOption];
1059
1060     // Ensure that each of the following don't start with an switch character.
1061     // Only make this check if we are returning errors for unknown arguments.
1062     if (!HasFlag(SO_O_NOERR))
1063     {
1064         for (int n = 0; n < a_nCount; ++n)
1065         {
1066             SOCHAR ch = PrepareArg(rgpszArg[n]);
1067             if (rgpszArg[n][0] == (SOCHAR)'-')
1068             {
1069                 rgpszArg[n][0] = ch;
1070                 m_nLastError = SO_ARG_INVALID_DATA;
1071                 return NULL;
1072             }
1073             rgpszArg[n][0] = ch;
1074         }
1075     }
1076
1077     // all good
1078     m_nNextOption += a_nCount;
1079     return rgpszArg;
1080 }
1081
1082 // ---------------------------------------------------------------------------
1083 //                                  TYPE DEFINITIONS
1084 // ---------------------------------------------------------------------------
1085
1086 /*! @brief ASCII/MBCS version of CSimpleOpt */
1087 typedef CSimpleOptTempl<char> CSimpleOptA;
1088
1089 /*! @brief wchar_t version of CSimpleOpt */
1090 typedef CSimpleOptTempl<wchar_t> CSimpleOptW;
1091
1092 #if defined(_UNICODE)
1093 /*! @brief TCHAR version dependent on if _UNICODE is defined */
1094 #define CSimpleOpt CSimpleOptW
1095 #else
1096 /*! @brief TCHAR version dependent on if _UNICODE is defined */
1097 #define CSimpleOpt CSimpleOptA
1098 #endif
1099
1100 #endif // INCLUDED_SimpleOpt