• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1module.exports = globSync
2globSync.GlobSync = GlobSync
3
4var fs = require('fs')
5var rp = require('fs.realpath')
6var minimatch = require('minimatch')
7var Minimatch = minimatch.Minimatch
8var Glob = require('./glob.js').Glob
9var util = require('util')
10var path = require('path')
11var assert = require('assert')
12var isAbsolute = require('path-is-absolute')
13var common = require('./common.js')
14var alphasort = common.alphasort
15var alphasorti = common.alphasorti
16var setopts = common.setopts
17var ownProp = common.ownProp
18var childrenIgnored = common.childrenIgnored
19var isIgnored = common.isIgnored
20
21function globSync (pattern, options) {
22  if (typeof options === 'function' || arguments.length === 3)
23    throw new TypeError('callback provided to sync glob\n'+
24                        'See: https://github.com/isaacs/node-glob/issues/167')
25
26  return new GlobSync(pattern, options).found
27}
28
29function GlobSync (pattern, options) {
30  if (!pattern)
31    throw new Error('must provide pattern')
32
33  if (typeof options === 'function' || arguments.length === 3)
34    throw new TypeError('callback provided to sync glob\n'+
35                        'See: https://github.com/isaacs/node-glob/issues/167')
36
37  if (!(this instanceof GlobSync))
38    return new GlobSync(pattern, options)
39
40  setopts(this, pattern, options)
41
42  if (this.noprocess)
43    return this
44
45  var n = this.minimatch.set.length
46  this.matches = new Array(n)
47  for (var i = 0; i < n; i ++) {
48    this._process(this.minimatch.set[i], i, false)
49  }
50  this._finish()
51}
52
53GlobSync.prototype._finish = function () {
54  assert(this instanceof GlobSync)
55  if (this.realpath) {
56    var self = this
57    this.matches.forEach(function (matchset, index) {
58      var set = self.matches[index] = Object.create(null)
59      for (var p in matchset) {
60        try {
61          p = self._makeAbs(p)
62          var real = rp.realpathSync(p, self.realpathCache)
63          set[real] = true
64        } catch (er) {
65          if (er.syscall === 'stat')
66            set[self._makeAbs(p)] = true
67          else
68            throw er
69        }
70      }
71    })
72  }
73  common.finish(this)
74}
75
76
77GlobSync.prototype._process = function (pattern, index, inGlobStar) {
78  assert(this instanceof GlobSync)
79
80  // Get the first [n] parts of pattern that are all strings.
81  var n = 0
82  while (typeof pattern[n] === 'string') {
83    n ++
84  }
85  // now n is the index of the first one that is *not* a string.
86
87  // See if there's anything else
88  var prefix
89  switch (n) {
90    // if not, then this is rather simple
91    case pattern.length:
92      this._processSimple(pattern.join('/'), index)
93      return
94
95    case 0:
96      // pattern *starts* with some non-trivial item.
97      // going to readdir(cwd), but not include the prefix in matches.
98      prefix = null
99      break
100
101    default:
102      // pattern has some string bits in the front.
103      // whatever it starts with, whether that's 'absolute' like /foo/bar,
104      // or 'relative' like '../baz'
105      prefix = pattern.slice(0, n).join('/')
106      break
107  }
108
109  var remain = pattern.slice(n)
110
111  // get the list of entries.
112  var read
113  if (prefix === null)
114    read = '.'
115  else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) {
116    if (!prefix || !isAbsolute(prefix))
117      prefix = '/' + prefix
118    read = prefix
119  } else
120    read = prefix
121
122  var abs = this._makeAbs(read)
123
124  //if ignored, skip processing
125  if (childrenIgnored(this, read))
126    return
127
128  var isGlobStar = remain[0] === minimatch.GLOBSTAR
129  if (isGlobStar)
130    this._processGlobStar(prefix, read, abs, remain, index, inGlobStar)
131  else
132    this._processReaddir(prefix, read, abs, remain, index, inGlobStar)
133}
134
135
136GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) {
137  var entries = this._readdir(abs, inGlobStar)
138
139  // if the abs isn't a dir, then nothing can match!
140  if (!entries)
141    return
142
143  // It will only match dot entries if it starts with a dot, or if
144  // dot is set.  Stuff like @(.foo|.bar) isn't allowed.
145  var pn = remain[0]
146  var negate = !!this.minimatch.negate
147  var rawGlob = pn._glob
148  var dotOk = this.dot || rawGlob.charAt(0) === '.'
149
150  var matchedEntries = []
151  for (var i = 0; i < entries.length; i++) {
152    var e = entries[i]
153    if (e.charAt(0) !== '.' || dotOk) {
154      var m
155      if (negate && !prefix) {
156        m = !e.match(pn)
157      } else {
158        m = e.match(pn)
159      }
160      if (m)
161        matchedEntries.push(e)
162    }
163  }
164
165  var len = matchedEntries.length
166  // If there are no matched entries, then nothing matches.
167  if (len === 0)
168    return
169
170  // if this is the last remaining pattern bit, then no need for
171  // an additional stat *unless* the user has specified mark or
172  // stat explicitly.  We know they exist, since readdir returned
173  // them.
174
175  if (remain.length === 1 && !this.mark && !this.stat) {
176    if (!this.matches[index])
177      this.matches[index] = Object.create(null)
178
179    for (var i = 0; i < len; i ++) {
180      var e = matchedEntries[i]
181      if (prefix) {
182        if (prefix.slice(-1) !== '/')
183          e = prefix + '/' + e
184        else
185          e = prefix + e
186      }
187
188      if (e.charAt(0) === '/' && !this.nomount) {
189        e = path.join(this.root, e)
190      }
191      this._emitMatch(index, e)
192    }
193    // This was the last one, and no stats were needed
194    return
195  }
196
197  // now test all matched entries as stand-ins for that part
198  // of the pattern.
199  remain.shift()
200  for (var i = 0; i < len; i ++) {
201    var e = matchedEntries[i]
202    var newPattern
203    if (prefix)
204      newPattern = [prefix, e]
205    else
206      newPattern = [e]
207    this._process(newPattern.concat(remain), index, inGlobStar)
208  }
209}
210
211
212GlobSync.prototype._emitMatch = function (index, e) {
213  if (isIgnored(this, e))
214    return
215
216  var abs = this._makeAbs(e)
217
218  if (this.mark)
219    e = this._mark(e)
220
221  if (this.absolute) {
222    e = abs
223  }
224
225  if (this.matches[index][e])
226    return
227
228  if (this.nodir) {
229    var c = this.cache[abs]
230    if (c === 'DIR' || Array.isArray(c))
231      return
232  }
233
234  this.matches[index][e] = true
235
236  if (this.stat)
237    this._stat(e)
238}
239
240
241GlobSync.prototype._readdirInGlobStar = function (abs) {
242  // follow all symlinked directories forever
243  // just proceed as if this is a non-globstar situation
244  if (this.follow)
245    return this._readdir(abs, false)
246
247  var entries
248  var lstat
249  var stat
250  try {
251    lstat = fs.lstatSync(abs)
252  } catch (er) {
253    if (er.code === 'ENOENT') {
254      // lstat failed, doesn't exist
255      return null
256    }
257  }
258
259  var isSym = lstat && lstat.isSymbolicLink()
260  this.symlinks[abs] = isSym
261
262  // If it's not a symlink or a dir, then it's definitely a regular file.
263  // don't bother doing a readdir in that case.
264  if (!isSym && lstat && !lstat.isDirectory())
265    this.cache[abs] = 'FILE'
266  else
267    entries = this._readdir(abs, false)
268
269  return entries
270}
271
272GlobSync.prototype._readdir = function (abs, inGlobStar) {
273  var entries
274
275  if (inGlobStar && !ownProp(this.symlinks, abs))
276    return this._readdirInGlobStar(abs)
277
278  if (ownProp(this.cache, abs)) {
279    var c = this.cache[abs]
280    if (!c || c === 'FILE')
281      return null
282
283    if (Array.isArray(c))
284      return c
285  }
286
287  try {
288    return this._readdirEntries(abs, fs.readdirSync(abs))
289  } catch (er) {
290    this._readdirError(abs, er)
291    return null
292  }
293}
294
295GlobSync.prototype._readdirEntries = function (abs, entries) {
296  // if we haven't asked to stat everything, then just
297  // assume that everything in there exists, so we can avoid
298  // having to stat it a second time.
299  if (!this.mark && !this.stat) {
300    for (var i = 0; i < entries.length; i ++) {
301      var e = entries[i]
302      if (abs === '/')
303        e = abs + e
304      else
305        e = abs + '/' + e
306      this.cache[e] = true
307    }
308  }
309
310  this.cache[abs] = entries
311
312  // mark and cache dir-ness
313  return entries
314}
315
316GlobSync.prototype._readdirError = function (f, er) {
317  // handle errors, and cache the information
318  switch (er.code) {
319    case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205
320    case 'ENOTDIR': // totally normal. means it *does* exist.
321      var abs = this._makeAbs(f)
322      this.cache[abs] = 'FILE'
323      if (abs === this.cwdAbs) {
324        var error = new Error(er.code + ' invalid cwd ' + this.cwd)
325        error.path = this.cwd
326        error.code = er.code
327        throw error
328      }
329      break
330
331    case 'ENOENT': // not terribly unusual
332    case 'ELOOP':
333    case 'ENAMETOOLONG':
334    case 'UNKNOWN':
335      this.cache[this._makeAbs(f)] = false
336      break
337
338    default: // some unusual error.  Treat as failure.
339      this.cache[this._makeAbs(f)] = false
340      if (this.strict)
341        throw er
342      if (!this.silent)
343        console.error('glob error', er)
344      break
345  }
346}
347
348GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) {
349
350  var entries = this._readdir(abs, inGlobStar)
351
352  // no entries means not a dir, so it can never have matches
353  // foo.txt/** doesn't match foo.txt
354  if (!entries)
355    return
356
357  // test without the globstar, and with every child both below
358  // and replacing the globstar.
359  var remainWithoutGlobStar = remain.slice(1)
360  var gspref = prefix ? [ prefix ] : []
361  var noGlobStar = gspref.concat(remainWithoutGlobStar)
362
363  // the noGlobStar pattern exits the inGlobStar state
364  this._process(noGlobStar, index, false)
365
366  var len = entries.length
367  var isSym = this.symlinks[abs]
368
369  // If it's a symlink, and we're in a globstar, then stop
370  if (isSym && inGlobStar)
371    return
372
373  for (var i = 0; i < len; i++) {
374    var e = entries[i]
375    if (e.charAt(0) === '.' && !this.dot)
376      continue
377
378    // these two cases enter the inGlobStar state
379    var instead = gspref.concat(entries[i], remainWithoutGlobStar)
380    this._process(instead, index, true)
381
382    var below = gspref.concat(entries[i], remain)
383    this._process(below, index, true)
384  }
385}
386
387GlobSync.prototype._processSimple = function (prefix, index) {
388  // XXX review this.  Shouldn't it be doing the mounting etc
389  // before doing stat?  kinda weird?
390  var exists = this._stat(prefix)
391
392  if (!this.matches[index])
393    this.matches[index] = Object.create(null)
394
395  // If it doesn't exist, then just mark the lack of results
396  if (!exists)
397    return
398
399  if (prefix && isAbsolute(prefix) && !this.nomount) {
400    var trail = /[\/\\]$/.test(prefix)
401    if (prefix.charAt(0) === '/') {
402      prefix = path.join(this.root, prefix)
403    } else {
404      prefix = path.resolve(this.root, prefix)
405      if (trail)
406        prefix += '/'
407    }
408  }
409
410  if (process.platform === 'win32')
411    prefix = prefix.replace(/\\/g, '/')
412
413  // Mark this as a match
414  this._emitMatch(index, prefix)
415}
416
417// Returns either 'DIR', 'FILE', or false
418GlobSync.prototype._stat = function (f) {
419  var abs = this._makeAbs(f)
420  var needDir = f.slice(-1) === '/'
421
422  if (f.length > this.maxLength)
423    return false
424
425  if (!this.stat && ownProp(this.cache, abs)) {
426    var c = this.cache[abs]
427
428    if (Array.isArray(c))
429      c = 'DIR'
430
431    // It exists, but maybe not how we need it
432    if (!needDir || c === 'DIR')
433      return c
434
435    if (needDir && c === 'FILE')
436      return false
437
438    // otherwise we have to stat, because maybe c=true
439    // if we know it exists, but not what it is.
440  }
441
442  var exists
443  var stat = this.statCache[abs]
444  if (!stat) {
445    var lstat
446    try {
447      lstat = fs.lstatSync(abs)
448    } catch (er) {
449      if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) {
450        this.statCache[abs] = false
451        return false
452      }
453    }
454
455    if (lstat && lstat.isSymbolicLink()) {
456      try {
457        stat = fs.statSync(abs)
458      } catch (er) {
459        stat = lstat
460      }
461    } else {
462      stat = lstat
463    }
464  }
465
466  this.statCache[abs] = stat
467
468  var c = true
469  if (stat)
470    c = stat.isDirectory() ? 'DIR' : 'FILE'
471
472  this.cache[abs] = this.cache[abs] || c
473
474  if (needDir && c === 'FILE')
475    return false
476
477  return c
478}
479
480GlobSync.prototype._mark = function (p) {
481  return common.mark(this, p)
482}
483
484GlobSync.prototype._makeAbs = function (f) {
485  return common.makeAbs(this, f)
486}
487