An update will be applied January 25th, between 12PM and 1:00PM EST (UTC -5:00). The site may be slow during that time.

Glob.cxx 14.8 KB
Newer Older
1
2
3
/*============================================================================
  KWSys - Kitware System Library
  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
Andy Cedilnik's avatar
Andy Cedilnik committed
4

5
6
  Distributed under the OSI-approved BSD License (the "License");
  see accompanying file Copyright.txt for details.
Andy Cedilnik's avatar
Andy Cedilnik committed
7

8
9
10
11
  This software is distributed WITHOUT ANY WARRANTY; without even the
  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the License for more information.
============================================================================*/
Andy Cedilnik's avatar
Andy Cedilnik committed
12
13
14
15
16
17
18
19
20
21
#include "kwsysPrivate.h"
#include KWSYS_HEADER(Glob.hxx)

#include KWSYS_HEADER(Configure.hxx)

#include KWSYS_HEADER(RegularExpression.hxx)
#include KWSYS_HEADER(SystemTools.hxx)
#include KWSYS_HEADER(Directory.hxx)
#include KWSYS_HEADER(stl/string)
#include KWSYS_HEADER(stl/vector)
22
#include KWSYS_HEADER(stl/algorithm)
Andy Cedilnik's avatar
Andy Cedilnik committed
23
24
25
26
27

// Work-around CMake dependency scanning limitation.  This must
// duplicate the above list of headers.
#if 0
# include "Glob.hxx.in"
Bill Hoffman's avatar
Bill Hoffman committed
28
# include "Directory.hxx.in"
Andy Cedilnik's avatar
Andy Cedilnik committed
29
30
31
32
33
# include "Configure.hxx.in"
# include "RegularExpression.hxx.in"
# include "SystemTools.hxx.in"
# include "kwsys_stl.hxx.in"
# include "kwsys_stl_string.hxx.in"
34
35
# include "kwsys_stl_vector.hxx.in"
# include "kwsys_stl_algorithm.hxx.in"
Andy Cedilnik's avatar
Andy Cedilnik committed
36
37
38
39
#endif

#include <ctype.h>
#include <stdio.h>
40
#include <string.h>
Andy Cedilnik's avatar
Andy Cedilnik committed
41
42
namespace KWSYS_NAMESPACE
{
43
#if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
44
45
// On Windows and apple, no difference between lower and upper case
# define KWSYS_GLOB_CASE_INDEPENDENT
Andy Cedilnik's avatar
Andy Cedilnik committed
46
47
#endif

48
49
50
#if defined(_WIN32) || defined(__CYGWIN__)
// Handle network paths
# define KWSYS_GLOB_SUPPORT_NETWORK_PATHS
Andy Cedilnik's avatar
Andy Cedilnik committed
51
52
53
54
55
56
#endif

//----------------------------------------------------------------------------
class GlobInternals
{
public:
57
58
  kwsys_stl::vector<kwsys_stl::string> Files;
  kwsys_stl::vector<kwsys::RegularExpression> Expressions;
Andy Cedilnik's avatar
Andy Cedilnik committed
59
60
61
62
63
};

//----------------------------------------------------------------------------
Glob::Glob()
{
64
65
66
  this->Internals = new GlobInternals;
  this->Recurse = false;
  this->Relative = "";
67
68
69
70

  this->RecurseThroughSymlinks = true;
    // RecurseThroughSymlinks is true by default for backwards compatibility,
    // not because it's a good idea...
71
  this->FollowedSymlinkCount = 0;
72
73
74
75

  // Keep separate variables for directory listing for back compatibility
  this->ListDirs = true;
  this->RecurseListDirs = false;
Andy Cedilnik's avatar
Andy Cedilnik committed
76
77
78
79
80
}

//----------------------------------------------------------------------------
Glob::~Glob()
{
81
  delete this->Internals;
Andy Cedilnik's avatar
Andy Cedilnik committed
82
83
84
}

//----------------------------------------------------------------------------
85
kwsys_stl::vector<kwsys_stl::string>& Glob::GetFiles()
Andy Cedilnik's avatar
Andy Cedilnik committed
86
{
87
  return this->Internals->Files;
Andy Cedilnik's avatar
Andy Cedilnik committed
88
89
90
}

//----------------------------------------------------------------------------
91
kwsys_stl::string Glob::PatternToRegex(const kwsys_stl::string& pattern,
92
93
                                       bool require_whole_string,
                                       bool preserve_case)
Andy Cedilnik's avatar
Andy Cedilnik committed
94
{
95
96
97
98
99
100
  // Incrementally build the regular expression from the pattern.
  kwsys_stl::string regex = require_whole_string? "^" : "";
  kwsys_stl::string::const_iterator pattern_first = pattern.begin();
  kwsys_stl::string::const_iterator pattern_last = pattern.end();
  for(kwsys_stl::string::const_iterator i = pattern_first;
      i != pattern_last; ++i)
Andy Cedilnik's avatar
Andy Cedilnik committed
101
    {
102
103
    int c = *i;
    if(c == '*')
Andy Cedilnik's avatar
Andy Cedilnik committed
104
      {
105
      // A '*' (not between brackets) matches any string.
106
107
108
109
      // We modify this to not match slashes since the orignal glob
      // pattern documentation was meant for matching file name
      // components separated by slashes.
      regex += "[^/]*";
Andy Cedilnik's avatar
Andy Cedilnik committed
110
      }
111
    else if(c == '?')
Andy Cedilnik's avatar
Andy Cedilnik committed
112
      {
113
      // A '?' (not between brackets) matches any single character.
114
115
116
117
      // We modify this to not match slashes since the orignal glob
      // pattern documentation was meant for matching file name
      // components separated by slashes.
      regex += "[^/]";
Andy Cedilnik's avatar
Andy Cedilnik committed
118
      }
119
    else if(c == '[')
Andy Cedilnik's avatar
Andy Cedilnik committed
120
      {
121
122
123
124
125
126
127
128
      // Parse out the bracket expression.  It begins just after the
      // opening character.
      kwsys_stl::string::const_iterator bracket_first = i+1;
      kwsys_stl::string::const_iterator bracket_last = bracket_first;

      // The first character may be complementation '!' or '^'.
      if(bracket_last != pattern_last &&
         (*bracket_last == '!' || *bracket_last == '^'))
Andy Cedilnik's avatar
Andy Cedilnik committed
129
        {
130
        ++bracket_last;
Andy Cedilnik's avatar
Andy Cedilnik committed
131
        }
132
133
134
135

      // If the next character is a ']' it is included in the brackets
      // because the bracket string may not be empty.
      if(bracket_last != pattern_last && *bracket_last == ']')
Andy Cedilnik's avatar
Andy Cedilnik committed
136
        {
137
        ++bracket_last;
138
        }
139
140
141

      // Search for the closing ']'.
      while(bracket_last != pattern_last && *bracket_last != ']')
Andy Cedilnik's avatar
Andy Cedilnik committed
142
        {
143
        ++bracket_last;
Andy Cedilnik's avatar
Andy Cedilnik committed
144
        }
145
146
147

      // Check whether we have a complete bracket string.
      if(bracket_last == pattern_last)
Andy Cedilnik's avatar
Andy Cedilnik committed
148
        {
149
150
151
        // The bracket string did not end, so it was opened simply by
        // a '[' that is supposed to be matched literally.
        regex += "\\[";
Andy Cedilnik's avatar
Andy Cedilnik committed
152
153
154
        }
      else
        {
155
156
157
158
159
160
161
162
        // Convert the bracket string to its regex equivalent.
        kwsys_stl::string::const_iterator k = bracket_first;

        // Open the regex block.
        regex += "[";

        // A regex range complement uses '^' instead of '!'.
        if(k != bracket_last && *k == '!')
Andy Cedilnik's avatar
Andy Cedilnik committed
163
          {
164
165
          regex += "^";
          ++k;
Andy Cedilnik's avatar
Andy Cedilnik committed
166
          }
167
168
169

        // Convert the remaining characters.
        for(; k != bracket_last; ++k)
Andy Cedilnik's avatar
Andy Cedilnik committed
170
          {
171
172
173
174
175
176
177
178
          // Backslashes must be escaped.
          if(*k == '\\')
            {
            regex += "\\";
            }

          // Store this character.
          regex += *k;
Andy Cedilnik's avatar
Andy Cedilnik committed
179
          }
180
181
182
183
184
185

        // Close the regex block.
        regex += "]";

        // Jump to the end of the bracket string.
        i = bracket_last;
Andy Cedilnik's avatar
Andy Cedilnik committed
186
187
188
189
        }
      }
    else
      {
190
191
192
193
194
195
196
197
198
199
200
201
202
203
      // A single character matches itself.
      int ch = c;
      if(!(('a' <= ch && ch <= 'z') ||
           ('A' <= ch && ch <= 'Z') ||
           ('0' <= ch && ch <= '9')))
        {
        // Escape the non-alphanumeric character.
        regex += "\\";
        }
#if defined(KWSYS_GLOB_CASE_INDEPENDENT)
      else
        {
        // On case-insensitive systems file names are converted to lower
        // case before matching.
204
205
206
207
        if(!preserve_case)
          {
          ch = tolower(ch);
          }
208
209
        }
#endif
210
      (void)preserve_case;
211
212
      // Store the character.
      regex.append(1, static_cast<char>(ch));
Andy Cedilnik's avatar
Andy Cedilnik committed
213
214
      }
    }
215
216
217
218
219
220

  if(require_whole_string)
    {
    regex += "$";
    }
  return regex;
Andy Cedilnik's avatar
Andy Cedilnik committed
221
222
223
}

//----------------------------------------------------------------------------
224
225
bool Glob::RecurseDirectory(kwsys_stl::string::size_type start,
  const kwsys_stl::string& dir, GlobMessages* messages)
Andy Cedilnik's avatar
Andy Cedilnik committed
226
{
227
  kwsys::Directory d;
228
  if ( !d.Load(dir) )
Andy Cedilnik's avatar
Andy Cedilnik committed
229
    {
230
    return true;
Andy Cedilnik's avatar
Andy Cedilnik committed
231
232
    }
  unsigned long cc;
233
234
  kwsys_stl::string realname;
  kwsys_stl::string fname;
Andy Cedilnik's avatar
Andy Cedilnik committed
235
236
237
  for ( cc = 0; cc < d.GetNumberOfFiles(); cc ++ )
    {
    fname = d.GetFile(cc);
238
    if ( fname == "." || fname == ".." )
Andy Cedilnik's avatar
Andy Cedilnik committed
239
240
241
242
243
244
245
246
247
248
249
250
251
      {
      continue;
      }

    if ( start == 0 )
      {
      realname = dir + fname;
      }
    else
      {
      realname = dir + "/" + fname;
      }

252
#if defined( KWSYS_GLOB_CASE_INDEPENDENT )
Andy Cedilnik's avatar
Andy Cedilnik committed
253
    // On Windows and apple, no difference between lower and upper case
254
    fname = kwsys::SystemTools::LowerCase(fname);
Andy Cedilnik's avatar
Andy Cedilnik committed
255
256
#endif

257
258
    bool isDir = kwsys::SystemTools::FileIsDirectory(realname);
    bool isSymLink = kwsys::SystemTools::FileIsSymlink(realname);
259

260
    if ( isDir && (!isSymLink || this->RecurseThroughSymlinks) )
Andy Cedilnik's avatar
Andy Cedilnik committed
261
      {
262
      if (isSymLink)
Andy Cedilnik's avatar
Andy Cedilnik committed
263
        {
264
        ++this->FollowedSymlinkCount;
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
        kwsys_stl::string realPathErrorMessage;
        kwsys_stl::string canonicalPath(SystemTools::GetRealPath(dir,
            &realPathErrorMessage));

        if(!realPathErrorMessage.empty())
          {
          if(messages)
            {
            messages->push_back(Message(
                Glob::error, "Canonical path generation from path '"
                + dir + "' failed! Reason: '" + realPathErrorMessage + "'"));
            }
          return false;
          }

        if(kwsys_stl::find(this->VisitedSymlinks.begin(),
            this->VisitedSymlinks.end(),
            canonicalPath) == this->VisitedSymlinks.end())
          {
284
285
286
287
288
289
          if(this->RecurseListDirs)
            {
            // symlinks are treated as directories
            this->AddFile(this->Internals->Files, realname);
            }

290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
          this->VisitedSymlinks.push_back(canonicalPath);
          if(!this->RecurseDirectory(start+1, realname, messages))
            {
            this->VisitedSymlinks.pop_back();

            return false;
            }
          this->VisitedSymlinks.pop_back();
          }
        // else we have already visited this symlink - prevent cyclic recursion
        else if(messages)
          {
          kwsys_stl::string message;
          for(kwsys_stl::vector<kwsys_stl::string>::const_iterator
                pathIt = kwsys_stl::find(this->VisitedSymlinks.begin(),
                                         this->VisitedSymlinks.end(),
                                         canonicalPath);
              pathIt != this->VisitedSymlinks.end(); ++pathIt)
            {
            message += *pathIt + "\n";
            }
          message += canonicalPath + "/" + fname;
          messages->push_back(Message(Glob::cyclicRecursion, message));
          }
        }
      else
        {
317
318
319
320
        if(this->RecurseListDirs)
          {
          this->AddFile(this->Internals->Files, realname);
          }
321
322
323
324
        if(!this->RecurseDirectory(start+1, realname, messages))
          {
          return false;
          }
Andy Cedilnik's avatar
Andy Cedilnik committed
325
326
        }
      }
327
    else
Andy Cedilnik's avatar
Andy Cedilnik committed
328
      {
329
      if ( !this->Internals->Expressions.empty() &&
330
           this->Internals->Expressions.rbegin()->find(fname) )
331
        {
332
        this->AddFile(this->Internals->Files, realname);
333
        }
Andy Cedilnik's avatar
Andy Cedilnik committed
334
335
      }
    }
336
337

  return true;
Andy Cedilnik's avatar
Andy Cedilnik committed
338
339
340
}

//----------------------------------------------------------------------------
341
void Glob::ProcessDirectory(kwsys_stl::string::size_type start,
342
  const kwsys_stl::string& dir, GlobMessages* messages)
Andy Cedilnik's avatar
Andy Cedilnik committed
343
{
344
  //kwsys_ios::cout << "ProcessDirectory: " << dir << kwsys_ios::endl;
345
346
  bool last = ( start == this->Internals->Expressions.size()-1 );
  if ( last && this->Recurse )
Andy Cedilnik's avatar
Andy Cedilnik committed
347
    {
348
    this->RecurseDirectory(start, dir, messages);
Andy Cedilnik's avatar
Andy Cedilnik committed
349
350
    return;
    }
Alexander Neundorf's avatar
   
Alexander Neundorf committed
351
352
353
354
355
356

  if ( start >= this->Internals->Expressions.size() )
    {
    return;
    }

357
  kwsys::Directory d;
358
  if ( !d.Load(dir) )
Andy Cedilnik's avatar
Andy Cedilnik committed
359
360
361
362
    {
    return;
    }
  unsigned long cc;
363
364
  kwsys_stl::string realname;
  kwsys_stl::string fname;
Andy Cedilnik's avatar
Andy Cedilnik committed
365
366
367
  for ( cc = 0; cc < d.GetNumberOfFiles(); cc ++ )
    {
    fname = d.GetFile(cc);
368
    if ( fname == "." || fname == ".." )
Andy Cedilnik's avatar
Andy Cedilnik committed
369
370
371
372
373
374
375
376
377
378
379
380
381
      {
      continue;
      }

    if ( start == 0 )
      {
      realname = dir + fname;
      }
    else
      {
      realname = dir + "/" + fname;
      }

382
383
#if defined(KWSYS_GLOB_CASE_INDEPENDENT)
    // On case-insensitive file systems convert to lower case for matching.
384
    fname = kwsys::SystemTools::LowerCase(fname);
Andy Cedilnik's avatar
Andy Cedilnik committed
385
386
#endif

387
    //kwsys_ios::cout << "Look at file: " << fname << kwsys_ios::endl;
388
389
    //kwsys_ios::cout << "Match: "
    // << this->Internals->TextExpressions[start].c_str() << kwsys_ios::endl;
Domen Vrankar's avatar
Domen Vrankar committed
390
    //kwsys_ios::cout << "Real name: " << realname << kwsys_ios::endl;
Andy Cedilnik's avatar
Andy Cedilnik committed
391

392
393
394
    if( (!last && !kwsys::SystemTools::FileIsDirectory(realname))
      || (!this->ListDirs && last &&
          kwsys::SystemTools::FileIsDirectory(realname)) )
Andy Cedilnik's avatar
Andy Cedilnik committed
395
396
397
398
      {
      continue;
      }

399
    if ( this->Internals->Expressions[start].find(fname) )
Andy Cedilnik's avatar
Andy Cedilnik committed
400
401
402
      {
      if ( last )
        {
403
        this->AddFile(this->Internals->Files, realname);
Andy Cedilnik's avatar
Andy Cedilnik committed
404
405
406
        }
      else
        {
407
        this->ProcessDirectory(start+1, realname, messages);
Andy Cedilnik's avatar
Andy Cedilnik committed
408
409
410
411
412
413
        }
      }
    }
}

//----------------------------------------------------------------------------
414
bool Glob::FindFiles(const kwsys_stl::string& inexpr, GlobMessages* messages)
Andy Cedilnik's avatar
Andy Cedilnik committed
415
{
416
417
418
  kwsys_stl::string cexpr;
  kwsys_stl::string::size_type cc;
  kwsys_stl::string expr = inexpr;
Andy Cedilnik's avatar
Andy Cedilnik committed
419

420
421
  this->Internals->Expressions.clear();
  this->Internals->Files.clear();
Andy Cedilnik's avatar
Andy Cedilnik committed
422

423
  if ( !kwsys::SystemTools::FileIsFullPath(expr) )
Andy Cedilnik's avatar
Andy Cedilnik committed
424
    {
425
    expr = kwsys::SystemTools::GetCurrentWorkingDirectory();
Andy Cedilnik's avatar
Andy Cedilnik committed
426
427
    expr += "/" + inexpr;
    }
428
  kwsys_stl::string fexpr = expr;
Andy Cedilnik's avatar
Andy Cedilnik committed
429

Francois Bertel's avatar
Francois Bertel committed
430
431
  kwsys_stl::string::size_type skip = 0;
  kwsys_stl::string::size_type last_slash = 0;
Andy Cedilnik's avatar
Andy Cedilnik committed
432
433
434
435
  for ( cc = 0; cc < expr.size(); cc ++ )
    {
    if ( cc > 0 && expr[cc] == '/' && expr[cc-1] != '\\' )
      {
Francois Bertel's avatar
Francois Bertel committed
436
      last_slash = cc;
Andy Cedilnik's avatar
Andy Cedilnik committed
437
      }
438
    if ( cc > 0 &&
Andy Cedilnik's avatar
Andy Cedilnik committed
439
440
441
442
443
444
445
446
      (expr[cc] == '[' || expr[cc] == '?' || expr[cc] == '*') &&
      expr[cc-1] != '\\' )
      {
      break;
      }
    }
  if ( last_slash > 0 )
    {
447
    //kwsys_ios::cout << "I can skip: " << fexpr.substr(0, last_slash)
448
    // << kwsys_ios::endl;
Andy Cedilnik's avatar
Andy Cedilnik committed
449
450
451
452
    skip = last_slash;
    }
  if ( skip == 0 )
    {
453
#if defined( KWSYS_GLOB_SUPPORT_NETWORK_PATHS )
Andy Cedilnik's avatar
Andy Cedilnik committed
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
    // Handle network paths
    if ( expr[0] == '/' && expr[1] == '/' )
      {
      int cnt = 0;
      for ( cc = 2; cc < expr.size(); cc ++ )
        {
        if ( expr[cc] == '/' )
          {
          cnt ++;
          if ( cnt == 2 )
            {
            break;
            }
          }
        }
469
      skip = int(cc + 1);
Andy Cedilnik's avatar
Andy Cedilnik committed
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
      }
    else
#endif
      // Handle drive letters on Windows
      if ( expr[1] == ':' && expr[0] != '/' )
        {
        skip = 2;
        }
    }

  if ( skip > 0 )
    {
    expr = expr.substr(skip);
    }

  cexpr = "";
  for ( cc = 0; cc < expr.size(); cc ++ )
    {
    int ch = expr[cc];
    if ( ch == '/' )
      {
491
      if ( !cexpr.empty() )
Andy Cedilnik's avatar
Andy Cedilnik committed
492
        {
493
        this->AddExpression(cexpr);
Andy Cedilnik's avatar
Andy Cedilnik committed
494
495
496
497
498
        }
      cexpr = "";
      }
    else
      {
499
      cexpr.append(1, static_cast<char>(ch));
Andy Cedilnik's avatar
Andy Cedilnik committed
500
501
      }
    }
502
  if ( !cexpr.empty() )
Andy Cedilnik's avatar
Andy Cedilnik committed
503
    {
504
    this->AddExpression(cexpr);
Andy Cedilnik's avatar
Andy Cedilnik committed
505
506
507
508
509
    }

  // Handle network paths
  if ( skip > 0 )
    {
510
    this->ProcessDirectory(0, fexpr.substr(0, skip) + "/", messages);
Andy Cedilnik's avatar
Andy Cedilnik committed
511
512
513
    }
  else
    {
514
    this->ProcessDirectory(0, "/", messages);
Andy Cedilnik's avatar
Andy Cedilnik committed
515
516
517
518
    }
  return true;
}

519
//----------------------------------------------------------------------------
520
void Glob::AddExpression(const kwsys_stl::string& expr)
Andy Cedilnik's avatar
Andy Cedilnik committed
521
{
522
  this->Internals->Expressions.push_back(
523
    kwsys::RegularExpression(
524
      this->PatternToRegex(expr)));
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
}

//----------------------------------------------------------------------------
void Glob::SetRelative(const char* dir)
{
  if ( !dir )
    {
    this->Relative = "";
    return;
    }
  this->Relative = dir;
}

//----------------------------------------------------------------------------
const char* Glob::GetRelative()
{
  if ( this->Relative.empty() )
    {
    return 0;
    }
  return this->Relative.c_str();
}

//----------------------------------------------------------------------------
549
void Glob::AddFile(kwsys_stl::vector<kwsys_stl::string>& files, const kwsys_stl::string& file)
550
551
552
{
  if ( !this->Relative.empty() )
    {
553
    files.push_back(kwsys::SystemTools::RelativePath(this->Relative, file));
554
555
556
557
558
    }
  else
    {
    files.push_back(file);
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
559
560
561
562
}

} // namespace KWSYS_NAMESPACE