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