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