Glob.cxx 13.2 KB
Newer Older
1 2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
Andy Cedilnik's avatar
Andy Cedilnik committed
3 4 5 6 7 8 9 10 11 12 13 14
#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)

// Work-around CMake dependency scanning limitation.  This must
// duplicate the above list of headers.
#if 0
15 16 17 18 19
#include "Configure.hxx.in"
#include "Directory.hxx.in"
#include "Glob.hxx.in"
#include "RegularExpression.hxx.in"
#include "SystemTools.hxx.in"
Andy Cedilnik's avatar
Andy Cedilnik committed
20 21
#endif

22
#include <algorithm>
Brad King's avatar
Brad King committed
23 24 25
#include <string>
#include <vector>

Andy Cedilnik's avatar
Andy Cedilnik committed
26 27
#include <ctype.h>
#include <stdio.h>
28
#include <string.h>
29
namespace KWSYS_NAMESPACE {
30
#if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
31
// On Windows and apple, no difference between lower and upper case
32
#define KWSYS_GLOB_CASE_INDEPENDENT
Andy Cedilnik's avatar
Andy Cedilnik committed
33 34
#endif

35 36
#if defined(_WIN32) || defined(__CYGWIN__)
// Handle network paths
37
#define KWSYS_GLOB_SUPPORT_NETWORK_PATHS
Andy Cedilnik's avatar
Andy Cedilnik committed
38 39 40 41 42 43
#endif

//----------------------------------------------------------------------------
class GlobInternals
{
public:
Brad King's avatar
Brad King committed
44 45
  std::vector<std::string> Files;
  std::vector<kwsys::RegularExpression> Expressions;
Andy Cedilnik's avatar
Andy Cedilnik committed
46 47 48 49 50
};

//----------------------------------------------------------------------------
Glob::Glob()
{
51 52 53
  this->Internals = new GlobInternals;
  this->Recurse = false;
  this->Relative = "";
54 55

  this->RecurseThroughSymlinks = true;
56 57
  // RecurseThroughSymlinks is true by default for backwards compatibility,
  // not because it's a good idea...
58
  this->FollowedSymlinkCount = 0;
59 60 61 62

  // Keep separate variables for directory listing for back compatibility
  this->ListDirs = true;
  this->RecurseListDirs = false;
Andy Cedilnik's avatar
Andy Cedilnik committed
63 64 65 66 67
}

//----------------------------------------------------------------------------
Glob::~Glob()
{
68
  delete this->Internals;
Andy Cedilnik's avatar
Andy Cedilnik committed
69 70 71
}

//----------------------------------------------------------------------------
Brad King's avatar
Brad King committed
72
std::vector<std::string>& Glob::GetFiles()
Andy Cedilnik's avatar
Andy Cedilnik committed
73
{
74
  return this->Internals->Files;
Andy Cedilnik's avatar
Andy Cedilnik committed
75 76 77
}

//----------------------------------------------------------------------------
Brad King's avatar
Brad King committed
78
std::string Glob::PatternToRegex(const std::string& pattern,
79
                                 bool require_whole_string, bool preserve_case)
Andy Cedilnik's avatar
Andy Cedilnik committed
80
{
81
  // Incrementally build the regular expression from the pattern.
82
  std::string regex = require_whole_string ? "^" : "";
Brad King's avatar
Brad King committed
83 84
  std::string::const_iterator pattern_first = pattern.begin();
  std::string::const_iterator pattern_last = pattern.end();
85
  for (std::string::const_iterator i = pattern_first; i != pattern_last; ++i) {
86
    int c = *i;
87
    if (c == '*') {
88
      // A '*' (not between brackets) matches any string.
89 90 91 92
      // We modify this to not match slashes since the orignal glob
      // pattern documentation was meant for matching file name
      // components separated by slashes.
      regex += "[^/]*";
93
    } else if (c == '?') {
94
      // A '?' (not between brackets) matches any single character.
95 96 97 98
      // We modify this to not match slashes since the orignal glob
      // pattern documentation was meant for matching file name
      // components separated by slashes.
      regex += "[^/]";
99
    } else if (c == '[') {
100 101
      // Parse out the bracket expression.  It begins just after the
      // opening character.
102
      std::string::const_iterator bracket_first = i + 1;
Brad King's avatar
Brad King committed
103
      std::string::const_iterator bracket_last = bracket_first;
104 105

      // The first character may be complementation '!' or '^'.
106 107
      if (bracket_last != pattern_last &&
          (*bracket_last == '!' || *bracket_last == '^')) {
108
        ++bracket_last;
109
      }
110 111 112

      // If the next character is a ']' it is included in the brackets
      // because the bracket string may not be empty.
113
      if (bracket_last != pattern_last && *bracket_last == ']') {
114
        ++bracket_last;
115
      }
116 117

      // Search for the closing ']'.
118
      while (bracket_last != pattern_last && *bracket_last != ']') {
119
        ++bracket_last;
120
      }
121 122

      // Check whether we have a complete bracket string.
123
      if (bracket_last == pattern_last) {
124 125 126
        // The bracket string did not end, so it was opened simply by
        // a '[' that is supposed to be matched literally.
        regex += "\\[";
127
      } else {
128
        // Convert the bracket string to its regex equivalent.
Brad King's avatar
Brad King committed
129
        std::string::const_iterator k = bracket_first;
130 131 132 133 134

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

        // A regex range complement uses '^' instead of '!'.
135
        if (k != bracket_last && *k == '!') {
136 137
          regex += "^";
          ++k;
138
        }
139 140

        // Convert the remaining characters.
141
        for (; k != bracket_last; ++k) {
142
          // Backslashes must be escaped.
143
          if (*k == '\\') {
144
            regex += "\\";
145
          }
146 147 148

          // Store this character.
          regex += *k;
149
        }
150 151 152 153 154 155

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

        // Jump to the end of the bracket string.
        i = bracket_last;
Andy Cedilnik's avatar
Andy Cedilnik committed
156
      }
157
    } else {
158 159
      // A single character matches itself.
      int ch = c;
160 161
      if (!(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') ||
            ('0' <= ch && ch <= '9'))) {
162 163
        // Escape the non-alphanumeric character.
        regex += "\\";
164
      }
165
#if defined(KWSYS_GLOB_CASE_INDEPENDENT)
166
      else {
167 168
        // On case-insensitive systems file names are converted to lower
        // case before matching.
169
        if (!preserve_case) {
170
          ch = tolower(ch);
171
        }
172
      }
173
#endif
174
      (void)preserve_case;
175 176
      // Store the character.
      regex.append(1, static_cast<char>(ch));
Andy Cedilnik's avatar
Andy Cedilnik committed
177
    }
178
  }
179

180
  if (require_whole_string) {
181
    regex += "$";
182
  }
183
  return regex;
Andy Cedilnik's avatar
Andy Cedilnik committed
184 185 186
}

//----------------------------------------------------------------------------
Brad King's avatar
Brad King committed
187
bool Glob::RecurseDirectory(std::string::size_type start,
188
                            const std::string& dir, GlobMessages* messages)
Andy Cedilnik's avatar
Andy Cedilnik committed
189
{
190
  kwsys::Directory d;
191
  if (!d.Load(dir)) {
192
    return true;
193
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
194
  unsigned long cc;
Brad King's avatar
Brad King committed
195 196
  std::string realname;
  std::string fname;
197
  for (cc = 0; cc < d.GetNumberOfFiles(); cc++) {
Andy Cedilnik's avatar
Andy Cedilnik committed
198
    fname = d.GetFile(cc);
199
    if (fname == "." || fname == "..") {
Andy Cedilnik's avatar
Andy Cedilnik committed
200
      continue;
201
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
202

203
    if (start == 0) {
Andy Cedilnik's avatar
Andy Cedilnik committed
204
      realname = dir + fname;
205
    } else {
Andy Cedilnik's avatar
Andy Cedilnik committed
206
      realname = dir + "/" + fname;
207
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
208

209
#if defined(KWSYS_GLOB_CASE_INDEPENDENT)
Andy Cedilnik's avatar
Andy Cedilnik committed
210
    // On Windows and apple, no difference between lower and upper case
211
    fname = kwsys::SystemTools::LowerCase(fname);
Andy Cedilnik's avatar
Andy Cedilnik committed
212 213
#endif

214 215
    bool isDir = kwsys::SystemTools::FileIsDirectory(realname);
    bool isSymLink = kwsys::SystemTools::FileIsSymlink(realname);
216

217 218
    if (isDir && (!isSymLink || this->RecurseThroughSymlinks)) {
      if (isSymLink) {
219
        ++this->FollowedSymlinkCount;
Brad King's avatar
Brad King committed
220
        std::string realPathErrorMessage;
221 222
        std::string canonicalPath(
          SystemTools::GetRealPath(dir, &realPathErrorMessage));
223

224 225
        if (!realPathErrorMessage.empty()) {
          if (messages) {
226
            messages->push_back(Message(
227 228
              Glob::error, "Canonical path generation from path '" + dir +
                "' failed! Reason: '" + realPathErrorMessage + "'"));
229
          }
230 231
          return false;
        }
232

233 234 235 236
        if (std::find(this->VisitedSymlinks.begin(),
                      this->VisitedSymlinks.end(),
                      canonicalPath) == this->VisitedSymlinks.end()) {
          if (this->RecurseListDirs) {
237 238
            // symlinks are treated as directories
            this->AddFile(this->Internals->Files, realname);
239
          }
240

241
          this->VisitedSymlinks.push_back(canonicalPath);
242
          if (!this->RecurseDirectory(start + 1, realname, messages)) {
243 244 245 246
            this->VisitedSymlinks.pop_back();

            return false;
          }
247 248
          this->VisitedSymlinks.pop_back();
        }
249
        // else we have already visited this symlink - prevent cyclic recursion
250
        else if (messages) {
Brad King's avatar
Brad King committed
251
          std::string message;
252 253 254 255
          for (std::vector<std::string>::const_iterator pathIt =
                 std::find(this->VisitedSymlinks.begin(),
                           this->VisitedSymlinks.end(), canonicalPath);
               pathIt != this->VisitedSymlinks.end(); ++pathIt) {
256
            message += *pathIt + "\n";
257
          }
258 259 260
          message += canonicalPath + "/" + fname;
          messages->push_back(Message(Glob::cyclicRecursion, message));
        }
261 262
      } else {
        if (this->RecurseListDirs) {
263
          this->AddFile(this->Internals->Files, realname);
264 265
        }
        if (!this->RecurseDirectory(start + 1, realname, messages)) {
266
          return false;
Andy Cedilnik's avatar
Andy Cedilnik committed
267 268
        }
      }
269 270 271
    } else {
      if (!this->Internals->Expressions.empty() &&
          this->Internals->Expressions.rbegin()->find(fname)) {
272
        this->AddFile(this->Internals->Files, realname);
Andy Cedilnik's avatar
Andy Cedilnik committed
273 274
      }
    }
275
  }
276 277

  return true;
Andy Cedilnik's avatar
Andy Cedilnik committed
278 279 280
}

//----------------------------------------------------------------------------
Brad King's avatar
Brad King committed
281
void Glob::ProcessDirectory(std::string::size_type start,
282
                            const std::string& dir, GlobMessages* messages)
Andy Cedilnik's avatar
Andy Cedilnik committed
283
{
284 285 286
  // std::cout << "ProcessDirectory: " << dir << std::endl;
  bool last = (start == this->Internals->Expressions.size() - 1);
  if (last && this->Recurse) {
287
    this->RecurseDirectory(start, dir, messages);
Andy Cedilnik's avatar
Andy Cedilnik committed
288
    return;
289
  }
290

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

295
  kwsys::Directory d;
296
  if (!d.Load(dir)) {
Andy Cedilnik's avatar
Andy Cedilnik committed
297
    return;
298
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
299
  unsigned long cc;
Brad King's avatar
Brad King committed
300 301
  std::string realname;
  std::string fname;
302
  for (cc = 0; cc < d.GetNumberOfFiles(); cc++) {
Andy Cedilnik's avatar
Andy Cedilnik committed
303
    fname = d.GetFile(cc);
304
    if (fname == "." || fname == "..") {
Andy Cedilnik's avatar
Andy Cedilnik committed
305
      continue;
306
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
307

308
    if (start == 0) {
Andy Cedilnik's avatar
Andy Cedilnik committed
309
      realname = dir + fname;
310
    } else {
Andy Cedilnik's avatar
Andy Cedilnik committed
311
      realname = dir + "/" + fname;
312
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
313

314 315
#if defined(KWSYS_GLOB_CASE_INDEPENDENT)
    // On case-insensitive file systems convert to lower case for matching.
316
    fname = kwsys::SystemTools::LowerCase(fname);
Andy Cedilnik's avatar
Andy Cedilnik committed
317 318
#endif

319 320
    // std::cout << "Look at file: " << fname << std::endl;
    // std::cout << "Match: "
321
    // << this->Internals->TextExpressions[start].c_str() << std::endl;
322
    // std::cout << "Real name: " << realname << std::endl;
Andy Cedilnik's avatar
Andy Cedilnik committed
323

324 325 326
    if ((!last && !kwsys::SystemTools::FileIsDirectory(realname)) ||
        (!this->ListDirs && last &&
         kwsys::SystemTools::FileIsDirectory(realname))) {
Andy Cedilnik's avatar
Andy Cedilnik committed
327
      continue;
328
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
329

330 331
    if (this->Internals->Expressions[start].find(fname)) {
      if (last) {
332
        this->AddFile(this->Internals->Files, realname);
333 334
      } else {
        this->ProcessDirectory(start + 1, realname, messages);
Andy Cedilnik's avatar
Andy Cedilnik committed
335 336
      }
    }
337
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
338 339 340
}

//----------------------------------------------------------------------------
Brad King's avatar
Brad King committed
341
bool Glob::FindFiles(const std::string& inexpr, GlobMessages* messages)
Andy Cedilnik's avatar
Andy Cedilnik committed
342
{
Brad King's avatar
Brad King committed
343 344 345
  std::string cexpr;
  std::string::size_type cc;
  std::string expr = inexpr;
Andy Cedilnik's avatar
Andy Cedilnik committed
346

347 348
  this->Internals->Expressions.clear();
  this->Internals->Files.clear();
Andy Cedilnik's avatar
Andy Cedilnik committed
349

350
  if (!kwsys::SystemTools::FileIsFullPath(expr)) {
351
    expr = kwsys::SystemTools::GetCurrentWorkingDirectory();
Andy Cedilnik's avatar
Andy Cedilnik committed
352
    expr += "/" + inexpr;
353
  }
Brad King's avatar
Brad King committed
354
  std::string fexpr = expr;
Andy Cedilnik's avatar
Andy Cedilnik committed
355

Brad King's avatar
Brad King committed
356 357
  std::string::size_type skip = 0;
  std::string::size_type last_slash = 0;
358 359
  for (cc = 0; cc < expr.size(); cc++) {
    if (cc > 0 && expr[cc] == '/' && expr[cc - 1] != '\\') {
Francois Bertel's avatar
Francois Bertel committed
360
      last_slash = cc;
361 362 363
    }
    if (cc > 0 && (expr[cc] == '[' || expr[cc] == '?' || expr[cc] == '*') &&
        expr[cc - 1] != '\\') {
Andy Cedilnik's avatar
Andy Cedilnik committed
364 365
      break;
    }
366 367 368
  }
  if (last_slash > 0) {
    // std::cout << "I can skip: " << fexpr.substr(0, last_slash)
369
    // << std::endl;
Andy Cedilnik's avatar
Andy Cedilnik committed
370
    skip = last_slash;
371 372 373
  }
  if (skip == 0) {
#if defined(KWSYS_GLOB_SUPPORT_NETWORK_PATHS)
Andy Cedilnik's avatar
Andy Cedilnik committed
374
    // Handle network paths
375
    if (expr[0] == '/' && expr[1] == '/') {
Andy Cedilnik's avatar
Andy Cedilnik committed
376
      int cnt = 0;
377 378 379 380
      for (cc = 2; cc < expr.size(); cc++) {
        if (expr[cc] == '/') {
          cnt++;
          if (cnt == 2) {
Andy Cedilnik's avatar
Andy Cedilnik committed
381 382 383 384
            break;
          }
        }
      }
385 386
      skip = int(cc + 1);
    } else
Andy Cedilnik's avatar
Andy Cedilnik committed
387 388
#endif
      // Handle drive letters on Windows
389 390
      if (expr[1] == ':' && expr[0] != '/') {
      skip = 2;
Andy Cedilnik's avatar
Andy Cedilnik committed
391
    }
392
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
393

394
  if (skip > 0) {
Andy Cedilnik's avatar
Andy Cedilnik committed
395
    expr = expr.substr(skip);
396
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
397 398

  cexpr = "";
399
  for (cc = 0; cc < expr.size(); cc++) {
Andy Cedilnik's avatar
Andy Cedilnik committed
400
    int ch = expr[cc];
401 402
    if (ch == '/') {
      if (!cexpr.empty()) {
403
        this->AddExpression(cexpr);
Andy Cedilnik's avatar
Andy Cedilnik committed
404
      }
405 406
      cexpr = "";
    } else {
407
      cexpr.append(1, static_cast<char>(ch));
Andy Cedilnik's avatar
Andy Cedilnik committed
408
    }
409 410
  }
  if (!cexpr.empty()) {
411
    this->AddExpression(cexpr);
412
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
413 414

  // Handle network paths
415
  if (skip > 0) {
416
    this->ProcessDirectory(0, fexpr.substr(0, skip) + "/", messages);
417
  } else {
418
    this->ProcessDirectory(0, "/", messages);
419
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
420 421 422
  return true;
}

423
//----------------------------------------------------------------------------
Brad King's avatar
Brad King committed
424
void Glob::AddExpression(const std::string& expr)
Andy Cedilnik's avatar
Andy Cedilnik committed
425
{
426
  this->Internals->Expressions.push_back(
427
    kwsys::RegularExpression(this->PatternToRegex(expr)));
428 429 430 431 432
}

//----------------------------------------------------------------------------
void Glob::SetRelative(const char* dir)
{
433
  if (!dir) {
434 435
    this->Relative = "";
    return;
436
  }
437 438 439 440 441 442
  this->Relative = dir;
}

//----------------------------------------------------------------------------
const char* Glob::GetRelative()
{
443
  if (this->Relative.empty()) {
444
    return 0;
445
  }
446 447 448 449
  return this->Relative.c_str();
}

//----------------------------------------------------------------------------
Brad King's avatar
Brad King committed
450
void Glob::AddFile(std::vector<std::string>& files, const std::string& file)
451
{
452
  if (!this->Relative.empty()) {
453
    files.push_back(kwsys::SystemTools::RelativePath(this->Relative, file));
454
  } else {
455
    files.push_back(file);
456
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
457 458 459
}

} // namespace KWSYS_NAMESPACE