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 }