diff --git a/external/header_only/SimpleGlob.h b/external/header_only/SimpleGlob.h
new file mode 100644
index 0000000000..dddc8d1bd1
--- /dev/null
+++ b/external/header_only/SimpleGlob.h
@@ -0,0 +1,959 @@
+/*! @file SimpleGlob.h
+
+ @version 3.6
+
+ @brief A cross-platform file globbing library providing the ability to
+ expand wildcards in command-line arguments to a list of all matching
+ files. It is designed explicitly to be portable to any platform and has
+ been tested on Windows and Linux. See CSimpleGlobTempl for the class
+ definition.
+
+ @section features FEATURES
+ - MIT Licence allows free use in all software (including GPL and
+ commercial)
+ - multi-platform (Windows 95/98/ME/NT/2K/XP, Linux, Unix)
+ - supports most of the standard linux glob() options
+ - recognition of a forward paths as equivalent to a backward slash
+ on Windows. e.g. "c:/path/foo*" is equivalent to "c:\path\foo*".
+ - implemented with only a single C++ header file
+ - char, wchar_t and Windows TCHAR in the same program
+ - complete working examples included
+ - compiles cleanly at warning level 4 (Windows/VC.NET 2003),
+ warning level 3 (Windows/VC6) and -Wall (Linux/gcc)
+
+ @section usage USAGE
+ The SimpleGlob class is used by following these steps:
+
+ - Include the SimpleGlob.h header file
+
+
+ \#include "SimpleGlob.h"
+
+
+ - Instantiate a CSimpleGlob object supplying the appropriate flags.
+
+
+ @link CSimpleGlobTempl CSimpleGlob @endlink glob(FLAGS);
+
+
+ - Add all file specifications to the glob class.
+
+
+ glob.Add("file*");
+ glob.Add(argc, argv);
+
+
+ - Process all files with File(), Files() and FileCount()
+
+
+ for (int n = 0; n < glob.FileCount(); ++n) {
+ ProcessFile(glob.File(n));
+ }
+
+
+
+
+ @section licence MIT LICENCE
+
+ The licence text below is the boilerplate "MIT Licence" used from:
+ http://www.opensource.org/licenses/mit-license.php
+
+ Copyright (c) 2006-2013, Brodie Thiesfield
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+#ifndef INCLUDED_SimpleGlob
+#define INCLUDED_SimpleGlob
+
+/*! @brief The operation of SimpleGlob is fine-tuned via the use of a
+ combination of the following flags.
+
+ The flags may be passed at initialization of the class and used for every
+ filespec added, or alternatively they may optionally be specified in the
+ call to Add() and be different for each filespec.
+
+ @param SG_GLOB_ERR
+ Return upon read error (e.g. directory does not have read permission)
+
+ @param SG_GLOB_MARK
+ Append a slash (backslash in Windows) to every path which corresponds
+ to a directory
+
+ @param SG_GLOB_NOSORT
+ By default, files are returned in sorted into string order. With this
+ flag, no sorting is done. This is not compatible with
+ SG_GLOB_FULLSORT.
+
+ @param SG_GLOB_FULLSORT
+ By default, files are sorted in groups belonging to each filespec that
+ was added. For example if the filespec "b*" was added before the
+ filespec "a*" then the argv array will contain all b* files sorted in
+ order, followed by all a* files sorted in order. If this flag is
+ specified, the entire array will be sorted ignoring the filespec
+ groups.
+
+ @param SG_GLOB_NOCHECK
+ If the pattern doesn't match anything, return the original pattern.
+
+ @param SG_GLOB_TILDE
+ Tilde expansion is carried out (on Unix platforms)
+
+ @param SG_GLOB_ONLYDIR
+ Return only directories which match (not compatible with
+ SG_GLOB_ONLYFILE)
+
+ @param SG_GLOB_ONLYFILE
+ Return only files which match (not compatible with SG_GLOB_ONLYDIR)
+
+ @param SG_GLOB_NODOT
+ Do not return the "." or ".." special directories.
+ */
+enum SG_Flags {
+ SG_GLOB_ERR = 1 << 0,
+ SG_GLOB_MARK = 1 << 1,
+ SG_GLOB_NOSORT = 1 << 2,
+ SG_GLOB_NOCHECK = 1 << 3,
+ SG_GLOB_TILDE = 1 << 4,
+ SG_GLOB_ONLYDIR = 1 << 5,
+ SG_GLOB_ONLYFILE = 1 << 6,
+ SG_GLOB_NODOT = 1 << 7,
+ SG_GLOB_FULLSORT = 1 << 8
+};
+
+/*! @brief Error return codes */
+enum SG_Error {
+ SG_SUCCESS = 0,
+ SG_ERR_NOMATCH = 1,
+ SG_ERR_MEMORY = -1,
+ SG_ERR_FAILURE = -2
+};
+
+// ---------------------------------------------------------------------------
+// Platform dependent implementations
+
+// if we aren't on Windows and we have ICU available, then enable ICU
+// by default. Define this to 0 to intentially disable it.
+#ifndef SG_HAVE_ICU
+# if !defined(_WIN32) && defined(USTRING_H)
+# define SG_HAVE_ICU 1
+# else
+# define SG_HAVE_ICU 0
+# endif
+#endif
+
+// don't include this in documentation as it isn't relevant
+#ifndef DOXYGEN
+
+// on Windows we want to use MBCS aware string functions and mimic the
+// Unix glob functionality. On Unix we just use glob.
+#ifdef _WIN32
+# include
+# define sg_strchr ::_mbschr
+# define sg_strrchr ::_mbsrchr
+# define sg_strlen ::_mbslen
+# if __STDC_WANT_SECURE_LIB__
+# define sg_strcpy_s(a,n,b) ::_mbscpy_s(a,n,b)
+# else
+# define sg_strcpy_s(a,n,b) ::_mbscpy(a,b)
+# endif
+# define sg_strcmp ::_mbscmp
+# define sg_strcasecmp ::_mbsicmp
+# define SOCHAR_T unsigned char
+#else
+# include
+# include
+# include
+# include
+# define MAX_PATH PATH_MAX
+# define sg_strchr ::strchr
+# define sg_strrchr ::strrchr
+# define sg_strlen ::strlen
+# define sg_strcpy_s(a,n,b) ::strcpy(a,b)
+# define sg_strcmp ::strcmp
+# define sg_strcasecmp ::strcasecmp
+# define SOCHAR_T char
+#endif
+
+#include
+#include
+#include
+
+// use assertions to test the input data
+#ifdef _DEBUG
+# ifdef _MSC_VER
+# include
+# define SG_ASSERT(b) _ASSERTE(b)
+# else
+# include
+# define SG_ASSERT(b) assert(b)
+# endif
+#else
+# define SG_ASSERT(b)
+#endif
+
+/*! @brief String manipulation functions. */
+class SimpleGlobUtil
+{
+public:
+ static const char * strchr(const char *s, char c) {
+ return (char *) sg_strchr((const SOCHAR_T *)s, c);
+ }
+ static const wchar_t * strchr(const wchar_t *s, wchar_t c) {
+ return ::wcschr(s, c);
+ }
+#if SG_HAVE_ICU
+ static const UChar * strchr(const UChar *s, UChar c) {
+ return ::u_strchr(s, c);
+ }
+#endif
+
+ static const char * strrchr(const char *s, char c) {
+ return (char *) sg_strrchr((const SOCHAR_T *)s, c);
+ }
+ static const wchar_t * strrchr(const wchar_t *s, wchar_t c) {
+ return ::wcsrchr(s, c);
+ }
+#if SG_HAVE_ICU
+ static const UChar * strrchr(const UChar *s, UChar c) {
+ return ::u_strrchr(s, c);
+ }
+#endif
+
+ // Note: char strlen returns number of bytes, not characters
+ static size_t strlen(const char *s) { return ::strlen(s); }
+ static size_t strlen(const wchar_t *s) { return ::wcslen(s); }
+#if SG_HAVE_ICU
+ static size_t strlen(const UChar *s) { return ::u_strlen(s); }
+#endif
+
+ static void strcpy_s(char *dst, size_t n, const char *src) {
+ (void) n;
+ sg_strcpy_s((SOCHAR_T *)dst, n, (const SOCHAR_T *)src);
+ }
+ static void strcpy_s(wchar_t *dst, size_t n, const wchar_t *src) {
+# if __STDC_WANT_SECURE_LIB__
+ ::wcscpy_s(dst, n, src);
+#else
+ (void) n;
+ ::wcscpy(dst, src);
+#endif
+ }
+#if SG_HAVE_ICU
+ static void strcpy_s(UChar *dst, size_t n, const UChar *src) {
+ ::u_strncpy(dst, src, n);
+ }
+#endif
+
+ static int strcmp(const char *s1, const char *s2) {
+ return sg_strcmp((const SOCHAR_T *)s1, (const SOCHAR_T *)s2);
+ }
+ static int strcmp(const wchar_t *s1, const wchar_t *s2) {
+ return ::wcscmp(s1, s2);
+ }
+#if SG_HAVE_ICU
+ static int strcmp(const UChar *s1, const UChar *s2) {
+ return ::u_strcmp(s1, s2);
+ }
+#endif
+
+ static int strcasecmp(const char *s1, const char *s2) {
+ return sg_strcasecmp((const SOCHAR_T *)s1, (const SOCHAR_T *)s2);
+ }
+#if _WIN32
+ static int strcasecmp(const wchar_t *s1, const wchar_t *s2) {
+ return ::_wcsicmp(s1, s2);
+ }
+#endif // _WIN32
+#if SG_HAVE_ICU
+ static int strcasecmp(const UChar *s1, const UChar *s2) {
+ return u_strcasecmp(s1, s2, 0);
+ }
+#endif
+};
+
+enum SG_FileType {
+ SG_FILETYPE_INVALID,
+ SG_FILETYPE_FILE,
+ SG_FILETYPE_DIR
+};
+
+#ifdef _WIN32
+
+#ifndef INVALID_FILE_ATTRIBUTES
+# define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
+#endif
+
+#define SG_PATH_CHAR '\\'
+
+/*! @brief Windows glob implementation. */
+template
+struct SimpleGlobBase
+{
+ SimpleGlobBase() : m_hFind(INVALID_HANDLE_VALUE) { }
+
+ int FindFirstFileS(const char * a_pszFileSpec, unsigned int) {
+ m_hFind = FindFirstFileA(a_pszFileSpec, &m_oFindDataA);
+ if (m_hFind != INVALID_HANDLE_VALUE) {
+ return SG_SUCCESS;
+ }
+ DWORD dwErr = GetLastError();
+ if (dwErr == ERROR_FILE_NOT_FOUND) {
+ return SG_ERR_NOMATCH;
+ }
+ return SG_ERR_FAILURE;
+ }
+ int FindFirstFileS(const wchar_t * a_pszFileSpec, unsigned int) {
+ m_hFind = FindFirstFileW(a_pszFileSpec, &m_oFindDataW);
+ if (m_hFind != INVALID_HANDLE_VALUE) {
+ return SG_SUCCESS;
+ }
+ DWORD dwErr = GetLastError();
+ if (dwErr == ERROR_FILE_NOT_FOUND) {
+ return SG_ERR_NOMATCH;
+ }
+ return SG_ERR_FAILURE;
+ }
+
+ bool FindNextFileS(char) {
+ return FindNextFileA(m_hFind, &m_oFindDataA) != FALSE;
+ }
+ bool FindNextFileS(wchar_t) {
+ return FindNextFileW(m_hFind, &m_oFindDataW) != FALSE;
+ }
+
+ void FindDone() {
+ FindClose(m_hFind);
+ }
+
+ const char * GetFileNameS(char) const {
+ return m_oFindDataA.cFileName;
+ }
+ const wchar_t * GetFileNameS(wchar_t) const {
+ return m_oFindDataW.cFileName;
+ }
+
+ bool IsDirS(char) const {
+ return this->GetFileTypeS(m_oFindDataA.dwFileAttributes) == SG_FILETYPE_DIR;
+ }
+ bool IsDirS(wchar_t) const {
+ return this->GetFileTypeS(m_oFindDataW.dwFileAttributes) == SG_FILETYPE_DIR;
+ }
+
+ SG_FileType GetFileTypeS(const char * a_pszPath) {
+ return this->GetFileTypeS(GetFileAttributesA(a_pszPath));
+ }
+ SG_FileType GetFileTypeS(const wchar_t * a_pszPath) {
+ return this->GetFileTypeS(GetFileAttributesW(a_pszPath));
+ }
+ SG_FileType GetFileTypeS(DWORD a_dwAttribs) const {
+ if (a_dwAttribs == INVALID_FILE_ATTRIBUTES) {
+ return SG_FILETYPE_INVALID;
+ }
+ if (a_dwAttribs & FILE_ATTRIBUTE_DIRECTORY) {
+ return SG_FILETYPE_DIR;
+ }
+ return SG_FILETYPE_FILE;
+ }
+
+private:
+ HANDLE m_hFind;
+ WIN32_FIND_DATAA m_oFindDataA;
+ WIN32_FIND_DATAW m_oFindDataW;
+};
+
+#else // !_WIN32
+
+#define SG_PATH_CHAR '/'
+
+/*! @brief Unix glob implementation. */
+template
+struct SimpleGlobBase
+{
+ SimpleGlobBase() {
+ memset(&m_glob, 0, sizeof(m_glob));
+ m_uiCurr = (size_t)-1;
+ }
+
+ ~SimpleGlobBase() {
+ globfree(&m_glob);
+ }
+
+ void FilePrep() {
+ m_bIsDir = false;
+ size_t len = strlen(m_glob.gl_pathv[m_uiCurr]);
+ if (m_glob.gl_pathv[m_uiCurr][len-1] == '/') {
+ m_bIsDir = true;
+ m_glob.gl_pathv[m_uiCurr][len-1] = 0;
+ }
+ }
+
+ int FindFirstFileS(const char * a_pszFileSpec, unsigned int a_uiFlags) {
+ int nFlags = GLOB_MARK | GLOB_NOSORT;
+ if (a_uiFlags & SG_GLOB_ERR) nFlags |= GLOB_ERR;
+ if (a_uiFlags & SG_GLOB_TILDE) nFlags |= GLOB_TILDE;
+ int rc = glob(a_pszFileSpec, nFlags, NULL, &m_glob);
+ if (rc == GLOB_NOSPACE) return SG_ERR_MEMORY;
+ if (rc == GLOB_ABORTED) return SG_ERR_FAILURE;
+ if (rc == GLOB_NOMATCH) return SG_ERR_NOMATCH;
+ m_uiCurr = 0;
+ FilePrep();
+ return SG_SUCCESS;
+ }
+
+#if SG_HAVE_ICU
+ int FindFirstFileS(const UChar * a_pszFileSpec, unsigned int a_uiFlags) {
+ char buf[PATH_MAX] = { 0 };
+ UErrorCode status = U_ZERO_ERROR;
+ u_strToUTF8(buf, sizeof(buf), NULL, a_pszFileSpec, -1, &status);
+ if (U_FAILURE(status)) return SG_ERR_FAILURE;
+ return this->FindFirstFileS(buf, a_uiFlags);
+ }
+#endif
+
+ bool FindNextFileS(char) {
+ SG_ASSERT(m_uiCurr != (size_t)-1);
+ if (++m_uiCurr >= m_glob.gl_pathc) {
+ return false;
+ }
+ FilePrep();
+ return true;
+ }
+
+#if SG_HAVE_ICU
+ bool FindNextFileS(UChar) {
+ return this->FindNextFileS((char)0);
+ }
+#endif
+
+ void FindDone() {
+ globfree(&m_glob);
+ memset(&m_glob, 0, sizeof(m_glob));
+ m_uiCurr = (size_t)-1;
+ }
+
+ const char * GetFileNameS(char) const {
+ SG_ASSERT(m_uiCurr != (size_t)-1);
+ return m_glob.gl_pathv[m_uiCurr];
+ }
+
+#if SG_HAVE_ICU
+ const UChar * GetFileNameS(UChar) const {
+ const char * pszFile = this->GetFileNameS((char)0);
+ if (!pszFile) return NULL;
+ UErrorCode status = U_ZERO_ERROR;
+ memset(m_szBuf, 0, sizeof(m_szBuf));
+ u_strFromUTF8(m_szBuf, PATH_MAX, NULL, pszFile, -1, &status);
+ if (U_FAILURE(status)) return NULL;
+ return m_szBuf;
+ }
+#endif
+
+ bool IsDirS(char) const {
+ SG_ASSERT(m_uiCurr != (size_t)-1);
+ return m_bIsDir;
+ }
+
+#if SG_HAVE_ICU
+ bool IsDirS(UChar) const {
+ return this->IsDirS((char)0);
+ }
+#endif
+
+ SG_FileType GetFileTypeS(const char * a_pszPath) const {
+ struct stat sb;
+ if (0 != stat(a_pszPath, &sb)) {
+ return SG_FILETYPE_INVALID;
+ }
+ if (S_ISDIR(sb.st_mode)) {
+ return SG_FILETYPE_DIR;
+ }
+ if (S_ISREG(sb.st_mode)) {
+ return SG_FILETYPE_FILE;
+ }
+ return SG_FILETYPE_INVALID;
+ }
+
+#if SG_HAVE_ICU
+ SG_FileType GetFileTypeS(const UChar * a_pszPath) const {
+ char buf[PATH_MAX] = { 0 };
+ UErrorCode status = U_ZERO_ERROR;
+ u_strToUTF8(buf, sizeof(buf), NULL, a_pszPath, -1, &status);
+ if (U_FAILURE(status)) return SG_FILETYPE_INVALID;
+ return this->GetFileTypeS(buf);
+ }
+#endif
+
+private:
+ glob_t m_glob;
+ size_t m_uiCurr;
+ bool m_bIsDir;
+#if SG_HAVE_ICU
+ mutable UChar m_szBuf[PATH_MAX];
+#endif
+};
+
+#endif // _WIN32
+
+#endif // DOXYGEN
+
+// ---------------------------------------------------------------------------
+// MAIN TEMPLATE CLASS
+// ---------------------------------------------------------------------------
+
+/*! @brief Implementation of the SimpleGlob class */
+template
+class CSimpleGlobTempl : private SimpleGlobBase
+{
+public:
+ /*! @brief Initialize the class.
+
+ @param a_uiFlags Combination of SG_GLOB flags.
+ @param a_nReservedSlots Number of slots in the argv array that
+ should be reserved. In the returned array these slots
+ argv[0] ... argv[a_nReservedSlots-1] will be left empty for
+ the caller to fill in.
+ */
+ CSimpleGlobTempl(unsigned int a_uiFlags = 0, int a_nReservedSlots = 0);
+
+ /*! @brief Deallocate all memory buffers. */
+ ~CSimpleGlobTempl();
+
+ /*! @brief Initialize (or re-initialize) the class in preparation for
+ adding new filespecs.
+
+ All existing files are cleared. Note that allocated memory is only
+ deallocated at object destruction.
+
+ @param a_uiFlags Combination of SG_GLOB flags.
+ @param a_nReservedSlots Number of slots in the argv array that
+ should be reserved. In the returned array these slots
+ argv[0] ... argv[a_nReservedSlots-1] will be left empty for
+ the caller to fill in.
+ */
+ int Init(unsigned int a_uiFlags = 0, int a_nReservedSlots = 0);
+
+ /*! @brief Add a new filespec to the glob.
+
+ The filesystem will be immediately scanned for all matching files and
+ directories and they will be added to the glob.
+
+ @param a_pszFileSpec Filespec to add to the glob.
+
+ @return SG_SUCCESS Matching files were added to the glob.
+ @return SG_ERR_NOMATCH Nothing matched the pattern. To ignore this
+ error compare return value to >= SG_SUCCESS.
+ @return SG_ERR_MEMORY Out of memory failure.
+ @return SG_ERR_FAILURE General failure.
+ */
+ int Add(const SOCHAR *a_pszFileSpec);
+
+ /*! @brief Add an array of filespec to the glob.
+
+ The filesystem will be immediately scanned for all matching files and
+ directories in each filespec and they will be added to the glob.
+
+ @param a_nCount Number of filespec in the array.
+ @param a_rgpszFileSpec Array of filespec to add to the glob.
+
+ @return SG_SUCCESS Matching files were added to the glob.
+ @return SG_ERR_NOMATCH Nothing matched the pattern. To ignore this
+ error compare return value to >= SG_SUCCESS.
+ @return SG_ERR_MEMORY Out of memory failure.
+ @return SG_ERR_FAILURE General failure.
+ */
+ int Add(int a_nCount, const SOCHAR * const * a_rgpszFileSpec);
+
+ /*! @brief Return the number of files in the argv array.
+ */
+ inline int FileCount() const { return m_nArgsLen; }
+
+ /*! @brief Return the full argv array. */
+ inline SOCHAR ** Files() {
+ SetArgvArrayType(POINTERS);
+ return m_rgpArgs;
+ }
+
+ /*! @brief Return the a single file. */
+ inline SOCHAR * File(int n) {
+ SG_ASSERT(n >= 0 && n < m_nArgsLen);
+ return Files()[n];
+ }
+
+private:
+ CSimpleGlobTempl(const CSimpleGlobTempl &); // disabled
+ CSimpleGlobTempl & operator=(const CSimpleGlobTempl &); // disabled
+
+ /*! @brief The argv array has it's members stored as either an offset into
+ the string buffer, or as pointers to their string in the buffer. The
+ offsets are used because if the string buffer is dynamically resized,
+ all pointers into that buffer would become invalid.
+ */
+ enum ARG_ARRAY_TYPE { OFFSETS, POINTERS };
+
+ /*! @brief Change the type of data stored in the argv array. */
+ void SetArgvArrayType(ARG_ARRAY_TYPE a_nNewType);
+
+ /*! @brief Add a filename to the array if it passes all requirements. */
+ int AppendName(const SOCHAR *a_pszFileName, bool a_bIsDir);
+
+ /*! @brief Grow the argv array to the required size. */
+ bool GrowArgvArray(int a_nNewLen);
+
+ /*! @brief Grow the string buffer to the required size. */
+ bool GrowStringBuffer(size_t a_uiMinSize);
+
+ /*! @brief Compare two (possible NULL) strings */
+ static int fileSortCompare(const void *a1, const void *a2);
+
+private:
+ unsigned int m_uiFlags;
+ ARG_ARRAY_TYPE m_nArgArrayType; //!< argv is indexes or pointers
+ SOCHAR ** m_rgpArgs; //!< argv
+ int m_nReservedSlots; //!< # client slots in argv array
+ int m_nArgsSize; //!< allocated size of array
+ int m_nArgsLen; //!< used length
+ SOCHAR * m_pBuffer; //!< argv string buffer
+ size_t m_uiBufferSize; //!< allocated size of buffer
+ size_t m_uiBufferLen; //!< used length of buffer
+ SOCHAR m_szPathPrefix[MAX_PATH]; //!< wildcard path prefix
+};
+
+// ---------------------------------------------------------------------------
+// IMPLEMENTATION
+// ---------------------------------------------------------------------------
+
+template
+CSimpleGlobTempl::CSimpleGlobTempl(
+ unsigned int a_uiFlags,
+ int a_nReservedSlots
+ )
+{
+ m_rgpArgs = NULL;
+ m_nArgsSize = 0;
+ m_pBuffer = NULL;
+ m_uiBufferSize = 0;
+
+ Init(a_uiFlags, a_nReservedSlots);
+}
+
+template
+CSimpleGlobTempl::~CSimpleGlobTempl()
+{
+ if (m_rgpArgs) free(m_rgpArgs);
+ if (m_pBuffer) free(m_pBuffer);
+}
+
+template
+int
+CSimpleGlobTempl::Init(
+ unsigned int a_uiFlags,
+ int a_nReservedSlots
+ )
+{
+ m_nArgArrayType = POINTERS;
+ m_uiFlags = a_uiFlags;
+ m_nArgsLen = a_nReservedSlots;
+ m_nReservedSlots = a_nReservedSlots;
+ m_uiBufferLen = 0;
+
+ if (m_nReservedSlots > 0) {
+ if (!GrowArgvArray(m_nReservedSlots)) {
+ return SG_ERR_MEMORY;
+ }
+ for (int n = 0; n < m_nReservedSlots; ++n) {
+ m_rgpArgs[n] = NULL;
+ }
+ }
+
+ return SG_SUCCESS;
+}
+
+template
+int
+CSimpleGlobTempl::Add(
+ const SOCHAR *a_pszFileSpec
+ )
+{
+#ifdef _WIN32
+ // Windows FindFirst/FindNext recognizes forward slash as the same as
+ // backward slash and follows the directories. We need to do the same
+ // when calculating the prefix and when we have no wildcards.
+ SOCHAR szFileSpec[MAX_PATH];
+ SimpleGlobUtil::strcpy_s(szFileSpec, MAX_PATH, a_pszFileSpec);
+ const SOCHAR * pszPath = SimpleGlobUtil::strchr(szFileSpec, '/');
+ while (pszPath) {
+ szFileSpec[pszPath - szFileSpec] = SG_PATH_CHAR;
+ pszPath = SimpleGlobUtil::strchr(pszPath + 1, '/');
+ }
+ a_pszFileSpec = szFileSpec;
+#endif
+
+ // if this doesn't contain wildcards then we can just add it directly
+ m_szPathPrefix[0] = 0;
+ if (!SimpleGlobUtil::strchr(a_pszFileSpec, '*') &&
+ !SimpleGlobUtil::strchr(a_pszFileSpec, '?'))
+ {
+ SG_FileType nType = this->GetFileTypeS(a_pszFileSpec);
+ if (nType == SG_FILETYPE_INVALID) {
+ if (m_uiFlags & SG_GLOB_NOCHECK) {
+ return AppendName(a_pszFileSpec, false);
+ }
+ return SG_ERR_NOMATCH;
+ }
+ return AppendName(a_pszFileSpec, nType == SG_FILETYPE_DIR);
+ }
+
+#ifdef _WIN32
+ // Windows doesn't return the directory with the filename, so we need to
+ // extract the path from the search string ourselves and prefix it to the
+ // filename we get back.
+ const SOCHAR * pszFilename =
+ SimpleGlobUtil::strrchr(a_pszFileSpec, SG_PATH_CHAR);
+ if (pszFilename) {
+ SimpleGlobUtil::strcpy_s(m_szPathPrefix, MAX_PATH, a_pszFileSpec);
+ m_szPathPrefix[pszFilename - a_pszFileSpec + 1] = 0;
+ }
+#endif
+
+ // search for the first match on the file
+ int rc = this->FindFirstFileS(a_pszFileSpec, m_uiFlags);
+ if (rc != SG_SUCCESS) {
+ if (rc == SG_ERR_NOMATCH && (m_uiFlags & SG_GLOB_NOCHECK)) {
+ int ok = AppendName(a_pszFileSpec, false);
+ if (ok != SG_SUCCESS) rc = ok;
+ }
+ return rc;
+ }
+
+ // add it and find all subsequent matches
+ int nError, nStartLen = m_nArgsLen;
+ bool bSuccess;
+ do {
+ nError = AppendName(this->GetFileNameS((SOCHAR)0), this->IsDirS((SOCHAR)0));
+ bSuccess = this->FindNextFileS((SOCHAR)0);
+ }
+ while (nError == SG_SUCCESS && bSuccess);
+ SimpleGlobBase::FindDone();
+
+ // sort these files if required
+ if (m_nArgsLen > nStartLen && !(m_uiFlags & SG_GLOB_NOSORT)) {
+ if (m_uiFlags & SG_GLOB_FULLSORT) {
+ nStartLen = m_nReservedSlots;
+ }
+ SetArgvArrayType(POINTERS);
+ qsort(
+ m_rgpArgs + nStartLen,
+ m_nArgsLen - nStartLen,
+ sizeof(m_rgpArgs[0]), fileSortCompare);
+ }
+
+ return nError;
+}
+
+template
+int
+CSimpleGlobTempl::Add(
+ int a_nCount,
+ const SOCHAR * const * a_rgpszFileSpec
+ )
+{
+ int nResult;
+ for (int n = 0; n < a_nCount; ++n) {
+ nResult = Add(a_rgpszFileSpec[n]);
+ if (nResult != SG_SUCCESS) {
+ return nResult;
+ }
+ }
+ return SG_SUCCESS;
+}
+
+template
+int
+CSimpleGlobTempl::AppendName(
+ const SOCHAR * a_pszFileName,
+ bool a_bIsDir
+ )
+{
+ // we need the argv array as offsets in case we resize it
+ SetArgvArrayType(OFFSETS);
+
+ // check for special cases which cause us to ignore this entry
+ if ((m_uiFlags & SG_GLOB_ONLYDIR) && !a_bIsDir) {
+ return SG_SUCCESS;
+ }
+ if ((m_uiFlags & SG_GLOB_ONLYFILE) && a_bIsDir) {
+ return SG_SUCCESS;
+ }
+ if ((m_uiFlags & SG_GLOB_NODOT) && a_bIsDir) {
+ if (a_pszFileName[0] == '.') {
+ if (a_pszFileName[1] == '\0') {
+ return SG_SUCCESS;
+ }
+ if (a_pszFileName[1] == '.' && a_pszFileName[2] == '\0') {
+ return SG_SUCCESS;
+ }
+ }
+ }
+
+ // ensure that we have enough room in the argv array
+ if (!GrowArgvArray(m_nArgsLen + 1)) {
+ return SG_ERR_MEMORY;
+ }
+
+ // ensure that we have enough room in the string buffer (+1 for null)
+ size_t uiPrefixLen = SimpleGlobUtil::strlen(m_szPathPrefix);
+ size_t uiLen = uiPrefixLen + SimpleGlobUtil::strlen(a_pszFileName) + 1;
+ if (a_bIsDir && (m_uiFlags & SG_GLOB_MARK) == SG_GLOB_MARK) {
+ ++uiLen; // need space for the backslash
+ }
+ if (!GrowStringBuffer(m_uiBufferLen + uiLen)) {
+ return SG_ERR_MEMORY;
+ }
+
+ // add this entry. m_uiBufferLen is offset from beginning of buffer.
+ m_rgpArgs[m_nArgsLen++] = (SOCHAR*)m_uiBufferLen;
+ SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen,
+ m_uiBufferSize - m_uiBufferLen, m_szPathPrefix);
+ SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen + uiPrefixLen,
+ m_uiBufferSize - m_uiBufferLen - uiPrefixLen, a_pszFileName);
+ m_uiBufferLen += uiLen;
+
+ // add the directory slash if desired
+ if (a_bIsDir && (m_uiFlags & SG_GLOB_MARK) == SG_GLOB_MARK) {
+ const static SOCHAR szDirSlash[] = { SG_PATH_CHAR, 0 };
+ SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen - 2,
+ m_uiBufferSize - (m_uiBufferLen - 2), szDirSlash);
+ }
+
+ return SG_SUCCESS;
+}
+
+template
+void
+CSimpleGlobTempl::SetArgvArrayType(
+ ARG_ARRAY_TYPE a_nNewType
+ )
+{
+ if (m_nArgArrayType == a_nNewType) return;
+ if (a_nNewType == POINTERS) {
+ SG_ASSERT(m_nArgArrayType == OFFSETS);
+ for (int n = 0; n < m_nArgsLen; ++n) {
+ m_rgpArgs[n] = (m_rgpArgs[n] == (SOCHAR*)-1) ?
+ NULL : m_pBuffer + (size_t) m_rgpArgs[n];
+ }
+ }
+ else {
+ SG_ASSERT(a_nNewType == OFFSETS);
+ SG_ASSERT(m_nArgArrayType == POINTERS);
+ for (int n = 0; n < m_nArgsLen; ++n) {
+ m_rgpArgs[n] = (m_rgpArgs[n] == NULL) ?
+ (SOCHAR*) -1 : (SOCHAR*) (m_rgpArgs[n] - m_pBuffer);
+ }
+ }
+ m_nArgArrayType = a_nNewType;
+}
+
+template
+bool
+CSimpleGlobTempl::GrowArgvArray(
+ int a_nNewLen
+ )
+{
+ if (a_nNewLen >= m_nArgsSize) {
+ static const int SG_ARGV_INITIAL_SIZE = 32;
+ int nNewSize = (m_nArgsSize > 0) ?
+ m_nArgsSize * 2 : SG_ARGV_INITIAL_SIZE;
+ while (a_nNewLen >= nNewSize) {
+ nNewSize *= 2;
+ }
+ void * pNewBuffer = realloc(m_rgpArgs, nNewSize * sizeof(SOCHAR*));
+ if (!pNewBuffer) return false;
+ m_nArgsSize = nNewSize;
+ m_rgpArgs = (SOCHAR**) pNewBuffer;
+ }
+ return true;
+}
+
+template
+bool
+CSimpleGlobTempl::GrowStringBuffer(
+ size_t a_uiMinSize
+ )
+{
+ if (a_uiMinSize >= m_uiBufferSize) {
+ static const int SG_BUFFER_INITIAL_SIZE = 1024;
+ size_t uiNewSize = (m_uiBufferSize > 0) ?
+ m_uiBufferSize * 2 : SG_BUFFER_INITIAL_SIZE;
+ while (a_uiMinSize >= uiNewSize) {
+ uiNewSize *= 2;
+ }
+ void * pNewBuffer = realloc(m_pBuffer, uiNewSize * sizeof(SOCHAR));
+ if (!pNewBuffer) return false;
+ m_uiBufferSize = uiNewSize;
+ m_pBuffer = (SOCHAR*) pNewBuffer;
+ }
+ return true;
+}
+
+template
+int
+CSimpleGlobTempl::fileSortCompare(
+ const void *a1,
+ const void *a2
+ )
+{
+ const SOCHAR * s1 = *(const SOCHAR **)a1;
+ const SOCHAR * s2 = *(const SOCHAR **)a2;
+ if (s1 && s2) {
+ return SimpleGlobUtil::strcasecmp(s1, s2);
+ }
+ // NULL sorts first
+ return s1 == s2 ? 0 : (s1 ? 1 : -1);
+}
+
+// ---------------------------------------------------------------------------
+// TYPE DEFINITIONS
+// ---------------------------------------------------------------------------
+
+/*! @brief ASCII/MBCS version of CSimpleGlob */
+typedef CSimpleGlobTempl CSimpleGlobA;
+
+/*! @brief wchar_t version of CSimpleGlob */
+typedef CSimpleGlobTempl CSimpleGlobW;
+
+#if SG_HAVE_ICU
+/*! @brief UChar version of CSimpleGlob */
+typedef CSimpleGlobTempl CSimpleGlobU;
+#endif
+
+#ifdef _UNICODE
+/*! @brief TCHAR version dependent on if _UNICODE is defined */
+# if SG_HAVE_ICU
+# define CSimpleGlob CSimpleGlobU
+# else
+# define CSimpleGlob CSimpleGlobW
+# endif
+#else
+/*! @brief TCHAR version dependent on if _UNICODE is defined */
+# define CSimpleGlob CSimpleGlobA
+#endif
+
+#endif // INCLUDED_SimpleGlob
diff --git a/external/header_only/SimpleOpt.h b/external/header_only/SimpleOpt.h
new file mode 100644
index 0000000000..ed27459fdc
--- /dev/null
+++ b/external/header_only/SimpleOpt.h
@@ -0,0 +1,1046 @@
+/*! @file SimpleOpt.h
+
+ @version 3.6
+
+ @brief A cross-platform command line library which can parse almost any
+ of the standard command line formats in use today. It is designed
+ explicitly to be portable to any platform and has been tested on Windows
+ and Linux. See CSimpleOptTempl for the class definition.
+
+ @section features FEATURES
+ - MIT Licence allows free use in all software (including GPL
+ and commercial)
+ - multi-platform (Windows 95/98/ME/NT/2K/XP, Linux, Unix)
+ - supports all lengths of option names:
+
+ -
+ | switch character only (e.g. use stdin for input)
+ |
-o
+ | short (single character)
+ |
-long
+ | long (multiple character, single switch character)
+ |
--longer
+ | long (multiple character, multiple switch characters)
+ |
+ - supports all types of arguments for options:
+
+ --option
+ | short/long option flag (no argument)
+ |
--option ARG
+ | short/long option with separate required argument
+ |
--option=ARG
+ | short/long option with combined required argument
+ |
--option[=ARG]
+ | short/long option with combined optional argument
+ |
-oARG
+ | short option with combined required argument
+ |
-o[ARG]
+ | short option with combined optional argument
+ |
+ - supports options with multiple or variable numbers of arguments:
+
+ --multi ARG1 ARG2
+ | Multiple arguments
+ |
--multi N ARG-1 ARG-2 ... ARG-N
+ | Variable number of arguments
+ |
+ - supports case-insensitive option matching on short, long and/or
+ word arguments.
+ - supports options which do not use a switch character. i.e. a special
+ word which is construed as an option.
+ e.g. "foo.exe open /directory/file.txt"
+ - supports clumping of multiple short options (no arguments) in a string
+ e.g. "foo.exe -abcdef file1" <==> "foo.exe -a -b -c -d -e -f file1"
+ - automatic recognition of a single slash as equivalent to a single
+ hyphen on Windows, e.g. "/f FILE" is equivalent to "-f FILE".
+ - file arguments can appear anywhere in the argument list:
+ "foo.exe file1.txt -a ARG file2.txt --flag file3.txt file4.txt"
+ files will be returned to the application in the same order they were
+ supplied on the command line
+ - short-circuit option matching: "--man" will match "--mandate"
+ invalid options can be handled while continuing to parse the command
+ line valid options list can be changed dynamically during command line
+ processing, i.e. accept different options depending on an option
+ supplied earlier in the command line.
+ - implemented with only a single C++ header file
+ - optionally use no C runtime or OS functions
+ - char, wchar_t and Windows TCHAR in the same program
+ - complete working examples included
+ - compiles cleanly at warning level 4 (Windows/VC.NET 2003), warning
+ level 3 (Windows/VC6) and -Wall (Linux/gcc)
+
+ @section usage USAGE
+ The SimpleOpt class is used by following these steps:
+
+ - Include the SimpleOpt.h header file
+
+ \#include "SimpleOpt.h"
+
+ - Define an array of valid options for your program.
+
+@link CSimpleOptTempl::SOption CSimpleOpt::SOption @endlink g_rgOptions[] = {
+ { OPT_FLAG, _T("-a"), SO_NONE }, // "-a"
+ { OPT_FLAG, _T("-b"), SO_NONE }, // "-b"
+ { OPT_ARG, _T("-f"), SO_REQ_SEP }, // "-f ARG"
+ { OPT_HELP, _T("-?"), SO_NONE }, // "-?"
+ { OPT_HELP, _T("--help"), SO_NONE }, // "--help"
+ SO_END_OF_OPTIONS // END
+};
+
+ Note that all options must start with a hyphen even if the slash will
+ be accepted. This is because the slash character is automatically
+ converted into a hyphen to test against the list of options.
+ For example, the following line matches both "-?" and "/?"
+ (on Windows).
+
+ { OPT_HELP, _T("-?"), SO_NONE }, // "-?"
+
+ - Instantiate a CSimpleOpt object supplying argc, argv and the option
+ table
+
+@link CSimpleOptTempl CSimpleOpt @endlink args(argc, argv, g_rgOptions);
+
+ - Process the arguments by calling Next() until it returns false.
+ On each call, first check for an error by calling LastError(), then
+ either handle the error or process the argument.
+
+while (args.Next()) {
+ if (args.LastError() == SO_SUCCESS) {
+ handle option: use OptionId(), OptionText() and OptionArg()
+ }
+ else {
+ handle error: see ESOError enums
+ }
+}
+
+ - Process all non-option arguments with File(), Files() and FileCount()
+
+ShowFiles(args.FileCount(), args.Files());
+
+
+
+ @section notes NOTES
+ - In MBCS mode, this library is guaranteed to work correctly only when
+ all option names use only ASCII characters.
+ - Note that if case-insensitive matching is being used then the first
+ matching option in the argument list will be returned.
+
+ @section licence MIT LICENCE
+
+ The licence text below is the boilerplate "MIT Licence" used from:
+ http://www.opensource.org/licenses/mit-license.php
+
+ Copyright (c) 2006-2013, Brodie Thiesfield
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+/*! @mainpage
+
+
+ Library | SimpleOpt
+ |
---|
Author | Brodie Thiesfield [code at jellycan dot com]
+ |
---|
Source | http://code.jellycan.com/simpleopt/
+ |
---|
+
+ @section SimpleOpt SimpleOpt
+
+ A cross-platform library providing a simple method to parse almost any of
+ the standard command-line formats in use today.
+
+ See the @link SimpleOpt.h SimpleOpt @endlink documentation for full
+ details.
+
+ @section SimpleGlob SimpleGlob
+
+ A cross-platform file globbing library providing the ability to
+ expand wildcards in command-line arguments to a list of all matching
+ files.
+
+ See the @link SimpleGlob.h SimpleGlob @endlink documentation for full
+ details.
+*/
+
+#ifndef INCLUDED_SimpleOpt
+#define INCLUDED_SimpleOpt
+
+// Default the max arguments to a fixed value. If you want to be able to
+// handle any number of arguments, then predefine this to 0 and it will
+// use an internal dynamically allocated buffer instead.
+#ifdef SO_MAX_ARGS
+# define SO_STATICBUF SO_MAX_ARGS
+#else
+# include // malloc, free
+# include // memcpy
+# define SO_STATICBUF 50
+#endif
+
+//! Error values
+typedef enum _ESOError
+{
+ //! No error
+ SO_SUCCESS = 0,
+
+ /*! It looks like an option (it starts with a switch character), but
+ it isn't registered in the option table. */
+ SO_OPT_INVALID = -1,
+
+ /*! Multiple options matched the supplied option text.
+ Only returned when NOT using SO_O_EXACT. */
+ SO_OPT_MULTIPLE = -2,
+
+ /*! Option doesn't take an argument, but a combined argument was
+ supplied. */
+ SO_ARG_INVALID = -3,
+
+ /*! SO_REQ_CMB style-argument was supplied to a SO_REQ_SEP option
+ Only returned when using SO_O_PEDANTIC. */
+ SO_ARG_INVALID_TYPE = -4,
+
+ //! Required argument was not supplied
+ SO_ARG_MISSING = -5,
+
+ /*! Option argument looks like another option.
+ Only returned when NOT using SO_O_NOERR. */
+ SO_ARG_INVALID_DATA = -6
+} ESOError;
+
+//! Option flags
+enum _ESOFlags
+{
+ /*! Disallow partial matching of option names */
+ SO_O_EXACT = 0x0001,
+
+ /*! Disallow use of slash as an option marker on Windows.
+ Un*x only ever recognizes a hyphen. */
+ SO_O_NOSLASH = 0x0002,
+
+ /*! Permit arguments on single letter options with no equals sign.
+ e.g. -oARG or -o[ARG] */
+ SO_O_SHORTARG = 0x0004,
+
+ /*! Permit single character options to be clumped into a single
+ option string. e.g. "-a -b -c" <==> "-abc" */
+ SO_O_CLUMP = 0x0008,
+
+ /*! Process the entire argv array for options, including the
+ argv[0] entry. */
+ SO_O_USEALL = 0x0010,
+
+ /*! Do not generate an error for invalid options. errors for missing
+ arguments will still be generated. invalid options will be
+ treated as files. invalid options in clumps will be silently
+ ignored. */
+ SO_O_NOERR = 0x0020,
+
+ /*! Validate argument type pedantically. Return an error when a
+ separated argument "-opt arg" is supplied by the user as a
+ combined argument "-opt=arg". By default this is not considered
+ an error. */
+ SO_O_PEDANTIC = 0x0040,
+
+ /*! Case-insensitive comparisons for short arguments */
+ SO_O_ICASE_SHORT = 0x0100,
+
+ /*! Case-insensitive comparisons for long arguments */
+ SO_O_ICASE_LONG = 0x0200,
+
+ /*! Case-insensitive comparisons for word arguments
+ i.e. arguments without any hyphens at the start. */
+ SO_O_ICASE_WORD = 0x0400,
+
+ /*! Case-insensitive comparisons for all arg types */
+ SO_O_ICASE = 0x0700
+};
+
+/*! Types of arguments that options may have. Note that some of the _ESOFlags
+ are not compatible with all argument types. SO_O_SHORTARG requires that
+ relevant options use either SO_REQ_CMB or SO_OPT. SO_O_CLUMP requires
+ that relevant options use only SO_NONE.
+ */
+typedef enum _ESOArgType {
+ /*! No argument. Just the option flags.
+ e.g. -o --opt */
+ SO_NONE,
+
+ /*! Required separate argument.
+ e.g. -o ARG --opt ARG */
+ SO_REQ_SEP,
+
+ /*! Required combined argument.
+ e.g. -oARG -o=ARG --opt=ARG */
+ SO_REQ_CMB,
+
+ /*! Optional combined argument.
+ e.g. -o[ARG] -o[=ARG] --opt[=ARG] */
+ SO_OPT,
+
+ /*! Multiple separate arguments. The actual number of arguments is
+ determined programatically at the time the argument is processed.
+ e.g. -o N ARG1 ARG2 ... ARGN --opt N ARG1 ARG2 ... ARGN */
+ SO_MULTI
+} ESOArgType;
+
+//! this option definition must be the last entry in the table
+#define SO_END_OF_OPTIONS { -1, NULL, SO_NONE }
+
+#ifdef _DEBUG
+# ifdef _MSC_VER
+# include
+# define SO_ASSERT(b) _ASSERTE(b)
+# else
+# include
+# define SO_ASSERT(b) assert(b)
+# endif
+#else
+# define SO_ASSERT(b) //!< assertion used to test input data
+#endif
+
+// ---------------------------------------------------------------------------
+// MAIN TEMPLATE CLASS
+// ---------------------------------------------------------------------------
+
+/*! @brief Implementation of the SimpleOpt class */
+template
+class CSimpleOptTempl
+{
+public:
+ /*! @brief Structure used to define all known options. */
+ struct SOption {
+ /*! ID to return for this flag. Optional but must be >= 0 */
+ int nId;
+
+ /*! arg string to search for, e.g. "open", "-", "-f", "--file"
+ Note that on Windows the slash option marker will be converted
+ to a hyphen so that "-f" will also match "/f". */
+ const SOCHAR * pszArg;
+
+ /*! type of argument accepted by this option */
+ ESOArgType nArgType;
+ };
+
+ /*! @brief Initialize the class. Init() must be called later. */
+ CSimpleOptTempl()
+ : m_rgShuffleBuf(NULL)
+ {
+ Init(0, NULL, NULL, 0);
+ }
+
+ /*! @brief Initialize the class in preparation for use. */
+ CSimpleOptTempl(
+ int argc,
+ SOCHAR * argv[],
+ const SOption * a_rgOptions,
+ int a_nFlags = 0
+ )
+ : m_rgShuffleBuf(NULL)
+ {
+ Init(argc, argv, a_rgOptions, a_nFlags);
+ }
+
+#ifndef SO_MAX_ARGS
+ /*! @brief Deallocate any allocated memory. */
+ ~CSimpleOptTempl() { if (m_rgShuffleBuf) free(m_rgShuffleBuf); }
+#endif
+
+ /*! @brief Initialize the class in preparation for calling Next.
+
+ The table of options pointed to by a_rgOptions does not need to be
+ valid at the time that Init() is called. However on every call to
+ Next() the table pointed to must be a valid options table with the
+ last valid entry set to SO_END_OF_OPTIONS.
+
+ NOTE: the array pointed to by a_argv will be modified by this
+ class and must not be used or modified outside of member calls to
+ this class.
+
+ @param a_argc Argument array size
+ @param a_argv Argument array
+ @param a_rgOptions Valid option array
+ @param a_nFlags Optional flags to modify the processing of
+ the arguments
+
+ @return true Successful
+ @return false if SO_MAX_ARGC > 0: Too many arguments
+ if SO_MAX_ARGC == 0: Memory allocation failure
+ */
+ bool Init(
+ int a_argc,
+ SOCHAR * a_argv[],
+ const SOption * a_rgOptions,
+ int a_nFlags = 0
+ );
+
+ /*! @brief Change the current options table during option parsing.
+
+ @param a_rgOptions Valid option array
+ */
+ inline void SetOptions(const SOption * a_rgOptions) {
+ m_rgOptions = a_rgOptions;
+ }
+
+ /*! @brief Change the current flags during option parsing.
+
+ Note that changing the SO_O_USEALL flag here will have no affect.
+ It must be set using Init() or the constructor.
+
+ @param a_nFlags Flags to modify the processing of the arguments
+ */
+ inline void SetFlags(int a_nFlags) { m_nFlags = a_nFlags; }
+
+ /*! @brief Query if a particular flag is set */
+ inline bool HasFlag(int a_nFlag) const {
+ return (m_nFlags & a_nFlag) == a_nFlag;
+ }
+
+ /*! @brief Advance to the next option if available.
+
+ When all options have been processed it will return false. When true
+ has been returned, you must check for an invalid or unrecognized
+ option using the LastError() method. This will be return an error
+ value other than SO_SUCCESS on an error. All standard data
+ (e.g. OptionText(), OptionArg(), OptionId(), etc) will be available
+ depending on the error.
+
+ After all options have been processed, the remaining files from the
+ command line can be processed in same order as they were passed to
+ the program.
+
+ @return true option or error available for processing
+ @return false all options have been processed
+ */
+ bool Next();
+
+ /*! Stops processing of the command line and returns all remaining
+ arguments as files. The next call to Next() will return false.
+ */
+ void Stop();
+
+ /*! @brief Return the last error that occurred.
+
+ This function must always be called before processing the current
+ option. This function is available only when Next() has returned true.
+ */
+ inline ESOError LastError() const { return m_nLastError; }
+
+ /*! @brief Return the nId value from the options array for the current
+ option.
+
+ This function is available only when Next() has returned true.
+ */
+ inline int OptionId() const { return m_nOptionId; }
+
+ /*! @brief Return the pszArg from the options array for the current
+ option.
+
+ This function is available only when Next() has returned true.
+ */
+ inline const SOCHAR * OptionText() const { return m_pszOptionText; }
+
+ /*! @brief Return the argument for the current option where one exists.
+
+ If there is no argument for the option, this will return NULL.
+ This function is available only when Next() has returned true.
+ */
+ inline SOCHAR * OptionArg() const { return m_pszOptionArg; }
+
+ /*! @brief Validate and return the desired number of arguments.
+
+ This is only valid when OptionId() has return the ID of an option
+ that is registered as SO_MULTI. It may be called multiple times
+ each time returning the desired number of arguments. Previously
+ returned argument pointers are remain valid.
+
+ If an error occurs during processing, NULL will be returned and
+ the error will be available via LastError().
+
+ @param n Number of arguments to return.
+ */
+ SOCHAR ** MultiArg(int n);
+
+ /*! @brief Returned the number of entries in the Files() array.
+
+ After Next() has returned false, this will be the list of files (or
+ otherwise unprocessed arguments).
+ */
+ inline int FileCount() const { return m_argc - m_nLastArg; }
+
+ /*! @brief Return the specified file argument.
+
+ @param n Index of the file to return. This must be between 0
+ and FileCount() - 1;
+ */
+ inline SOCHAR * File(int n) const {
+ SO_ASSERT(n >= 0 && n < FileCount());
+ return m_argv[m_nLastArg + n];
+ }
+
+ /*! @brief Return the array of files. */
+ inline SOCHAR ** Files() const { return &m_argv[m_nLastArg]; }
+
+private:
+ CSimpleOptTempl(const CSimpleOptTempl &); // disabled
+ CSimpleOptTempl & operator=(const CSimpleOptTempl &); // disabled
+
+ SOCHAR PrepareArg(SOCHAR * a_pszString) const;
+ bool NextClumped();
+ void ShuffleArg(int a_nStartIdx, int a_nCount);
+ int LookupOption(const SOCHAR * a_pszOption) const;
+ int CalcMatch(const SOCHAR *a_pszSource, const SOCHAR *a_pszTest) const;
+
+ // Find the '=' character within a string.
+ inline SOCHAR * FindEquals(SOCHAR *s) const {
+ while (*s && *s != (SOCHAR)'=') ++s;
+ return *s ? s : NULL;
+ }
+ bool IsEqual(SOCHAR a_cLeft, SOCHAR a_cRight, int a_nArgType) const;
+
+ inline void Copy(SOCHAR ** ppDst, SOCHAR ** ppSrc, int nCount) const {
+#ifdef SO_MAX_ARGS
+ // keep our promise of no CLIB usage
+ while (nCount-- > 0) *ppDst++ = *ppSrc++;
+#else
+ memcpy(ppDst, ppSrc, nCount * sizeof(SOCHAR*));
+#endif
+ }
+
+private:
+ const SOption * m_rgOptions; //!< pointer to options table
+ int m_nFlags; //!< flags
+ int m_nOptionIdx; //!< current argv option index
+ int m_nOptionId; //!< id of current option (-1 = invalid)
+ int m_nNextOption; //!< index of next option
+ int m_nLastArg; //!< last argument, after this are files
+ int m_argc; //!< argc to process
+ SOCHAR ** m_argv; //!< argv
+ const SOCHAR * m_pszOptionText; //!< curr option text, e.g. "-f"
+ SOCHAR * m_pszOptionArg; //!< curr option arg, e.g. "c:\file.txt"
+ SOCHAR * m_pszClump; //!< clumped single character options
+ SOCHAR m_szShort[3]; //!< temp for clump and combined args
+ ESOError m_nLastError; //!< error status from the last call
+ SOCHAR ** m_rgShuffleBuf; //!< shuffle buffer for large argc
+};
+
+// ---------------------------------------------------------------------------
+// IMPLEMENTATION
+// ---------------------------------------------------------------------------
+
+template
+bool
+CSimpleOptTempl::Init(
+ int a_argc,
+ SOCHAR * a_argv[],
+ const SOption * a_rgOptions,
+ int a_nFlags
+ )
+{
+ m_argc = a_argc;
+ m_nLastArg = a_argc;
+ m_argv = a_argv;
+ m_rgOptions = a_rgOptions;
+ m_nLastError = SO_SUCCESS;
+ m_nOptionIdx = 0;
+ m_nOptionId = -1;
+ m_pszOptionText = NULL;
+ m_pszOptionArg = NULL;
+ m_nNextOption = (a_nFlags & SO_O_USEALL) ? 0 : 1;
+ m_szShort[0] = (SOCHAR)'-';
+ m_szShort[2] = (SOCHAR)'\0';
+ m_nFlags = a_nFlags;
+ m_pszClump = NULL;
+
+#ifdef SO_MAX_ARGS
+ if (m_argc > SO_MAX_ARGS) {
+ m_nLastError = SO_ARG_INVALID_DATA;
+ m_nLastArg = 0;
+ return false;
+ }
+#else
+ if (m_rgShuffleBuf) {
+ free(m_rgShuffleBuf);
+ }
+ if (m_argc > SO_STATICBUF) {
+ m_rgShuffleBuf = (SOCHAR**) malloc(sizeof(SOCHAR*) * m_argc);
+ if (!m_rgShuffleBuf) {
+ return false;
+ }
+ }
+#endif
+
+ return true;
+}
+
+template
+bool
+CSimpleOptTempl::Next()
+{
+#ifdef SO_MAX_ARGS
+ if (m_argc > SO_MAX_ARGS) {
+ SO_ASSERT(!"Too many args! Check the return value of Init()!");
+ return false;
+ }
+#endif
+
+ // process a clumped option string if appropriate
+ if (m_pszClump && *m_pszClump) {
+ // silently discard invalid clumped option
+ bool bIsValid = NextClumped();
+ while (*m_pszClump && !bIsValid && HasFlag(SO_O_NOERR)) {
+ bIsValid = NextClumped();
+ }
+
+ // return this option if valid or we are returning errors
+ if (bIsValid || !HasFlag(SO_O_NOERR)) {
+ return true;
+ }
+ }
+ SO_ASSERT(!m_pszClump || !*m_pszClump);
+ m_pszClump = NULL;
+
+ // init for the next option
+ m_nOptionIdx = m_nNextOption;
+ m_nOptionId = -1;
+ m_pszOptionText = NULL;
+ m_pszOptionArg = NULL;
+ m_nLastError = SO_SUCCESS;
+
+ // find the next option
+ SOCHAR cFirst;
+ int nTableIdx = -1;
+ int nOptIdx = m_nOptionIdx;
+ while (nTableIdx < 0 && nOptIdx < m_nLastArg) {
+ SOCHAR * pszArg = m_argv[nOptIdx];
+ m_pszOptionArg = NULL;
+
+ // find this option in the options table
+ cFirst = PrepareArg(pszArg);
+ if (pszArg[0] == (SOCHAR)'-') {
+ // find any combined argument string and remove equals sign
+ m_pszOptionArg = FindEquals(pszArg);
+ if (m_pszOptionArg) {
+ *m_pszOptionArg++ = (SOCHAR)'\0';
+ }
+ }
+ nTableIdx = LookupOption(pszArg);
+
+ // if we didn't find this option but if it is a short form
+ // option then we try the alternative forms
+ if (nTableIdx < 0
+ && !m_pszOptionArg
+ && pszArg[0] == (SOCHAR)'-'
+ && pszArg[1]
+ && pszArg[1] != (SOCHAR)'-'
+ && pszArg[2])
+ {
+ // test for a short-form with argument if appropriate
+ if (HasFlag(SO_O_SHORTARG)) {
+ m_szShort[1] = pszArg[1];
+ int nIdx = LookupOption(m_szShort);
+ if (nIdx >= 0
+ && (m_rgOptions[nIdx].nArgType == SO_REQ_CMB
+ || m_rgOptions[nIdx].nArgType == SO_OPT))
+ {
+ m_pszOptionArg = &pszArg[2];
+ pszArg = m_szShort;
+ nTableIdx = nIdx;
+ }
+ }
+
+ // test for a clumped short-form option string and we didn't
+ // match on the short-form argument above
+ if (nTableIdx < 0 && HasFlag(SO_O_CLUMP)) {
+ m_pszClump = &pszArg[1];
+ ++m_nNextOption;
+ if (nOptIdx > m_nOptionIdx) {
+ ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx);
+ }
+ return Next();
+ }
+ }
+
+ // The option wasn't found. If it starts with a switch character
+ // and we are not suppressing errors for invalid options then it
+ // is reported as an error, otherwise it is data.
+ if (nTableIdx < 0) {
+ if (!HasFlag(SO_O_NOERR) && pszArg[0] == (SOCHAR)'-') {
+ m_pszOptionText = pszArg;
+ break;
+ }
+
+ pszArg[0] = cFirst;
+ ++nOptIdx;
+ if (m_pszOptionArg) {
+ *(--m_pszOptionArg) = (SOCHAR)'=';
+ }
+ }
+ }
+
+ // end of options
+ if (nOptIdx >= m_nLastArg) {
+ if (nOptIdx > m_nOptionIdx) {
+ ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx);
+ }
+ return false;
+ }
+ ++m_nNextOption;
+
+ // get the option id
+ ESOArgType nArgType = SO_NONE;
+ if (nTableIdx < 0) {
+ m_nLastError = (ESOError) nTableIdx; // error code
+ }
+ else {
+ m_nOptionId = m_rgOptions[nTableIdx].nId;
+ m_pszOptionText = m_rgOptions[nTableIdx].pszArg;
+
+ // ensure that the arg type is valid
+ nArgType = m_rgOptions[nTableIdx].nArgType;
+ switch (nArgType) {
+ case SO_NONE:
+ if (m_pszOptionArg) {
+ m_nLastError = SO_ARG_INVALID;
+ }
+ break;
+
+ case SO_REQ_SEP:
+ if (m_pszOptionArg) {
+ // they wanted separate args, but we got a combined one,
+ // unless we are pedantic, just accept it.
+ if (HasFlag(SO_O_PEDANTIC)) {
+ m_nLastError = SO_ARG_INVALID_TYPE;
+ }
+ }
+ // more processing after we shuffle
+ break;
+
+ case SO_REQ_CMB:
+ if (!m_pszOptionArg) {
+ m_nLastError = SO_ARG_MISSING;
+ }
+ break;
+
+ case SO_OPT:
+ // nothing to do
+ break;
+
+ case SO_MULTI:
+ // nothing to do. Caller must now check for valid arguments
+ // using GetMultiArg()
+ break;
+ }
+ }
+
+ // shuffle the files out of the way
+ if (nOptIdx > m_nOptionIdx) {
+ ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx);
+ }
+
+ // we need to return the separate arg if required, just re-use the
+ // multi-arg code because it all does the same thing
+ if ( nArgType == SO_REQ_SEP
+ && !m_pszOptionArg
+ && m_nLastError == SO_SUCCESS)
+ {
+ SOCHAR ** ppArgs = MultiArg(1);
+ if (ppArgs) {
+ m_pszOptionArg = *ppArgs;
+ }
+ }
+
+ return true;
+}
+
+template
+void
+CSimpleOptTempl::Stop()
+{
+ if (m_nNextOption < m_nLastArg) {
+ ShuffleArg(m_nNextOption, m_nLastArg - m_nNextOption);
+ }
+}
+
+template
+SOCHAR
+CSimpleOptTempl::PrepareArg(
+ SOCHAR * a_pszString
+ ) const
+{
+#ifdef _WIN32
+ // On Windows we can accept the forward slash as a single character
+ // option delimiter, but it cannot replace the '-' option used to
+ // denote stdin. On Un*x paths may start with slash so it may not
+ // be used to start an option.
+ if (!HasFlag(SO_O_NOSLASH)
+ && a_pszString[0] == (SOCHAR)'/'
+ && a_pszString[1]
+ && a_pszString[1] != (SOCHAR)'-')
+ {
+ a_pszString[0] = (SOCHAR)'-';
+ return (SOCHAR)'/';
+ }
+#endif
+ return a_pszString[0];
+}
+
+template
+bool
+CSimpleOptTempl::NextClumped()
+{
+ // prepare for the next clumped option
+ m_szShort[1] = *m_pszClump++;
+ m_nOptionId = -1;
+ m_pszOptionText = NULL;
+ m_pszOptionArg = NULL;
+ m_nLastError = SO_SUCCESS;
+
+ // lookup this option, ensure that we are using exact matching
+ int nSavedFlags = m_nFlags;
+ m_nFlags = SO_O_EXACT;
+ int nTableIdx = LookupOption(m_szShort);
+ m_nFlags = nSavedFlags;
+
+ // unknown option
+ if (nTableIdx < 0) {
+ m_pszOptionText = m_szShort; // invalid option
+ m_nLastError = (ESOError) nTableIdx; // error code
+ return false;
+ }
+
+ // valid option
+ m_pszOptionText = m_rgOptions[nTableIdx].pszArg;
+ ESOArgType nArgType = m_rgOptions[nTableIdx].nArgType;
+ if (nArgType == SO_NONE) {
+ m_nOptionId = m_rgOptions[nTableIdx].nId;
+ return true;
+ }
+
+ if (nArgType == SO_REQ_CMB && *m_pszClump) {
+ m_nOptionId = m_rgOptions[nTableIdx].nId;
+ m_pszOptionArg = m_pszClump;
+ while (*m_pszClump) ++m_pszClump; // must point to an empty string
+ return true;
+ }
+
+ // invalid option as it requires an argument
+ m_nLastError = SO_ARG_MISSING;
+ return true;
+}
+
+// Shuffle arguments to the end of the argv array.
+//
+// For example:
+// argv[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8" };
+//
+// ShuffleArg(1, 1) = { "0", "2", "3", "4", "5", "6", "7", "8", "1" };
+// ShuffleArg(5, 2) = { "0", "1", "2", "3", "4", "7", "8", "5", "6" };
+// ShuffleArg(2, 4) = { "0", "1", "6", "7", "8", "2", "3", "4", "5" };
+template
+void
+CSimpleOptTempl::ShuffleArg(
+ int a_nStartIdx,
+ int a_nCount
+ )
+{
+ SOCHAR * staticBuf[SO_STATICBUF];
+ SOCHAR ** buf = m_rgShuffleBuf ? m_rgShuffleBuf : staticBuf;
+ int nTail = m_argc - a_nStartIdx - a_nCount;
+
+ // make a copy of the elements to be moved
+ Copy(buf, m_argv + a_nStartIdx, a_nCount);
+
+ // move the tail down
+ Copy(m_argv + a_nStartIdx, m_argv + a_nStartIdx + a_nCount, nTail);
+
+ // append the moved elements to the tail
+ Copy(m_argv + a_nStartIdx + nTail, buf, a_nCount);
+
+ // update the index of the last unshuffled arg
+ m_nLastArg -= a_nCount;
+}
+
+// match on the long format strings. partial matches will be
+// accepted only if that feature is enabled.
+template
+int
+CSimpleOptTempl::LookupOption(
+ const SOCHAR * a_pszOption
+ ) const
+{
+ int nBestMatch = -1; // index of best match so far
+ int nBestMatchLen = 0; // matching characters of best match
+ int nLastMatchLen = 0; // matching characters of last best match
+
+ for (int n = 0; m_rgOptions[n].nId >= 0; ++n) {
+ // the option table must use hyphens as the option character,
+ // the slash character is converted to a hyphen for testing.
+ SO_ASSERT(m_rgOptions[n].pszArg[0] != (SOCHAR)'/');
+
+ int nMatchLen = CalcMatch(m_rgOptions[n].pszArg, a_pszOption);
+ if (nMatchLen == -1) {
+ return n;
+ }
+ if (nMatchLen > 0 && nMatchLen >= nBestMatchLen) {
+ nLastMatchLen = nBestMatchLen;
+ nBestMatchLen = nMatchLen;
+ nBestMatch = n;
+ }
+ }
+
+ // only partial matches or no match gets to here, ensure that we
+ // don't return a partial match unless it is a clear winner
+ if (HasFlag(SO_O_EXACT) || nBestMatch == -1) {
+ return SO_OPT_INVALID;
+ }
+ return (nBestMatchLen > nLastMatchLen) ? nBestMatch : SO_OPT_MULTIPLE;
+}
+
+// calculate the number of characters that match (case-sensitive)
+// 0 = no match, > 0 == number of characters, -1 == perfect match
+template
+int
+CSimpleOptTempl::CalcMatch(
+ const SOCHAR * a_pszSource,
+ const SOCHAR * a_pszTest
+ ) const
+{
+ if (!a_pszSource || !a_pszTest) {
+ return 0;
+ }
+
+ // determine the argument type
+ int nArgType = SO_O_ICASE_LONG;
+ if (a_pszSource[0] != '-') {
+ nArgType = SO_O_ICASE_WORD;
+ }
+ else if (a_pszSource[1] != '-' && !a_pszSource[2]) {
+ nArgType = SO_O_ICASE_SHORT;
+ }
+
+ // match and skip leading hyphens
+ while (*a_pszSource == (SOCHAR)'-' && *a_pszSource == *a_pszTest) {
+ ++a_pszSource;
+ ++a_pszTest;
+ }
+ if (*a_pszSource == (SOCHAR)'-' || *a_pszTest == (SOCHAR)'-') {
+ return 0;
+ }
+
+ // find matching number of characters in the strings
+ int nLen = 0;
+ while (*a_pszSource && IsEqual(*a_pszSource, *a_pszTest, nArgType)) {
+ ++a_pszSource;
+ ++a_pszTest;
+ ++nLen;
+ }
+
+ // if we have exhausted the source...
+ if (!*a_pszSource) {
+ // and the test strings, then it's a perfect match
+ if (!*a_pszTest) {
+ return -1;
+ }
+
+ // otherwise the match failed as the test is longer than
+ // the source. i.e. "--mant" will not match the option "--man".
+ return 0;
+ }
+
+ // if we haven't exhausted the test string then it is not a match
+ // i.e. "--mantle" will not best-fit match to "--mandate" at all.
+ if (*a_pszTest) {
+ return 0;
+ }
+
+ // partial match to the current length of the test string
+ return nLen;
+}
+
+template
+bool
+CSimpleOptTempl::IsEqual(
+ SOCHAR a_cLeft,
+ SOCHAR a_cRight,
+ int a_nArgType
+ ) const
+{
+ // if this matches then we are doing case-insensitive matching
+ if (m_nFlags & a_nArgType) {
+ if (a_cLeft >= 'A' && a_cLeft <= 'Z') a_cLeft += 'a' - 'A';
+ if (a_cRight >= 'A' && a_cRight <= 'Z') a_cRight += 'a' - 'A';
+ }
+ return a_cLeft == a_cRight;
+}
+
+// calculate the number of characters that match (case-sensitive)
+// 0 = no match, > 0 == number of characters, -1 == perfect match
+template
+SOCHAR **
+CSimpleOptTempl::MultiArg(
+ int a_nCount
+ )
+{
+ // ensure we have enough arguments
+ if (m_nNextOption + a_nCount > m_nLastArg) {
+ m_nLastError = SO_ARG_MISSING;
+ return NULL;
+ }
+
+ // our argument array
+ SOCHAR ** rgpszArg = &m_argv[m_nNextOption];
+
+ // Ensure that each of the following don't start with an switch character.
+ // Only make this check if we are returning errors for unknown arguments.
+ if (!HasFlag(SO_O_NOERR)) {
+ for (int n = 0; n < a_nCount; ++n) {
+ SOCHAR ch = PrepareArg(rgpszArg[n]);
+ if (rgpszArg[n][0] == (SOCHAR)'-') {
+ rgpszArg[n][0] = ch;
+ m_nLastError = SO_ARG_INVALID_DATA;
+ return NULL;
+ }
+ rgpszArg[n][0] = ch;
+ }
+ }
+
+ // all good
+ m_nNextOption += a_nCount;
+ return rgpszArg;
+}
+
+
+// ---------------------------------------------------------------------------
+// TYPE DEFINITIONS
+// ---------------------------------------------------------------------------
+
+/*! @brief ASCII/MBCS version of CSimpleOpt */
+typedef CSimpleOptTempl CSimpleOptA;
+
+/*! @brief wchar_t version of CSimpleOpt */
+typedef CSimpleOptTempl CSimpleOptW;
+
+#if defined(_UNICODE)
+/*! @brief TCHAR version dependent on if _UNICODE is defined */
+# define CSimpleOpt CSimpleOptW
+#else
+/*! @brief TCHAR version dependent on if _UNICODE is defined */
+# define CSimpleOpt CSimpleOptA
+#endif
+
+#endif // INCLUDED_SimpleOpt
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 21ce02ed0f..c99d1a3c54 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,6 +13,7 @@ set(SOURCE_FILES
util/SignalMediator.hxx
util/Singleton.hxx
util/Meta.hxx
+ util/ParseCli.{hxx,cxx}
util/Exception.{hxx,cxx}
util/OSystem.{hxx,cxx}
services/Randomizer.{hxx,cxx}
diff --git a/src/Game.cxx b/src/Game.cxx
index bb70c5a53b..42defd90e3 100644
--- a/src/Game.cxx
+++ b/src/Game.cxx
@@ -79,7 +79,7 @@ void Game::initialize()
LOG(LOG_DEBUG) << "Initialized Game Object";
}
-void Game::run(bool SkipMenu)
+void Game::run()
{
Camera::instance().centerScreenOnMapCenter();
diff --git a/src/Game.hxx b/src/Game.hxx
index 23785bf7f6..c98e3bd901 100644
--- a/src/Game.hxx
+++ b/src/Game.hxx
@@ -44,7 +44,7 @@ public:
* @details starts running the game
* @param SkipMenu if the main menu should be skipped or not
*/
- virtual void run(bool SkipMenu = false);
+ virtual void run();
/// ends the game
virtual void shutdown();
diff --git a/src/engine/basics/Settings.hxx b/src/engine/basics/Settings.hxx
index 2817f0a833..c0e2b2c742 100644
--- a/src/engine/basics/Settings.hxx
+++ b/src/engine/basics/Settings.hxx
@@ -166,6 +166,15 @@ struct SettingsData
/// Write errors to a log file
bool writeErrorLogFile;
+
+ // ==================================
+ // Command line options
+ // ==================================
+
+ /// Sets a different video driver
+ std::string videoDriver = "Default";
+
+ bool skipMenu = false;
};
/**
diff --git a/src/main.cxx b/src/main.cxx
index 646e986d52..6cc464ce99 100644
--- a/src/main.cxx
+++ b/src/main.cxx
@@ -6,11 +6,13 @@
#include "LOG.hxx"
#include "engine/WindowManager.hxx"
#include
+#include "SimpleOpt.h"
+#include "ParseCli.hxx"
#include
#include
-bool initialize(const char *videoDriver)
+bool initialize()
{
if (SDL_Init(0) != 0)
{
@@ -19,6 +21,10 @@ bool initialize(const char *videoDriver)
return false;
}
+ const char *videoDriver = nullptr;
+ if (Settings::instance().videoDriver != "Default")
+ videoDriver = Settings::instance().videoDriver.c_str();
+
if (SDL_VideoInit(videoDriver) != 0)
{
LOG(LOG_ERROR) << "Unknown video driver " << videoDriver;
@@ -47,29 +53,9 @@ bool initialize(const char *videoDriver)
int protected_main(int argc, char **argv)
{
- (void)argc;
- (void)argv;
-
- LOG(LOG_INFO) << VERSION;
-
- // add commandline parameter to skipMenu
- auto has_args = [argv, argc](const std::string ¶m)
- {
- for (int i = 1; i < argc; ++i)
- if (param == argv[i])
- return i;
-
- LOG(LOG_DEBUG) << "Unknown game option " << param;
- return 0;
- };
-
- bool skipMenu = has_args("--skipMenu");
- uint32_t videoOpt = has_args("--video");
- const char *videoDriver = nullptr;
- if (videoOpt)
- {
- videoDriver = argv[videoOpt + 1];
- }
+ ParseCli cli;
+ if (!cli.ProcessCommandLine(argc, argv))
+ return EXIT_FAILURE;
LOG(LOG_DEBUG) << "Launching Cytopia";
@@ -77,13 +63,13 @@ int protected_main(int argc, char **argv)
LOG(LOG_DEBUG) << "Initializing Cytopia";
- if (!initialize(videoDriver))
+ if (!initialize())
return EXIT_FAILURE;
else
LOG(LOG_DEBUG) << "DONE Cytopia";
bool startGame = true;
- if (!skipMenu)
+ if (!Settings::instance().skipMenu)
{
startGame = mainMenu();
}
@@ -91,7 +77,7 @@ int protected_main(int argc, char **argv)
if (startGame)
{
LOG(LOG_DEBUG) << "Running the Game";
- game.run(skipMenu);
+ game.run();
}
LOG(LOG_DEBUG) << "Closing the Game";
@@ -118,4 +104,4 @@ int main(int argc, char **argv)
}
return EXIT_FAILURE;
-}
+}
\ No newline at end of file
diff --git a/src/util/ParseCli.cxx b/src/util/ParseCli.cxx
new file mode 100644
index 0000000000..824456a2d7
--- /dev/null
+++ b/src/util/ParseCli.cxx
@@ -0,0 +1,90 @@
+#include "ParseCli.hxx"
+#include "LOG.hxx"
+#include "Singleton.hxx"
+#include "Settings.hxx"
+
+// option identifiers
+enum
+{
+ OPT_HELP,
+ OPT_SKIPMENU,
+ OPT_VIDEODRIVER
+};
+
+// option array
+CSimpleOpt::SOption cmdline_options[] = {{OPT_SKIPMENU, ("--skipMenu"), SO_NONE},
+ {OPT_VIDEODRIVER, ("--video"), SO_REQ_SEP},
+ {OPT_HELP, ("--help"), SO_NONE},
+ {OPT_HELP, ("-h"), SO_NONE},
+ SO_END_OF_OPTIONS};
+
+bool ParseCli::ProcessCommandLine(int argc, char *argv[])
+{
+ CSimpleOpt args(argc, argv, cmdline_options);
+ bool success = true;
+ while (args.Next())
+ {
+ if (args.LastError() != SO_SUCCESS)
+ {
+ LOG(LOG_ERROR) << GetLastErrorText(args.LastError()) << " " << args.OptionText();
+ success = false;
+ }
+
+ switch (args.OptionId())
+ {
+ case OPT_HELP:
+ ShowUsage();
+ return false;
+ case OPT_SKIPMENU:
+ Settings::instance().skipMenu = true;
+ break;
+ case OPT_VIDEODRIVER:
+ if (args.OptionArg())
+ {
+ Settings::instance().videoDriver = args.OptionArg();
+ }
+ else
+ {
+ LOG(LOG_ERROR) << "videoDriver not set";
+ ShowUsage();
+ return false;
+ }
+ break;
+ default:
+ ShowUsage();
+ }
+ }
+
+ return success;
+}
+
+void ParseCli::ShowUsage()
+{
+ LOG(LOG_INFO) << "Usage: Cytopia [OPTIONS]";
+ LOG(LOG_INFO) << "\t--help (this)";
+ LOG(LOG_INFO) << "\t--skipMenu (Skips the main menu)";
+ LOG(LOG_INFO) << "\t--video (Sets a different video driver)";
+}
+
+std::string ParseCli::GetLastErrorText(int a_nError)
+{
+ switch (a_nError)
+ {
+ case SO_SUCCESS:
+ return "Success";
+ case SO_OPT_INVALID:
+ return "Unrecognized option";
+ case SO_OPT_MULTIPLE:
+ return "Option matched multiple strings";
+ case SO_ARG_INVALID:
+ return "Option does not accept argument";
+ case SO_ARG_INVALID_TYPE:
+ return "Invalid argument format";
+ case SO_ARG_MISSING:
+ return "Required argument is missing";
+ case SO_ARG_INVALID_DATA:
+ return "Invalid argument data";
+ default:
+ return "Unknown error";
+ }
+}
\ No newline at end of file
diff --git a/src/util/ParseCli.hxx b/src/util/ParseCli.hxx
new file mode 100644
index 0000000000..c2070a93c3
--- /dev/null
+++ b/src/util/ParseCli.hxx
@@ -0,0 +1,18 @@
+#ifndef CYTOPIA_PARSECLI_HXX
+#define CYTOPIA_PARSECLI_HXX
+
+#include "SimpleOpt.h"
+#include
+
+class ParseCli
+{
+public:
+ bool ProcessCommandLine(int argc, char **argv);
+
+private:
+ std::string GetLastErrorText(int a_nError);
+
+ void ShowUsage();
+};
+
+#endif //CYTOPIA_PARSECLI_HXX