sync.js (12002B)
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 alphasort = common.alphasort 15 var alphasorti = common.alphasorti 16 var setopts = common.setopts 17 var ownProp = common.ownProp 18 var childrenIgnored = common.childrenIgnored 19 var isIgnored = common.isIgnored 20 21 function 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 29 function 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 53 GlobSync.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 77 GlobSync.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 136 GlobSync.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 212 GlobSync.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 241 GlobSync.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 272 GlobSync.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 295 GlobSync.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 316 GlobSync.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 348 GlobSync.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 387 GlobSync.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 418 GlobSync.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 480 GlobSync.prototype._mark = function (p) { 481 return common.mark(this, p) 482 } 483 484 GlobSync.prototype._makeAbs = function (f) { 485 return common.makeAbs(this, f) 486 }