SimpleGlob.h revision 8b52a31e
1/*! @file SimpleGlob.h
2
3    @version 3.5
4
5    @brief A cross-platform file globbing library providing the ability to
6    expand wildcards in command-line arguments to a list of all matching
7    files. It is designed explicitly to be portable to any platform and has
8    been tested on Windows and Linux. See CSimpleGlobTempl for the class
9    definition.
10
11    @section features FEATURES
12
13    -   MIT Licence allows free use in all software (including GPL and
14        commercial)
15    -   multi-platform (Windows 95/98/ME/NT/2K/XP, Linux, Unix)
16    -   supports most of the standard linux glob() options
17    -   recognition of a forward paths as equivalent to a backward slash
18        on Windows. e.g. "c:/path/foo*" is equivalent to "c:\path\foo*".
19    -   implemented with only a single C++ header file
20    -   char, wchar_t and Windows TCHAR in the same program
21    -   complete working examples included
22    -   compiles cleanly at warning level 4 (Windows/VC.NET 2003),
23        warning level 3 (Windows/VC6) and -Wall (Linux/gcc)
24
25    @section usage USAGE
26
27    The SimpleGlob class is used by following these steps:
28
29    <ol>
30    <li> Include the SimpleGlob.h header file
31
32        <pre>
33        \#include "SimpleGlob.h"
34        </pre>
35
36   <li> Instantiate a CSimpleGlob object supplying the appropriate flags.
37
38        <pre>
39        @link CSimpleGlobTempl CSimpleGlob @endlink glob(FLAGS);
40        </pre>
41
42   <li> Add all file specifications to the glob class.
43
44        <pre>
45        glob.Add("file*");
46        glob.Add(argc, argv);
47        </pre>
48
49   <li> Process all files with File(), Files() and FileCount()
50
51        <pre>
52        for (int n = 0; n < glob.FileCount(); ++n) {
53            ProcessFile(glob.File(n));
54        }
55        </pre>
56
57    </ol>
58
59    @section licence MIT LICENCE
60
61    The licence text below is the boilerplate "MIT Licence" used from:
62    http://www.opensource.org/licenses/mit-license.php
63
64    Copyright (c) 2006-2007, Brodie Thiesfield
65
66    Permission is hereby granted, free of charge, to any person obtaining a
67    copy of this software and associated documentation files (the "Software"),
68    to deal in the Software without restriction, including without limitation
69    the rights to use, copy, modify, merge, publish, distribute, sublicense,
70    and/or sell copies of the Software, and to permit persons to whom the
71    Software is furnished to do so, subject to the following conditions:
72
73    The above copyright notice and this permission notice shall be included
74    in all copies or substantial portions of the Software.
75
76    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
77    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
78    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
79    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
80    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
81    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
82    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
83*/
84
85#ifndef INCLUDED_SimpleGlob
86#define INCLUDED_SimpleGlob
87
88/*! @brief The operation of SimpleGlob is fine-tuned via the use of a
89    combination of the following flags.
90
91    The flags may be passed at initialization of the class and used for every
92    filespec added, or alternatively they may optionally be specified in the
93    call to Add() and be different for each filespec.
94
95    @param SG_GLOB_ERR
96        Return upon read error (e.g. directory does not have read permission)
97
98    @param SG_GLOB_MARK
99        Append a slash (backslash in Windows) to every path which corresponds
100        to a directory
101
102    @param SG_GLOB_NOSORT
103        By default, files are returned in sorted into string order. With this
104        flag, no sorting is done. This is not compatible with
105        SG_GLOB_FULLSORT.
106
107    @param SG_GLOB_FULLSORT
108        By default, files are sorted in groups belonging to each filespec that
109        was added. For example if the filespec "b*" was added before the
110        filespec "a*" then the argv array will contain all b* files sorted in
111        order, followed by all a* files sorted in order. If this flag is
112        specified, the entire array will be sorted ignoring the filespec
113        groups.
114
115    @param SG_GLOB_NOCHECK
116        If the pattern doesn't match anything, return the original pattern.
117
118    @param SG_GLOB_TILDE
119        Tilde expansion is carried out (on Unix platforms)
120
121    @param SG_GLOB_ONLYDIR
122        Return only directories which match (not compatible with
123        SG_GLOB_ONLYFILE)
124
125    @param SG_GLOB_ONLYFILE
126        Return only files which match (not compatible with SG_GLOB_ONLYDIR)
127
128    @param SG_GLOB_NODOT
129        Do not return the "." or ".." special directories.
130 */
131enum SG_Flags {
132    SG_GLOB_ERR         = 1 << 0,
133    SG_GLOB_MARK        = 1 << 1,
134    SG_GLOB_NOSORT      = 1 << 2,
135    SG_GLOB_NOCHECK     = 1 << 3,
136    SG_GLOB_TILDE       = 1 << 4,
137    SG_GLOB_ONLYDIR     = 1 << 5,
138    SG_GLOB_ONLYFILE    = 1 << 6,
139    SG_GLOB_NODOT       = 1 << 7,
140    SG_GLOB_FULLSORT    = 1 << 8
141};
142
143/*! @brief Error return codes */
144enum SG_Error {
145    SG_SUCCESS          =  0,
146    SG_ERR_NOMATCH      =  1,
147    SG_ERR_MEMORY       = -1,
148    SG_ERR_FAILURE      = -2
149};
150#ifndef MAX_PATH
151# define MAX_PATH           4096
152#endif
153// ---------------------------------------------------------------------------
154// Platform dependent implementations
155
156// if we aren't on Windows and we have ICU available, then enable ICU
157// by default. Define this to 0 to intentially disable it.
158#ifndef SG_HAVE_ICU
159# if !defined(WIN32) && defined(USTRING_H)
160#   define SG_HAVE_ICU 1
161# else
162#   define SG_HAVE_ICU 0
163# endif
164#endif
165
166// don't include this in documentation as it isn't relevant
167#ifndef DOXYGEN
168
169// on Windows we want to use MBCS aware string functions and mimic the
170// Unix glob functionality. On Unix we just use glob.
171#ifdef WIN32
172# include <mbstring.h>
173# define sg_strchr          ::_mbschr
174# define sg_strrchr         ::_mbsrchr
175# define sg_strlen          ::_mbslen
176# if __STDC_WANT_SECURE_LIB__
177#  define sg_strcpy_s(a,n,b) ::_mbscpy_s(a,n,b)
178# else
179#  define sg_strcpy_s(a,n,b) ::_mbscpy(a,b)
180# endif
181# define sg_strcmp          ::_mbscmp
182# define sg_strcasecmp      ::_mbsicmp
183# define SOCHAR_T           unsigned char
184#else
185# include <sys/types.h>
186# include <sys/stat.h>
187# include <glob.h>
188# include <limits.h>
189# define sg_strchr          ::strchr
190# define sg_strrchr         ::strrchr
191# define sg_strlen          ::strlen
192# define sg_strcpy_s(a,n,b) ::strcpy(a,b)
193# define sg_strcmp          ::strcmp
194# define sg_strcasecmp      ::strcasecmp
195# define SOCHAR_T           char
196#endif
197
198#include <stdlib.h>
199#include <string.h>
200#include <wchar.h>
201
202// use assertions to test the input data
203#ifdef _DEBUG
204# ifdef _MSC_VER
205#  include <crtdbg.h>
206#  define SG_ASSERT(b)    _ASSERTE(b)
207# else
208#  include <assert.h>
209#  define SG_ASSERT(b)    assert(b)
210# endif
211#else
212# define SG_ASSERT(b)
213#endif
214
215/*! @brief String manipulation functions. */
216class SimpleGlobUtil
217{
218public:
219    static const char * strchr(const char *s, char c) {
220        return (char *) sg_strchr((const SOCHAR_T *)s, c);
221    }
222    static const wchar_t * strchr(const wchar_t *s, wchar_t c) {
223        return ::wcschr(s, c);
224    }
225#if SG_HAVE_ICU
226    static const UChar * strchr(const UChar *s, UChar c) {
227        return ::u_strchr(s, c);
228    }
229#endif
230
231    static const char * strrchr(const char *s, char c) {
232        return (char *) sg_strrchr((const SOCHAR_T *)s, c);
233    }
234    static const wchar_t * strrchr(const wchar_t *s, wchar_t c) {
235        return ::wcsrchr(s, c);
236    }
237#if SG_HAVE_ICU
238    static const UChar * strrchr(const UChar *s, UChar c) {
239        return ::u_strrchr(s, c);
240    }
241#endif
242
243    // Note: char strlen returns number of bytes, not characters
244    static size_t strlen(const char *s) { return ::strlen(s); }
245    static size_t strlen(const wchar_t *s) { return ::wcslen(s); }
246#if SG_HAVE_ICU
247    static size_t strlen(const UChar *s) { return ::u_strlen(s); }
248#endif
249
250    static void strcpy_s(char *dst, size_t n, const char *src)  {
251        (void) n;
252        sg_strcpy_s((SOCHAR_T *)dst, n, (const SOCHAR_T *)src);
253    }
254    static void strcpy_s(wchar_t *dst, size_t n, const wchar_t *src) {
255# if __STDC_WANT_SECURE_LIB__
256        ::wcscpy_s(dst, n, src);
257#else
258        (void) n;
259        ::wcscpy(dst, src);
260#endif
261    }
262#if SG_HAVE_ICU
263    static void strcpy_s(UChar *dst, size_t n, const UChar *src)  {
264        ::u_strncpy(dst, src, n);
265    }
266#endif
267
268    static int strcmp(const char *s1, const char *s2) {
269        return sg_strcmp((const SOCHAR_T *)s1, (const SOCHAR_T *)s2);
270    }
271    static int strcmp(const wchar_t *s1, const wchar_t *s2) {
272        return ::wcscmp(s1, s2);
273    }
274#if SG_HAVE_ICU
275    static int strcmp(const UChar *s1, const UChar *s2) {
276        return ::u_strcmp(s1, s2);
277    }
278#endif
279
280    static int strcasecmp(const char *s1, const char *s2) {
281        return sg_strcasecmp((const SOCHAR_T *)s1, (const SOCHAR_T *)s2);
282    }
283#if WIN32
284    static int strcasecmp(const wchar_t *s1, const wchar_t *s2) {
285        return ::_wcsicmp(s1, s2);
286    }
287#endif // WIN32
288#if SG_HAVE_ICU
289    static int strcasecmp(const UChar *s1, const UChar *s2) {
290        return u_strcasecmp(s1, s2, 0);
291    }
292#endif
293};
294
295enum SG_FileType {
296    SG_FILETYPE_INVALID,
297    SG_FILETYPE_FILE,
298    SG_FILETYPE_DIR
299};
300
301#ifdef WIN32
302#ifndef INVALID_FILE_ATTRIBUTES
303# define INVALID_FILE_ATTRIBUTES    ((uint32_t)-1)
304#endif
305
306#define SG_PATH_CHAR    '\\'
307
308/*! @brief Windows glob implementation. */
309template<class SOCHAR>
310struct SimpleGlobBase
311{
312    SimpleGlobBase() : m_hFind(INVALID_HANDLE_VALUE) { }
313
314    int FindFirstFileS(const char * a_pszFileSpec, unsigned int) {
315        m_hFind = FindFirstFileA(a_pszFileSpec, &m_oFindDataA);
316        if (m_hFind != INVALID_HANDLE_VALUE) {
317            return SG_SUCCESS;
318        }
319        uint32_t dwErr = GetLastError();
320        if (dwErr == ERROR_FILE_NOT_FOUND) {
321            return SG_ERR_NOMATCH;
322        }
323        return SG_ERR_FAILURE;
324    }
325   /* int FindFirstFileS(const wchar_t * a_pszFileSpec, unsigned int) {
326        m_hFind = FindFirstFileW(a_pszFileSpec, &m_oFindDataW);
327        if (m_hFind != INVALID_HANDLE_VALUE) {
328            return SG_SUCCESS;
329        }
330        uint32_t dwErr = GetLastError();
331        if (dwErr == ERROR_FILE_NOT_FOUND) {
332            return SG_ERR_NOMATCH;
333        }
334        return SG_ERR_FAILURE;
335    }*/
336
337    bool FindNextFileS(char) {
338        return FindNextFileA(m_hFind, &m_oFindDataA) != FALSE;
339    }
340  /*  bool FindNextFileS(wchar_t) {
341        return FindNextFileW(m_hFind, &m_oFindDataW) != FALSE;
342    }*/
343
344    void FindDone() {
345        FindClose(m_hFind);
346    }
347
348    const char * GetFileNameS(char) const {
349        return m_oFindDataA.cFileName;
350    }
351    /*const wchar_t * GetFileNameS(wchar_t) const {
352        return m_oFindDataW.cFileName;
353    }*/
354
355    bool IsDirS(char) const {
356        return GetFileTypeS(m_oFindDataA.dwFileAttributes) == SG_FILETYPE_DIR;
357    }
358    /*bool IsDirS(wchar_t) const {
359        return GetFileTypeS(m_oFindDataW.dwFileAttributes) == SG_FILETYPE_DIR;
360    }*/
361
362    SG_FileType GetFileTypeS(const char * a_pszPath) {
363        return GetFileTypeS(GetFileAttributesA(a_pszPath));
364    }
365    /*SG_FileType GetFileTypeS(const wchar_t * a_pszPath)  {
366        return GetFileTypeS(GetFileAttributesW(a_pszPath));
367    }*/
368    SG_FileType GetFileTypeS(uint32_t a_dwAttribs) const {
369        if (a_dwAttribs == INVALID_FILE_ATTRIBUTES) {
370            return SG_FILETYPE_INVALID;
371        }
372        if (a_dwAttribs & FILE_ATTRIBUTE_DIRECTORY) {
373            return SG_FILETYPE_DIR;
374        }
375        return SG_FILETYPE_FILE;
376    }
377typedef struct _FILETIME {
378    uint32_t dwLowDateTime;
379    uint32_t dwHighDateTime;
380} FILETIME;
381
382
383typedef struct _WIN32_FIND_DATAA {
384    uint32_t dwFileAttributes;
385    FILETIME ftCreationTime;
386    FILETIME ftLastAccessTime;
387    FILETIME ftLastWriteTime;
388    uint32_t nFileSizeHigh;
389    uint32_t nFileSizerLow;
390    uint32_t dwReserved0;
391    uint32_t dwReserved1;
392    char     cFileName[MAX_PATH];
393    char     cAlternateFileName[14];
394} WIN32_FIND_DATAA;
395
396private:
397    void *              m_hFind;
398    WIN32_FIND_DATAA    m_oFindDataA;
399    WIN32_FIND_DATAA    m_oFindDataW;
400};
401
402#else // !WIN32
403
404#define SG_PATH_CHAR    '/'
405
406/*! @brief Unix glob implementation. */
407template<class SOCHAR>
408struct SimpleGlobBase
409{
410    SimpleGlobBase() {
411        memset(&m_glob, 0, sizeof(m_glob));
412        m_uiCurr = (size_t)-1;
413    }
414
415    ~SimpleGlobBase() {
416        globfree(&m_glob);
417    }
418
419    void FilePrep() {
420        m_bIsDir = false;
421        size_t len = strlen(m_glob.gl_pathv[m_uiCurr]);
422        if (m_glob.gl_pathv[m_uiCurr][len-1] == '/') {
423            m_bIsDir = true;
424            m_glob.gl_pathv[m_uiCurr][len-1] = 0;
425        }
426    }
427
428    int FindFirstFileS(const char * a_pszFileSpec, unsigned int a_uiFlags) {
429        int nFlags = GLOB_MARK | GLOB_NOSORT;
430        if (a_uiFlags & SG_GLOB_ERR)    nFlags |= GLOB_ERR;
431        if (a_uiFlags & SG_GLOB_TILDE)  nFlags |= GLOB_TILDE;
432        int rc = glob(a_pszFileSpec, nFlags, NULL, &m_glob);
433        if (rc == GLOB_NOSPACE) return SG_ERR_MEMORY;
434        if (rc == GLOB_ABORTED) return SG_ERR_FAILURE;
435        if (rc == GLOB_NOMATCH) return SG_ERR_NOMATCH;
436        m_uiCurr = 0;
437        FilePrep();
438        return SG_SUCCESS;
439    }
440
441#if SG_HAVE_ICU
442    int FindFirstFileS(const UChar * a_pszFileSpec, unsigned int a_uiFlags) {
443        char buf[PATH_MAX] = { 0 };
444        UErrorCode status = U_ZERO_ERROR;
445        u_strToUTF8(buf, sizeof(buf), NULL, a_pszFileSpec, -1, &status);
446        if (U_FAILURE(status)) return SG_ERR_FAILURE;
447        return FindFirstFileS(buf, a_uiFlags);
448    }
449#endif
450
451    bool FindNextFileS(char) {
452        SG_ASSERT(m_uiCurr != (size_t)-1);
453        if (++m_uiCurr >= m_glob.gl_pathc) {
454            return false;
455        }
456        FilePrep();
457        return true;
458    }
459
460#if SG_HAVE_ICU
461    bool FindNextFileS(UChar) {
462        return FindNextFileS((char)0);
463    }
464#endif
465
466    void FindDone() {
467        globfree(&m_glob);
468        memset(&m_glob, 0, sizeof(m_glob));
469        m_uiCurr = (size_t)-1;
470    }
471
472    const char * GetFileNameS(char) const {
473        SG_ASSERT(m_uiCurr != (size_t)-1);
474        return m_glob.gl_pathv[m_uiCurr];
475    }
476
477#if SG_HAVE_ICU
478    const UChar * GetFileNameS(UChar) const {
479        const char * pszFile = GetFileNameS((char)0);
480        if (!pszFile) return NULL;
481        UErrorCode status = U_ZERO_ERROR;
482        memset(m_szBuf, 0, sizeof(m_szBuf));
483        u_strFromUTF8(m_szBuf, PATH_MAX, NULL, pszFile, -1, &status);
484        if (U_FAILURE(status)) return NULL;
485        return m_szBuf;
486    }
487#endif
488
489    bool IsDirS(char) const {
490        SG_ASSERT(m_uiCurr != (size_t)-1);
491        return m_bIsDir;
492    }
493
494#if SG_HAVE_ICU
495    bool IsDirS(UChar) const {
496        return IsDirS((char)0);
497    }
498#endif
499
500    SG_FileType GetFileTypeS(const char * a_pszPath) const {
501        struct stat sb;
502        if (0 != stat(a_pszPath, &sb)) {
503            return SG_FILETYPE_INVALID;
504        }
505        if (S_ISDIR(sb.st_mode)) {
506            return SG_FILETYPE_DIR;
507        }
508        if (S_ISREG(sb.st_mode)) {
509            return SG_FILETYPE_FILE;
510        }
511        return SG_FILETYPE_INVALID;
512    }
513
514#if SG_HAVE_ICU
515    SG_FileType GetFileTypeS(const UChar * a_pszPath) const {
516        char buf[PATH_MAX] = { 0 };
517        UErrorCode status = U_ZERO_ERROR;
518        u_strToUTF8(buf, sizeof(buf), NULL, a_pszPath, -1, &status);
519        if (U_FAILURE(status)) return SG_FILETYPE_INVALID;
520        return GetFileTypeS(buf);
521    }
522#endif
523
524private:
525    glob_t  m_glob;
526    size_t  m_uiCurr;
527    bool    m_bIsDir;
528#if SG_HAVE_ICU
529    mutable UChar m_szBuf[PATH_MAX];
530#endif
531};
532
533#endif // WIN32
534
535#endif // DOXYGEN
536
537// ---------------------------------------------------------------------------
538//                              MAIN TEMPLATE CLASS
539// ---------------------------------------------------------------------------
540
541/*! @brief Implementation of the SimpleGlob class */
542template<class SOCHAR>
543class CSimpleGlobTempl : private SimpleGlobBase<SOCHAR>
544{
545public:
546    /*! @brief Initialize the class.
547
548        @param a_uiFlags            Combination of SG_GLOB flags.
549        @param a_nReservedSlots     Number of slots in the argv array that
550            should be reserved. In the returned array these slots
551            argv[0] ... argv[a_nReservedSlots-1] will be left empty for
552            the caller to fill in.
553     */
554    CSimpleGlobTempl(unsigned int a_uiFlags = 0, int a_nReservedSlots = 0);
555
556    /*! @brief Deallocate all memory buffers. */
557    ~CSimpleGlobTempl();
558
559    /*! @brief Initialize (or re-initialize) the class in preparation for
560        adding new filespecs.
561
562        All existing files are cleared. Note that allocated memory is only
563        deallocated at object destruction.
564
565        @param a_uiFlags            Combination of SG_GLOB flags.
566        @param a_nReservedSlots     Number of slots in the argv array that
567            should be reserved. In the returned array these slots
568            argv[0] ... argv[a_nReservedSlots-1] will be left empty for
569            the caller to fill in.
570     */
571    int Init(unsigned int a_uiFlags = 0, int a_nReservedSlots = 0);
572
573    /*! @brief Add a new filespec to the glob.
574
575        The filesystem will be immediately scanned for all matching files and
576        directories and they will be added to the glob.
577
578        @param a_pszFileSpec    Filespec to add to the glob.
579
580        @return SG_SUCCESS      Matching files were added to the glob.
581        @return SG_ERR_NOMATCH  Nothing matched the pattern. To ignore this
582                                error compare return value to >= SG_SUCCESS.
583        @return SG_ERR_MEMORY   Out of memory failure.
584        @return SG_ERR_FAILURE  General failure.
585     */
586    int Add(const SOCHAR *a_pszFileSpec);
587
588    /*! @brief Add an array of filespec to the glob.
589
590        The filesystem will be immediately scanned for all matching files and
591        directories in each filespec and they will be added to the glob.
592
593        @param a_nCount         Number of filespec in the array.
594        @param a_rgpszFileSpec  Array of filespec to add to the glob.
595
596        @return SG_SUCCESS      Matching files were added to the glob.
597        @return SG_ERR_NOMATCH  Nothing matched the pattern. To ignore this
598                                error compare return value to >= SG_SUCCESS.
599        @return SG_ERR_MEMORY   Out of memory failure.
600        @return SG_ERR_FAILURE  General failure.
601     */
602    int Add(int a_nCount, const SOCHAR * const * a_rgpszFileSpec);
603
604    /*! @brief Return the number of files in the argv array.
605     */
606    inline int FileCount() const { return m_nArgsLen; }
607
608    /*! @brief Return the full argv array. */
609    inline SOCHAR ** Files() {
610        SetArgvArrayType(POINTERS);
611        return m_rgpArgs;
612    }
613
614    /*! @brief Return the a single file. */
615    inline SOCHAR * File(int n) {
616        SG_ASSERT(n >= 0 && n < m_nArgsLen);
617        return Files()[n];
618    }
619
620private:
621    CSimpleGlobTempl(const CSimpleGlobTempl &); // disabled
622    CSimpleGlobTempl & operator=(const CSimpleGlobTempl &); // disabled
623
624    /*! @brief The argv array has it's members stored as either an offset into
625        the string buffer, or as pointers to their string in the buffer. The
626        offsets are used because if the string buffer is dynamically resized,
627        all pointers into that buffer would become invalid.
628     */
629    enum ARG_ARRAY_TYPE { OFFSETS, POINTERS };
630
631    /*! @brief Change the type of data stored in the argv array. */
632    void SetArgvArrayType(ARG_ARRAY_TYPE a_nNewType);
633
634    /*! @brief Add a filename to the array if it passes all requirements. */
635    int AppendName(const SOCHAR *a_pszFileName, bool a_bIsDir);
636
637    /*! @brief Grow the argv array to the required size. */
638    bool GrowArgvArray(int a_nNewLen);
639
640    /*! @brief Grow the string buffer to the required size. */
641    bool GrowStringBuffer(size_t a_uiMinSize);
642
643    /*! @brief Compare two (possible NULL) strings */
644    static int fileSortCompare(const void *a1, const void *a2);
645
646private:
647    unsigned int        m_uiFlags;
648    ARG_ARRAY_TYPE      m_nArgArrayType;    //!< argv is indexes or pointers
649    SOCHAR **           m_rgpArgs;          //!< argv
650    int                 m_nReservedSlots;   //!< # client slots in argv array
651    int                 m_nArgsSize;        //!< allocated size of array
652    int                 m_nArgsLen;         //!< used length
653    SOCHAR *            m_pBuffer;          //!< argv string buffer
654    size_t              m_uiBufferSize;     //!< allocated size of buffer
655    size_t              m_uiBufferLen;      //!< used length of buffer
656    SOCHAR              m_szPathPrefix[MAX_PATH]; //!< wildcard path prefix
657};
658
659// ---------------------------------------------------------------------------
660//                                  IMPLEMENTATION
661// ---------------------------------------------------------------------------
662
663template<class SOCHAR>
664CSimpleGlobTempl<SOCHAR>::CSimpleGlobTempl(
665    unsigned int    a_uiFlags,
666    int             a_nReservedSlots
667    )
668{
669    m_rgpArgs           = NULL;
670    m_nArgsSize         = 0;
671    m_pBuffer           = NULL;
672    m_uiBufferSize      = 0;
673
674    Init(a_uiFlags, a_nReservedSlots);
675}
676
677template<class SOCHAR>
678CSimpleGlobTempl<SOCHAR>::~CSimpleGlobTempl()
679{
680    if (m_rgpArgs) free(m_rgpArgs);
681    if (m_pBuffer) free(m_pBuffer);
682}
683
684template<class SOCHAR>
685int
686CSimpleGlobTempl<SOCHAR>::Init(
687    unsigned int    a_uiFlags,
688    int             a_nReservedSlots
689    )
690{
691    m_nArgArrayType     = POINTERS;
692    m_uiFlags           = a_uiFlags;
693    m_nArgsLen          = a_nReservedSlots;
694    m_nReservedSlots    = a_nReservedSlots;
695    m_uiBufferLen       = 0;
696
697    if (m_nReservedSlots > 0) {
698        if (!GrowArgvArray(m_nReservedSlots)) {
699            return SG_ERR_MEMORY;
700        }
701        for (int n = 0; n < m_nReservedSlots; ++n) {
702            m_rgpArgs[n] = NULL;
703        }
704    }
705
706    return SG_SUCCESS;
707}
708
709template<class SOCHAR>
710int
711CSimpleGlobTempl<SOCHAR>::Add(
712    const SOCHAR *a_pszFileSpec
713    )
714{
715#ifdef WIN32
716    // Windows FindFirst/FindNext recognizes forward slash as the same as
717    // backward slash and follows the directories. We need to do the same
718    // when calculating the prefix and when we have no wildcards.
719    SOCHAR szFileSpec[MAX_PATH];
720    SimpleGlobUtil::strcpy_s(szFileSpec, MAX_PATH, a_pszFileSpec);
721    const SOCHAR * pszPath = SimpleGlobUtil::strchr(szFileSpec, '/');
722    while (pszPath) {
723        szFileSpec[pszPath - szFileSpec] = SG_PATH_CHAR;
724        pszPath = SimpleGlobUtil::strchr(pszPath + 1, '/');
725    }
726    a_pszFileSpec = szFileSpec;
727#endif
728
729    // if this doesn't contain wildcards then we can just add it directly
730    m_szPathPrefix[0] = 0;
731    if (!SimpleGlobUtil::strchr(a_pszFileSpec, '*') &&
732        !SimpleGlobUtil::strchr(a_pszFileSpec, '?'))
733    {
734        SG_FileType nType = GetFileTypeS(a_pszFileSpec);
735        if (nType == SG_FILETYPE_INVALID) {
736            if (m_uiFlags & SG_GLOB_NOCHECK) {
737                return AppendName(a_pszFileSpec, false);
738            }
739            return SG_ERR_NOMATCH;
740        }
741        return AppendName(a_pszFileSpec, nType == SG_FILETYPE_DIR);
742    }
743
744#ifdef WIN32
745    // Windows doesn't return the directory with the filename, so we need to
746    // extract the path from the search string ourselves and prefix it to the
747    // filename we get back.
748    const SOCHAR * pszFilename =
749        SimpleGlobUtil::strrchr(a_pszFileSpec, SG_PATH_CHAR);
750    if (pszFilename) {
751        SimpleGlobUtil::strcpy_s(m_szPathPrefix, MAX_PATH, a_pszFileSpec);
752        m_szPathPrefix[pszFilename - a_pszFileSpec + 1] = 0;
753    }
754#endif
755
756    // search for the first match on the file
757    int rc = FindFirstFileS(a_pszFileSpec, m_uiFlags);
758    if (rc != SG_SUCCESS) {
759        if (rc == SG_ERR_NOMATCH && (m_uiFlags & SG_GLOB_NOCHECK)) {
760            int ok = AppendName(a_pszFileSpec, false);
761            if (ok != SG_SUCCESS) rc = ok;
762        }
763        return rc;
764    }
765
766    // add it and find all subsequent matches
767    int nError, nStartLen = m_nArgsLen;
768    bool bSuccess;
769    do {
770        nError = AppendName(GetFileNameS((SOCHAR)0), IsDirS((SOCHAR)0));
771        bSuccess = FindNextFileS((SOCHAR)0);
772    }
773    while (nError == SG_SUCCESS && bSuccess);
774    SimpleGlobBase<SOCHAR>::FindDone();
775
776    // sort these files if required
777    if (m_nArgsLen > nStartLen && !(m_uiFlags & SG_GLOB_NOSORT)) {
778        if (m_uiFlags & SG_GLOB_FULLSORT) {
779            nStartLen = m_nReservedSlots;
780        }
781        SetArgvArrayType(POINTERS);
782        qsort(
783            m_rgpArgs + nStartLen,
784            m_nArgsLen - nStartLen,
785            sizeof(m_rgpArgs[0]), fileSortCompare);
786    }
787
788    return nError;
789}
790
791template<class SOCHAR>
792int
793CSimpleGlobTempl<SOCHAR>::Add(
794    int                     a_nCount,
795    const SOCHAR * const *  a_rgpszFileSpec
796    )
797{
798    int nResult;
799    for (int n = 0; n < a_nCount; ++n) {
800        nResult = Add(a_rgpszFileSpec[n]);
801        if (nResult != SG_SUCCESS) {
802            return nResult;
803        }
804    }
805    return SG_SUCCESS;
806}
807
808template<class SOCHAR>
809int
810CSimpleGlobTempl<SOCHAR>::AppendName(
811    const SOCHAR *  a_pszFileName,
812    bool            a_bIsDir
813    )
814{
815    // we need the argv array as offsets in case we resize it
816    SetArgvArrayType(OFFSETS);
817
818    // check for special cases which cause us to ignore this entry
819    if ((m_uiFlags & SG_GLOB_ONLYDIR) && !a_bIsDir) {
820        return SG_SUCCESS;
821    }
822    if ((m_uiFlags & SG_GLOB_ONLYFILE) && a_bIsDir) {
823        return SG_SUCCESS;
824    }
825    if ((m_uiFlags & SG_GLOB_NODOT) && a_bIsDir) {
826        if (a_pszFileName[0] == '.') {
827            if (a_pszFileName[1] == '\0') {
828                return SG_SUCCESS;
829            }
830            if (a_pszFileName[1] == '.' && a_pszFileName[2] == '\0') {
831                return SG_SUCCESS;
832            }
833        }
834    }
835
836    // ensure that we have enough room in the argv array
837    if (!GrowArgvArray(m_nArgsLen + 1)) {
838        return SG_ERR_MEMORY;
839    }
840
841    // ensure that we have enough room in the string buffer (+1 for null)
842    size_t uiPrefixLen = SimpleGlobUtil::strlen(m_szPathPrefix);
843    size_t uiLen = uiPrefixLen + SimpleGlobUtil::strlen(a_pszFileName) + 1;
844    if (a_bIsDir && (m_uiFlags & SG_GLOB_MARK) == SG_GLOB_MARK) {
845        ++uiLen;    // need space for the backslash
846    }
847    if (!GrowStringBuffer(m_uiBufferLen + uiLen)) {
848        return SG_ERR_MEMORY;
849    }
850
851    // add this entry. m_uiBufferLen is offset from beginning of buffer.
852    m_rgpArgs[m_nArgsLen++] = (SOCHAR*)m_uiBufferLen;
853    SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen,
854        m_uiBufferSize - m_uiBufferLen, m_szPathPrefix);
855    SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen + uiPrefixLen,
856        m_uiBufferSize - m_uiBufferLen - uiPrefixLen, a_pszFileName);
857    m_uiBufferLen += uiLen;
858
859    // add the directory slash if desired
860    if (a_bIsDir && (m_uiFlags & SG_GLOB_MARK) == SG_GLOB_MARK) {
861        const static SOCHAR szDirSlash[] = { SG_PATH_CHAR, 0 };
862        SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen - 2,
863            m_uiBufferSize - (m_uiBufferLen - 2), szDirSlash);
864    }
865
866    return SG_SUCCESS;
867}
868
869template<class SOCHAR>
870void
871CSimpleGlobTempl<SOCHAR>::SetArgvArrayType(
872    ARG_ARRAY_TYPE  a_nNewType
873    )
874{
875    if (m_nArgArrayType == a_nNewType) return;
876    if (a_nNewType == POINTERS) {
877        SG_ASSERT(m_nArgArrayType == OFFSETS);
878        for (int n = 0; n < m_nArgsLen; ++n) {
879            m_rgpArgs[n] = (m_rgpArgs[n] == (SOCHAR*)-1) ?
880                NULL : m_pBuffer + (size_t) m_rgpArgs[n];
881        }
882    }
883    else {
884        SG_ASSERT(a_nNewType == OFFSETS);
885        SG_ASSERT(m_nArgArrayType == POINTERS);
886        for (int n = 0; n < m_nArgsLen; ++n) {
887            m_rgpArgs[n] = (m_rgpArgs[n] == NULL) ?
888                (SOCHAR*) -1 : (SOCHAR*) (m_rgpArgs[n] - m_pBuffer);
889        }
890    }
891    m_nArgArrayType = a_nNewType;
892}
893
894template<class SOCHAR>
895bool
896CSimpleGlobTempl<SOCHAR>::GrowArgvArray(
897    int a_nNewLen
898    )
899{
900    if (a_nNewLen >= m_nArgsSize) {
901        static const int SG_ARGV_INITIAL_SIZE = 32;
902        int nNewSize = (m_nArgsSize > 0) ?
903            m_nArgsSize * 2 : SG_ARGV_INITIAL_SIZE;
904        while (a_nNewLen >= nNewSize) {
905            nNewSize *= 2;
906        }
907        void * pNewBuffer = realloc(m_rgpArgs, nNewSize * sizeof(SOCHAR*));
908        if (!pNewBuffer) return false;
909        m_nArgsSize = nNewSize;
910        m_rgpArgs = (SOCHAR**) pNewBuffer;
911    }
912    return true;
913}
914
915template<class SOCHAR>
916bool
917CSimpleGlobTempl<SOCHAR>::GrowStringBuffer(
918    size_t a_uiMinSize
919    )
920{
921    if (a_uiMinSize >= m_uiBufferSize) {
922        static const int SG_BUFFER_INITIAL_SIZE = 1024;
923        size_t uiNewSize = (m_uiBufferSize > 0) ?
924            m_uiBufferSize * 2 : SG_BUFFER_INITIAL_SIZE;
925        while (a_uiMinSize >= uiNewSize) {
926            uiNewSize *= 2;
927        }
928        void * pNewBuffer = realloc(m_pBuffer, uiNewSize * sizeof(SOCHAR));
929        if (!pNewBuffer) return false;
930        m_uiBufferSize = uiNewSize;
931        m_pBuffer = (SOCHAR*) pNewBuffer;
932    }
933    return true;
934}
935
936template<class SOCHAR>
937int
938CSimpleGlobTempl<SOCHAR>::fileSortCompare(
939    const void *a1,
940    const void *a2
941    )
942{
943    const SOCHAR * s1 = *(const SOCHAR **)a1;
944    const SOCHAR * s2 = *(const SOCHAR **)a2;
945    if (s1 && s2) {
946        return SimpleGlobUtil::strcasecmp(s1, s2);
947    }
948    // NULL sorts first
949    return s1 == s2 ? 0 : (s1 ? 1 : -1);
950}
951
952// ---------------------------------------------------------------------------
953//                                  TYPE DEFINITIONS
954// ---------------------------------------------------------------------------
955
956/*! @brief ASCII/MBCS version of CSimpleGlob */
957typedef CSimpleGlobTempl<char>    CSimpleGlobA;
958
959/*! @brief wchar_t version of CSimpleGlob */
960typedef CSimpleGlobTempl<wchar_t> CSimpleGlobW;
961
962#if SG_HAVE_ICU
963/*! @brief UChar version of CSimpleGlob */
964typedef CSimpleGlobTempl<UChar> CSimpleGlobU;
965#endif
966
967#ifdef _UNICODE
968/*! @brief TCHAR version dependent on if _UNICODE is defined */
969# if SG_HAVE_ICU
970#  define CSimpleGlob CSimpleGlobU
971# else
972#  define CSimpleGlob CSimpleGlobW
973# endif
974#else
975/*! @brief TCHAR version dependent on if _UNICODE is defined */
976# define CSimpleGlob CSimpleGlobA
977#endif
978
979#endif // INCLUDED_SimpleGlob
980