Glob.cxx 12.6 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
#endif

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

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

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

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

Glob::~Glob()
{
65
  delete this->Internals;
Andy Cedilnik's avatar
Andy Cedilnik committed
66
67
}

Brad King's avatar
Brad King committed
68
std::vector<std::string>& Glob::GetFiles()
Andy Cedilnik's avatar
Andy Cedilnik committed
69
{
70
  return this->Internals->Files;
Andy Cedilnik's avatar
Andy Cedilnik committed
71
72
}

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

      // The first character may be complementation '!' or '^'.
101
102
      if (bracket_last != pattern_last &&
          (*bracket_last == '!' || *bracket_last == '^')) {
103
        ++bracket_last;
104
      }
105
106
107

      // If the next character is a ']' it is included in the brackets
      // because the bracket string may not be empty.
108
      if (bracket_last != pattern_last && *bracket_last == ']') {
109
        ++bracket_last;
110
      }
111
112

      // Search for the closing ']'.
113
      while (bracket_last != pattern_last && *bracket_last != ']') {
114
        ++bracket_last;
115
      }
116
117

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

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

        // A regex range complement uses '^' instead of '!'.
130
        if (k != bracket_last && *k == '!') {
131
132
          regex += "^";
          ++k;
133
        }
134
135

        // Convert the remaining characters.
136
        for (; k != bracket_last; ++k) {
137
          // Backslashes must be escaped.
138
          if (*k == '\\') {
139
            regex += "\\";
140
          }
141
142
143

          // Store this character.
          regex += *k;
144
        }
145
146
147
148
149
150

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

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

175
  if (require_whole_string) {
176
    regex += "$";
177
  }
178
  return regex;
Andy Cedilnik's avatar
Andy Cedilnik committed
179
180
}

Brad King's avatar
Brad King committed
181
bool Glob::RecurseDirectory(std::string::size_type start,
182
                            const std::string& dir, GlobMessages* messages)
Andy Cedilnik's avatar
Andy Cedilnik committed
183
{
184
  kwsys::Directory d;
185
186
  std::string errorMessage;
  if (!d.Load(dir, &errorMessage)) {
187
188
189
190
191
192
    if (messages) {
      if (!errorMessage.empty()) {
        messages->push_back(Message(Glob::warning,
                                    "Error listing directory '" + dir +
                                      "'! Reason: '" + errorMessage + "'"));
      }
193
    }
194
    return true;
195
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
196
  unsigned long cc;
Brad King's avatar
Brad King committed
197
198
  std::string realname;
  std::string fname;
199
  for (cc = 0; cc < d.GetNumberOfFiles(); cc++) {
Andy Cedilnik's avatar
Andy Cedilnik committed
200
    fname = d.GetFile(cc);
201
    if (fname == "." || fname == "..") {
Andy Cedilnik's avatar
Andy Cedilnik committed
202
      continue;
203
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
204

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

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

216
217
    bool isDir = kwsys::SystemTools::FileIsDirectory(realname);
    bool isSymLink = kwsys::SystemTools::FileIsSymlink(realname);
218

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

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

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

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

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

  return true;
Andy Cedilnik's avatar
Andy Cedilnik committed
281
282
}

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

295
  if (start >= this->Internals->Expressions.size()) {
Alexander Neundorf's avatar
   
Alexander Neundorf committed
296
    return;
297
  }
Alexander Neundorf's avatar
   
Alexander Neundorf committed
298

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

312
    if (start == 0) {
Andy Cedilnik's avatar
Andy Cedilnik committed
313
      realname = dir + fname;
314
    } else {
Andy Cedilnik's avatar
Andy Cedilnik committed
315
      realname = dir + "/" + fname;
316
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
317

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

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

328
329
330
    if ((!last && !kwsys::SystemTools::FileIsDirectory(realname)) ||
        (!this->ListDirs && last &&
         kwsys::SystemTools::FileIsDirectory(realname))) {
Andy Cedilnik's avatar
Andy Cedilnik committed
331
      continue;
332
    }
Andy Cedilnik's avatar
Andy Cedilnik committed
333

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

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

350
351
  this->Internals->Expressions.clear();
  this->Internals->Files.clear();
Andy Cedilnik's avatar
Andy Cedilnik committed
352

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

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

397
  if (skip > 0) {
398
    expr.erase(0, skip);
399
  }
Andy Cedilnik's avatar
Andy Cedilnik committed
400

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

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

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

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

const char* Glob::GetRelative()
{
441
  if (this->Relative.empty()) {
442
    return nullptr;
443
  }
444
445
446
  return this->Relative.c_str();
}

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

} // namespace KWSYS_NAMESPACE