PageRenderTime 79ms CodeModel.GetById 33ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 1ms

/include/SimpleOpt.h

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