toulbar2
SimpleOpt.h
1
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
196typedef enum _ESOError {
198 SO_SUCCESS = 0,
199
202 SO_OPT_INVALID = -1,
203
206 SO_OPT_MULTIPLE = -2,
207
210 SO_ARG_INVALID = -3,
211
214 SO_ARG_INVALID_TYPE = -4,
215
217 SO_ARG_MISSING = -5,
218
221 SO_ARG_INVALID_DATA = -6
222} ESOError;
223
225enum _ESOFlags {
227 SO_O_EXACT = 0x0001,
228
231 SO_O_NOSLASH = 0x0002,
232
235 SO_O_SHORTARG = 0x0004,
236
239 SO_O_CLUMP = 0x0008,
240
243 SO_O_USEALL = 0x0010,
244
249 SO_O_NOERR = 0x0020,
250
255 SO_O_PEDANTIC = 0x0040,
256
258 SO_O_ICASE_SHORT = 0x0100,
259
261 SO_O_ICASE_LONG = 0x0200,
262
265 SO_O_ICASE_WORD = 0x0400,
266
268 SO_O_ICASE = 0x0700
269};
270
276typedef enum _ESOArgType {
279 SO_NONE,
280
283 SO_REQ_SEP,
284
287 SO_REQ_CMB,
288
291 SO_OPT,
292
296 SO_MULTI
297} ESOArgType;
298
300#define SO_END_OF_OPTIONS \
301 { \
302 -1, NULL, SO_NONE \
303 }
304
305#ifdef _DEBUG
306#ifdef _MSC_VER
307#include <crtdbg.h>
308#define SO_ASSERT(b) _ASSERTE(b)
309#else
310#include <assert.h>
311#define SO_ASSERT(b) assert(b)
312#endif
313#else
314#define SO_ASSERT(b)
315#endif
316
317// ---------------------------------------------------------------------------
318// MAIN TEMPLATE CLASS
319// ---------------------------------------------------------------------------
320
322template <class SOCHAR>
323class CSimpleOptTempl {
324public:
326 struct SOption {
328 int nId;
329
333 const SOCHAR* pszArg;
334
336 ESOArgType nArgType;
337 };
338
340 CSimpleOptTempl()
341 : m_rgShuffleBuf(NULL)
342 {
343 Init(0, NULL, NULL, 0);
344 }
345
347 CSimpleOptTempl(
348 int argc,
349 SOCHAR* argv[],
350 const SOption* a_rgOptions,
351 int a_nFlags = 0)
352 : m_rgShuffleBuf(NULL)
353 {
354 Init(argc, argv, a_rgOptions, a_nFlags);
355 }
356
357#ifndef SO_MAX_ARGS
359 ~CSimpleOptTempl()
360 {
361 if (m_rgShuffleBuf)
362 free(m_rgShuffleBuf);
363 }
364#endif
365
387 bool Init(
388 int a_argc,
389 SOCHAR* a_argv[],
390 const SOption* a_rgOptions,
391 int a_nFlags = 0);
392
397 inline void SetOptions(const SOption* a_rgOptions)
398 {
399 m_rgOptions = a_rgOptions;
400 }
401
409 inline void SetFlags(int a_nFlags) { m_nFlags = a_nFlags; }
410
412 inline bool HasFlag(int a_nFlag) const
413 {
414 return (m_nFlags & a_nFlag) == a_nFlag;
415 }
416
433 bool Next();
434
438 void Stop();
439
445 inline ESOError LastError() const { return m_nLastError; }
446
452 inline int OptionId() const { return m_nOptionId; }
453
459 inline const SOCHAR* OptionText() const { return m_pszOptionText; }
460
466 inline SOCHAR* OptionArg() const { return m_pszOptionArg; }
467
480 SOCHAR** MultiArg(int n);
481
487 inline int FileCount() const { return m_argc - m_nLastArg; }
488
494 inline SOCHAR* File(int n) const
495 {
496 SO_ASSERT(n >= 0 && n < FileCount());
497 return m_argv[m_nLastArg + n];
498 }
499
501 inline SOCHAR** Files() const { return &m_argv[m_nLastArg]; }
502
503private:
504 CSimpleOptTempl(const CSimpleOptTempl&); // disabled
505 CSimpleOptTempl& operator=(const CSimpleOptTempl&); // disabled
506
507 SOCHAR PrepareArg(SOCHAR* a_pszString) const;
508 bool NextClumped();
509 void ShuffleArg(int a_nStartIdx, int a_nCount);
510 int LookupOption(const SOCHAR* a_pszOption) const;
511 int CalcMatch(const SOCHAR* a_pszSource, const SOCHAR* a_pszTest) const;
512
513 // Find the '=' character within a string.
514 inline SOCHAR* FindEquals(SOCHAR* s) const
515 {
516 while (*s && *s != (SOCHAR)'=')
517 ++s;
518 return *s ? s : NULL;
519 }
520 bool IsEqual(SOCHAR a_cLeft, SOCHAR a_cRight, int a_nArgType) const;
521
522 inline void Copy(SOCHAR** ppDst, SOCHAR** ppSrc, int nCount) const
523 {
524#ifdef SO_MAX_ARGS
525 // keep our promise of no CLIB usage
526 while (nCount-- > 0)
527 *ppDst++ = *ppSrc++;
528#else
529 memmove(ppDst, ppSrc, nCount * sizeof(SOCHAR*));
530#endif
531 }
532
533private:
534 const SOption* m_rgOptions;
535 int m_nFlags;
536 int m_nOptionIdx;
537 int m_nOptionId;
538 int m_nNextOption;
539 int m_nLastArg;
540 int m_argc;
541 SOCHAR** m_argv;
542 const SOCHAR* m_pszOptionText;
543 SOCHAR* m_pszOptionArg;
544 SOCHAR* m_pszClump;
545 SOCHAR m_szShort[3];
546 ESOError m_nLastError;
547 SOCHAR** m_rgShuffleBuf;
548};
549
550// ---------------------------------------------------------------------------
551// IMPLEMENTATION
552// ---------------------------------------------------------------------------
553
554template <class SOCHAR>
555bool CSimpleOptTempl<SOCHAR>::Init(
556 int a_argc,
557 SOCHAR* a_argv[],
558 const SOption* a_rgOptions,
559 int a_nFlags)
560{
561 m_argc = a_argc;
562 m_nLastArg = a_argc;
563 m_argv = a_argv;
564 m_rgOptions = a_rgOptions;
565 m_nLastError = SO_SUCCESS;
566 m_nOptionIdx = 0;
567 m_nOptionId = -1;
568 m_pszOptionText = NULL;
569 m_pszOptionArg = NULL;
570 m_nNextOption = (a_nFlags & SO_O_USEALL) ? 0 : 1;
571 m_szShort[0] = (SOCHAR)'-';
572 m_szShort[2] = (SOCHAR)'\0';
573 m_nFlags = a_nFlags;
574 m_pszClump = NULL;
575
576#ifdef SO_MAX_ARGS
577 if (m_argc > SO_MAX_ARGS) {
578 m_nLastError = SO_ARG_INVALID_DATA;
579 m_nLastArg = 0;
580 return false;
581 }
582#else
583 if (m_rgShuffleBuf) {
584 free(m_rgShuffleBuf);
585 }
586 if (m_argc > SO_STATICBUF) {
587 m_rgShuffleBuf = (SOCHAR**)malloc(sizeof(SOCHAR*) * m_argc);
588 if (!m_rgShuffleBuf) {
589 return false;
590 }
591 }
592#endif
593
594 return true;
595}
596
597template <class SOCHAR>
598bool CSimpleOptTempl<SOCHAR>::Next()
599{
600#ifdef SO_MAX_ARGS
601 if (m_argc > SO_MAX_ARGS) {
602 SO_ASSERT(!"Too many args! Check the return value of Init()!");
603 return false;
604 }
605#endif
606
607 // process a clumped option string if appropriate
608 if (m_pszClump && *m_pszClump) {
609 // silently discard invalid clumped option
610 bool bIsValid = NextClumped();
611 while (*m_pszClump && !bIsValid && HasFlag(SO_O_NOERR)) {
612 bIsValid = NextClumped();
613 }
614
615 // return this option if valid or we are returning errors
616 if (bIsValid || !HasFlag(SO_O_NOERR)) {
617 return true;
618 }
619 }
620 SO_ASSERT(!m_pszClump || !*m_pszClump);
621 m_pszClump = NULL;
622
623 // init for the next option
624 m_nOptionIdx = m_nNextOption;
625 m_nOptionId = -1;
626 m_pszOptionText = NULL;
627 m_pszOptionArg = NULL;
628 m_nLastError = SO_SUCCESS;
629
630 // find the next option
631 SOCHAR cFirst;
632 int nTableIdx = -1;
633 int nOptIdx = m_nOptionIdx;
634 while (nTableIdx < 0 && nOptIdx < m_nLastArg) {
635 SOCHAR* pszArg = m_argv[nOptIdx];
636 m_pszOptionArg = NULL;
637
638 // find this option in the options table
639 cFirst = PrepareArg(pszArg);
640 if (pszArg[0] == (SOCHAR)'-') {
641 // find any combined argument string and remove equals sign
642 m_pszOptionArg = FindEquals(pszArg);
643 if (m_pszOptionArg) {
644 *m_pszOptionArg++ = (SOCHAR)'\0';
645 }
646 }
647 nTableIdx = LookupOption(pszArg);
648
649 // if we didn't find this option but if it is a short form
650 // option then we try the alternative forms
651 if (nTableIdx < 0
652 && !m_pszOptionArg
653 && pszArg[0] == (SOCHAR)'-'
654 && pszArg[1]
655 && pszArg[1] != (SOCHAR)'-'
656 && pszArg[2]) {
657 // test for a short-form with argument if appropriate
658 if (HasFlag(SO_O_SHORTARG)) {
659 m_szShort[1] = pszArg[1];
660 int nIdx = LookupOption(m_szShort);
661 if (nIdx >= 0
662 && (m_rgOptions[nIdx].nArgType == SO_REQ_CMB
663 || m_rgOptions[nIdx].nArgType == SO_OPT)) {
664 m_pszOptionArg = &pszArg[2];
665 pszArg = m_szShort;
666 nTableIdx = nIdx;
667 }
668 }
669
670 // test for a clumped short-form option string and we didn't
671 // match on the short-form argument above
672 if (nTableIdx < 0 && HasFlag(SO_O_CLUMP)) {
673 m_pszClump = &pszArg[1];
674 ++m_nNextOption;
675 if (nOptIdx > m_nOptionIdx) {
676 ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx);
677 }
678 return Next();
679 }
680 }
681
682 // The option wasn't found. If it starts with a switch character
683 // and we are not suppressing errors for invalid options then it
684 // is reported as an error, otherwise it is data.
685 if (nTableIdx < 0) {
686 if (!HasFlag(SO_O_NOERR) && pszArg[0] == (SOCHAR)'-') {
687 m_pszOptionText = pszArg;
688 break;
689 }
690
691 pszArg[0] = cFirst;
692 ++nOptIdx;
693 if (m_pszOptionArg) {
694 *(--m_pszOptionArg) = (SOCHAR)'=';
695 }
696 }
697 }
698
699 // end of options
700 if (nOptIdx >= m_nLastArg) {
701 if (nOptIdx > m_nOptionIdx) {
702 ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx);
703 }
704 return false;
705 }
706 ++m_nNextOption;
707
708 // get the option id
709 ESOArgType nArgType = SO_NONE;
710 if (nTableIdx < 0) {
711 m_nLastError = (ESOError)nTableIdx; // error code
712 } else {
713 m_nOptionId = m_rgOptions[nTableIdx].nId;
714 m_pszOptionText = m_rgOptions[nTableIdx].pszArg;
715
716 // ensure that the arg type is valid
717 nArgType = m_rgOptions[nTableIdx].nArgType;
718 switch (nArgType) {
719 case SO_NONE:
720 if (m_pszOptionArg) {
721 m_nLastError = SO_ARG_INVALID;
722 }
723 break;
724
725 case SO_REQ_SEP:
726 if (m_pszOptionArg) {
727 // they wanted separate args, but we got a combined one,
728 // unless we are pedantic, just accept it.
729 if (HasFlag(SO_O_PEDANTIC)) {
730 m_nLastError = SO_ARG_INVALID_TYPE;
731 }
732 }
733 // more processing after we shuffle
734 break;
735
736 case SO_REQ_CMB:
737 if (!m_pszOptionArg) {
738 m_nLastError = SO_ARG_MISSING;
739 }
740 break;
741
742 case SO_OPT:
743 // nothing to do
744 break;
745
746 case SO_MULTI:
747 // nothing to do. Caller must now check for valid arguments
748 // using GetMultiArg()
749 break;
750 }
751 }
752
753 // shuffle the files out of the way
754 if (nOptIdx > m_nOptionIdx) {
755 ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx);
756 }
757
758 // we need to return the separate arg if required, just re-use the
759 // multi-arg code because it all does the same thing
760 if (nArgType == SO_REQ_SEP
761 && !m_pszOptionArg
762 && m_nLastError == SO_SUCCESS) {
763 SOCHAR** ppArgs = MultiArg(1);
764 if (ppArgs) {
765 m_pszOptionArg = *ppArgs;
766 }
767 }
768
769 return true;
770}
771
772template <class SOCHAR>
773void CSimpleOptTempl<SOCHAR>::Stop()
774{
775 if (m_nNextOption < m_nLastArg) {
776 ShuffleArg(m_nNextOption, m_nLastArg - m_nNextOption);
777 }
778}
779
780template <class SOCHAR>
781SOCHAR
782CSimpleOptTempl<SOCHAR>::PrepareArg(
783 SOCHAR* a_pszString) const
784{
785#ifdef __WIN32__
786 // On Windows we can accept the forward slash as a single character
787 // option delimiter, but it cannot replace the '-' option used to
788 // denote stdin. On Un*x paths may start with slash so it may not
789 // be used to start an option.
790 if (!HasFlag(SO_O_NOSLASH)
791 && a_pszString[0] == (SOCHAR)'/'
792 && a_pszString[1]
793 && a_pszString[1] != (SOCHAR)'-') {
794 a_pszString[0] = (SOCHAR)'-';
795 return (SOCHAR)'/';
796 }
797#endif
798 return a_pszString[0];
799}
800
801template <class SOCHAR>
802bool CSimpleOptTempl<SOCHAR>::NextClumped()
803{
804 // prepare for the next clumped option
805 m_szShort[1] = *m_pszClump++;
806 m_nOptionId = -1;
807 m_pszOptionText = NULL;
808 m_pszOptionArg = NULL;
809 m_nLastError = SO_SUCCESS;
810
811 // lookup this option, ensure that we are using exact matching
812 int nSavedFlags = m_nFlags;
813 m_nFlags = SO_O_EXACT;
814 int nTableIdx = LookupOption(m_szShort);
815 m_nFlags = nSavedFlags;
816
817 // unknown option
818 if (nTableIdx < 0) {
819 m_pszOptionText = m_szShort; // invalid option
820 m_nLastError = (ESOError)nTableIdx; // error code
821 return false;
822 }
823
824 // valid option
825 m_pszOptionText = m_rgOptions[nTableIdx].pszArg;
826 ESOArgType nArgType = m_rgOptions[nTableIdx].nArgType;
827 if (nArgType == SO_NONE) {
828 m_nOptionId = m_rgOptions[nTableIdx].nId;
829 return true;
830 }
831
832 if (nArgType == SO_REQ_CMB && *m_pszClump) {
833 m_nOptionId = m_rgOptions[nTableIdx].nId;
834 m_pszOptionArg = m_pszClump;
835 while (*m_pszClump)
836 ++m_pszClump; // must point to an empty string
837 return true;
838 }
839
840 // invalid option as it requires an argument
841 m_nLastError = SO_ARG_MISSING;
842 return true;
843}
844
845// Shuffle arguments to the end of the argv array.
846//
847// For example:
848// argv[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8" };
849//
850// ShuffleArg(1, 1) = { "0", "2", "3", "4", "5", "6", "7", "8", "1" };
851// ShuffleArg(5, 2) = { "0", "1", "2", "3", "4", "7", "8", "5", "6" };
852// ShuffleArg(2, 4) = { "0", "1", "6", "7", "8", "2", "3", "4", "5" };
853template <class SOCHAR>
854void CSimpleOptTempl<SOCHAR>::ShuffleArg(
855 int a_nStartIdx,
856 int a_nCount)
857{
858 SOCHAR* staticBuf[SO_STATICBUF];
859 SOCHAR** buf = m_rgShuffleBuf ? m_rgShuffleBuf : staticBuf;
860 int nTail = m_argc - a_nStartIdx - a_nCount;
861
862 // make a copy of the elements to be moved
863 Copy(buf, m_argv + a_nStartIdx, a_nCount);
864
865 // move the tail down
866 Copy(m_argv + a_nStartIdx, m_argv + a_nStartIdx + a_nCount, nTail);
867
868 // append the moved elements to the tail
869 Copy(m_argv + a_nStartIdx + nTail, buf, a_nCount);
870
871 // update the index of the last unshuffled arg
872 m_nLastArg -= a_nCount;
873}
874
875// match on the long format strings. partial matches will be
876// accepted only if that feature is enabled.
877template <class SOCHAR>
878int CSimpleOptTempl<SOCHAR>::LookupOption(
879 const SOCHAR* a_pszOption) const
880{
881 int nBestMatch = -1; // index of best match so far
882 int nBestMatchLen = 0; // matching characters of best match
883 int nLastMatchLen = 0; // matching characters of last best match
884
885 for (int n = 0; m_rgOptions[n].nId >= 0; ++n) {
886 // the option table must use hyphens as the option character,
887 // the slash character is converted to a hyphen for testing.
888 SO_ASSERT(m_rgOptions[n].pszArg[0] != (SOCHAR)'/');
889
890 int nMatchLen = CalcMatch(m_rgOptions[n].pszArg, a_pszOption);
891 if (nMatchLen == -1) {
892 return n;
893 }
894 if (nMatchLen > 0 && nMatchLen >= nBestMatchLen) {
895 nLastMatchLen = nBestMatchLen;
896 nBestMatchLen = nMatchLen;
897 nBestMatch = n;
898 }
899 }
900
901 // only partial matches or no match gets to here, ensure that we
902 // don't return a partial match unless it is a clear winner
903 if (HasFlag(SO_O_EXACT) || nBestMatch == -1) {
904 return SO_OPT_INVALID;
905 }
906 return (nBestMatchLen > nLastMatchLen) ? nBestMatch : SO_OPT_MULTIPLE;
907}
908
909// calculate the number of characters that match (case-sensitive)
910// 0 = no match, > 0 == number of characters, -1 == perfect match
911template <class SOCHAR>
912int CSimpleOptTempl<SOCHAR>::CalcMatch(
913 const SOCHAR* a_pszSource,
914 const SOCHAR* a_pszTest) const
915{
916 if (!a_pszSource || !a_pszTest) {
917 return 0;
918 }
919
920 // determine the argument type
921 int nArgType = SO_O_ICASE_LONG;
922 if (a_pszSource[0] != '-') {
923 nArgType = SO_O_ICASE_WORD;
924 } else if (a_pszSource[1] != '-' && !a_pszSource[2]) {
925 nArgType = SO_O_ICASE_SHORT;
926 }
927
928 // match and skip leading hyphens
929 while (*a_pszSource == (SOCHAR)'-' && *a_pszSource == *a_pszTest) {
930 ++a_pszSource;
931 ++a_pszTest;
932 }
933 if (*a_pszSource == (SOCHAR)'-' || *a_pszTest == (SOCHAR)'-') {
934 return 0;
935 }
936
937 // find matching number of characters in the strings
938 int nLen = 0;
939 while (*a_pszSource && IsEqual(*a_pszSource, *a_pszTest, nArgType)) {
940 ++a_pszSource;
941 ++a_pszTest;
942 ++nLen;
943 }
944
945 // if we have exhausted the source...
946 if (!*a_pszSource) {
947 // and the test strings, then it's a perfect match
948 if (!*a_pszTest) {
949 return -1;
950 }
951
952 // otherwise the match failed as the test is longer than
953 // the source. i.e. "--mant" will not match the option "--man".
954 return 0;
955 }
956
957 // if we haven't exhausted the test string then it is not a match
958 // i.e. "--mantle" will not best-fit match to "--mandate" at all.
959 if (*a_pszTest) {
960 return 0;
961 }
962
963 // partial match to the current length of the test string
964 return nLen;
965}
966
967template <class SOCHAR>
968bool CSimpleOptTempl<SOCHAR>::IsEqual(
969 SOCHAR a_cLeft,
970 SOCHAR a_cRight,
971 int a_nArgType) const
972{
973 // if this matches then we are doing case-insensitive matching
974 if (m_nFlags & a_nArgType) {
975 if (a_cLeft >= 'A' && a_cLeft <= 'Z')
976 a_cLeft += 'a' - 'A';
977 if (a_cRight >= 'A' && a_cRight <= 'Z')
978 a_cRight += 'a' - 'A';
979 }
980 return a_cLeft == a_cRight;
981}
982
983// calculate the number of characters that match (case-sensitive)
984// 0 = no match, > 0 == number of characters, -1 == perfect match
985template <class SOCHAR>
986SOCHAR**
987CSimpleOptTempl<SOCHAR>::MultiArg(
988 int a_nCount)
989{
990 // ensure we have enough arguments
991 if (m_nNextOption + a_nCount > m_nLastArg) {
992 m_nLastError = SO_ARG_MISSING;
993 return NULL;
994 }
995
996 // our argument array
997 SOCHAR** rgpszArg = &m_argv[m_nNextOption];
998
999 // Ensure that each of the following don't start with an switch character.
1000 // Only make this check if we are returning errors for unknown arguments.
1001 if (!HasFlag(SO_O_NOERR)) {
1002 for (int n = 0; n < a_nCount; ++n) {
1003 SOCHAR ch = PrepareArg(rgpszArg[n]);
1004 if (rgpszArg[n][0] == (SOCHAR)'-') {
1005 rgpszArg[n][0] = ch;
1006 m_nLastError = SO_ARG_INVALID_DATA;
1007 return NULL;
1008 }
1009 rgpszArg[n][0] = ch;
1010 }
1011 }
1012
1013 // all good
1014 m_nNextOption += a_nCount;
1015 return rgpszArg;
1016}
1017
1018// ---------------------------------------------------------------------------
1019// TYPE DEFINITIONS
1020// ---------------------------------------------------------------------------
1021
1023typedef CSimpleOptTempl<char> CSimpleOptA;
1024
1026typedef CSimpleOptTempl<wchar_t> CSimpleOptW;
1027
1028#if defined(_UNICODE)
1030#define CSimpleOpt CSimpleOptW
1031#else
1033#define CSimpleOpt CSimpleOptA
1034#endif
1035
1036#endif // INCLUDED_SimpleOpt