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