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 
196 typedef 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 
225 enum _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 
276 typedef 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 
322 template <class SOCHAR>
323 class CSimpleOptTempl {
324 public:
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 
503 private:
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 
533 private:
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 
554 template <class SOCHAR>
555 bool 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 
597 template <class SOCHAR>
598 bool 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 
772 template <class SOCHAR>
773 void CSimpleOptTempl<SOCHAR>::Stop()
774 {
775  if (m_nNextOption < m_nLastArg) {
776  ShuffleArg(m_nNextOption, m_nLastArg - m_nNextOption);
777  }
778 }
779 
780 template <class SOCHAR>
781 SOCHAR
782 CSimpleOptTempl<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 
801 template <class SOCHAR>
802 bool 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" };
853 template <class SOCHAR>
854 void 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.
877 template <class SOCHAR>
878 int 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
911 template <class SOCHAR>
912 int 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 
967 template <class SOCHAR>
968 bool 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
985 template <class SOCHAR>
986 SOCHAR**
987 CSimpleOptTempl<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 
1023 typedef CSimpleOptTempl<char> CSimpleOptA;
1024 
1026 typedef 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