| // Approach: |
| // |
| // 1. Get the minimatch set |
| // 2. For each pattern in the set, PROCESS(pattern) |
| // 3. Store matches per-set, then uniq them |
| // |
| // PROCESS(pattern) |
| // Get the first [n] items from pattern that are all strings |
| // Join these together. This is PREFIX. |
| // If there is no more remaining, then stat(PREFIX) and |
| // add to matches if it succeeds. END. |
| // readdir(PREFIX) as ENTRIES |
| // If fails, END |
| // If pattern[n] is GLOBSTAR |
| // // handle the case where the globstar match is empty |
| // // by pruning it out, and testing the resulting pattern |
| // PROCESS(pattern[0..n] + pattern[n+1 .. $]) |
| // // handle other cases. |
| // for ENTRY in ENTRIES (not dotfiles) |
| // // attach globstar + tail onto the entry |
| // PROCESS(pattern[0..n] + ENTRY + pattern[n .. $]) |
| // |
| // else // not globstar |
| // for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) |
| // Test ENTRY against pattern[n] |
| // If fails, continue |
| // If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) |
| // |
| // Caveat: |
| // Cache all stats and readdirs results to minimize syscall. Since all |
| // we ever care about is existence and directory-ness, we can just keep |
| // `true` for files, and [children,...] for directories, or `false` for |
| // things that don't exist. |
| |
| |
| |
| module.exports = glob |
| |
| var fs = require("graceful-fs") |
| , minimatch = require("minimatch") |
| , Minimatch = minimatch.Minimatch |
| , inherits = require("inherits") |
| , EE = require("events").EventEmitter |
| , path = require("path") |
| , isDir = {} |
| , assert = require("assert").ok |
| |
| function glob (pattern, options, cb) { |
| if (typeof options === "function") cb = options, options = {} |
| if (!options) options = {} |
| |
| if (typeof options === "number") { |
| deprecated() |
| return |
| } |
| |
| var g = new Glob(pattern, options, cb) |
| return g.sync ? g.found : g |
| } |
| |
| glob.fnmatch = deprecated |
| |
| function deprecated () { |
| throw new Error("glob's interface has changed. Please see the docs.") |
| } |
| |
| glob.sync = globSync |
| function globSync (pattern, options) { |
| if (typeof options === "number") { |
| deprecated() |
| return |
| } |
| |
| options = options || {} |
| options.sync = true |
| return glob(pattern, options) |
| } |
| |
| |
| glob.Glob = Glob |
| inherits(Glob, EE) |
| function Glob (pattern, options, cb) { |
| if (!(this instanceof Glob)) { |
| return new Glob(pattern, options, cb) |
| } |
| |
| if (typeof cb === "function") { |
| this.on("error", cb) |
| this.on("end", function (matches) { |
| cb(null, matches) |
| }) |
| } |
| |
| options = options || {} |
| |
| this.EOF = {} |
| this._emitQueue = [] |
| |
| this.maxDepth = options.maxDepth || 1000 |
| this.maxLength = options.maxLength || Infinity |
| this.cache = options.cache || {} |
| this.statCache = options.statCache || {} |
| |
| this.changedCwd = false |
| var cwd = process.cwd() |
| if (!options.hasOwnProperty("cwd")) this.cwd = cwd |
| else { |
| this.cwd = options.cwd |
| this.changedCwd = path.resolve(options.cwd) !== cwd |
| } |
| |
| this.root = options.root || path.resolve(this.cwd, "/") |
| this.root = path.resolve(this.root) |
| if (process.platform === "win32") |
| this.root = this.root.replace(/\\/g, "/") |
| |
| this.nomount = !!options.nomount |
| |
| if (!pattern) { |
| throw new Error("must provide pattern") |
| } |
| |
| // base-matching: just use globstar for that. |
| if (options.matchBase && -1 === pattern.indexOf("/")) { |
| if (options.noglobstar) { |
| throw new Error("base matching requires globstar") |
| } |
| pattern = "**/" + pattern |
| } |
| |
| this.strict = options.strict !== false |
| this.dot = !!options.dot |
| this.mark = !!options.mark |
| this.sync = !!options.sync |
| this.nounique = !!options.nounique |
| this.nonull = !!options.nonull |
| this.nosort = !!options.nosort |
| this.nocase = !!options.nocase |
| this.stat = !!options.stat |
| |
| this.debug = !!options.debug || !!options.globDebug |
| if (this.debug) |
| this.log = console.error |
| |
| this.silent = !!options.silent |
| |
| var mm = this.minimatch = new Minimatch(pattern, options) |
| this.options = mm.options |
| pattern = this.pattern = mm.pattern |
| |
| this.error = null |
| this.aborted = false |
| |
| // list of all the patterns that ** has resolved do, so |
| // we can avoid visiting multiple times. |
| this._globstars = {} |
| |
| EE.call(this) |
| |
| // process each pattern in the minimatch set |
| var n = this.minimatch.set.length |
| |
| // The matches are stored as {<filename>: true,...} so that |
| // duplicates are automagically pruned. |
| // Later, we do an Object.keys() on these. |
| // Keep them as a list so we can fill in when nonull is set. |
| this.matches = new Array(n) |
| |
| this.minimatch.set.forEach(iterator.bind(this)) |
| function iterator (pattern, i, set) { |
| this._process(pattern, 0, i, function (er) { |
| if (er) this.emit("error", er) |
| if (-- n <= 0) this._finish() |
| }) |
| } |
| } |
| |
| Glob.prototype.log = function () {} |
| |
| Glob.prototype._finish = function () { |
| assert(this instanceof Glob) |
| |
| var nou = this.nounique |
| , all = nou ? [] : {} |
| |
| for (var i = 0, l = this.matches.length; i < l; i ++) { |
| var matches = this.matches[i] |
| this.log("matches[%d] =", i, matches) |
| // do like the shell, and spit out the literal glob |
| if (!matches) { |
| if (this.nonull) { |
| var literal = this.minimatch.globSet[i] |
| if (nou) all.push(literal) |
| else all[literal] = true |
| } |
| } else { |
| // had matches |
| var m = Object.keys(matches) |
| if (nou) all.push.apply(all, m) |
| else m.forEach(function (m) { |
| all[m] = true |
| }) |
| } |
| } |
| |
| if (!nou) all = Object.keys(all) |
| |
| if (!this.nosort) { |
| all = all.sort(this.nocase ? alphasorti : alphasort) |
| } |
| |
| if (this.mark) { |
| // at *some* point we statted all of these |
| all = all.map(function (m) { |
| var sc = this.cache[m] |
| if (!sc) |
| return m |
| var isDir = (Array.isArray(sc) || sc === 2) |
| if (isDir && m.slice(-1) !== "/") { |
| return m + "/" |
| } |
| if (!isDir && m.slice(-1) === "/") { |
| return m.replace(/\/+$/, "") |
| } |
| return m |
| }, this) |
| } |
| |
| this.log("emitting end", all) |
| |
| this.EOF = this.found = all |
| this.emitMatch(this.EOF) |
| } |
| |
| function alphasorti (a, b) { |
| a = a.toLowerCase() |
| b = b.toLowerCase() |
| return alphasort(a, b) |
| } |
| |
| function alphasort (a, b) { |
| return a > b ? 1 : a < b ? -1 : 0 |
| } |
| |
| Glob.prototype.abort = function () { |
| this.aborted = true |
| this.emit("abort") |
| } |
| |
| Glob.prototype.pause = function () { |
| if (this.paused) return |
| if (this.sync) |
| this.emit("error", new Error("Can't pause/resume sync glob")) |
| this.paused = true |
| this.emit("pause") |
| } |
| |
| Glob.prototype.resume = function () { |
| if (!this.paused) return |
| if (this.sync) |
| this.emit("error", new Error("Can't pause/resume sync glob")) |
| this.paused = false |
| this.emit("resume") |
| this._processEmitQueue() |
| //process.nextTick(this.emit.bind(this, "resume")) |
| } |
| |
| Glob.prototype.emitMatch = function (m) { |
| if (!this.stat || this.statCache[m] || m === this.EOF) { |
| this._emitQueue.push(m) |
| this._processEmitQueue() |
| } else { |
| this._stat(m, function(exists, isDir) { |
| if (exists) { |
| this._emitQueue.push(m) |
| this._processEmitQueue() |
| } |
| }) |
| } |
| } |
| |
| Glob.prototype._processEmitQueue = function (m) { |
| while (!this._processingEmitQueue && |
| !this.paused) { |
| this._processingEmitQueue = true |
| var m = this._emitQueue.shift() |
| if (!m) { |
| this._processingEmitQueue = false |
| break |
| } |
| |
| this.log('emit!', m === this.EOF ? "end" : "match") |
| |
| this.emit(m === this.EOF ? "end" : "match", m) |
| this._processingEmitQueue = false |
| } |
| } |
| |
| Glob.prototype._process = function (pattern, depth, index, cb_) { |
| assert(this instanceof Glob) |
| |
| var cb = function cb (er, res) { |
| assert(this instanceof Glob) |
| if (this.paused) { |
| if (!this._processQueue) { |
| this._processQueue = [] |
| this.once("resume", function () { |
| var q = this._processQueue |
| this._processQueue = null |
| q.forEach(function (cb) { cb() }) |
| }) |
| } |
| this._processQueue.push(cb_.bind(this, er, res)) |
| } else { |
| cb_.call(this, er, res) |
| } |
| }.bind(this) |
| |
| if (this.aborted) return cb() |
| |
| if (depth > this.maxDepth) return cb() |
| |
| // Get the first [n] parts of pattern that are all strings. |
| var n = 0 |
| while (typeof pattern[n] === "string") { |
| n ++ |
| } |
| // now n is the index of the first one that is *not* a string. |
| |
| // see if there's anything else |
| var prefix |
| switch (n) { |
| // if not, then this is rather simple |
| case pattern.length: |
| prefix = pattern.join("/") |
| this._stat(prefix, function (exists, isDir) { |
| // either it's there, or it isn't. |
| // nothing more to do, either way. |
| if (exists) { |
| if (prefix && isAbsolute(prefix) && !this.nomount) { |
| if (prefix.charAt(0) === "/") { |
| prefix = path.join(this.root, prefix) |
| } else { |
| prefix = path.resolve(this.root, prefix) |
| } |
| } |
| |
| if (process.platform === "win32") |
| prefix = prefix.replace(/\\/g, "/") |
| |
| this.matches[index] = this.matches[index] || {} |
| this.matches[index][prefix] = true |
| this.emitMatch(prefix) |
| } |
| return cb() |
| }) |
| return |
| |
| case 0: |
| // pattern *starts* with some non-trivial item. |
| // going to readdir(cwd), but not include the prefix in matches. |
| prefix = null |
| break |
| |
| default: |
| // pattern has some string bits in the front. |
| // whatever it starts with, whether that's "absolute" like /foo/bar, |
| // or "relative" like "../baz" |
| prefix = pattern.slice(0, n) |
| prefix = prefix.join("/") |
| break |
| } |
| |
| // get the list of entries. |
| var read |
| if (prefix === null) read = "." |
| else if (isAbsolute(prefix) || isAbsolute(pattern.join("/"))) { |
| if (!prefix || !isAbsolute(prefix)) { |
| prefix = path.join("/", prefix) |
| } |
| read = prefix = path.resolve(prefix) |
| |
| // if (process.platform === "win32") |
| // read = prefix = prefix.replace(/^[a-zA-Z]:|\\/g, "/") |
| |
| this.log('absolute: ', prefix, this.root, pattern, read) |
| } else { |
| read = prefix |
| } |
| |
| this.log('readdir(%j)', read, this.cwd, this.root) |
| |
| return this._readdir(read, function (er, entries) { |
| if (er) { |
| // not a directory! |
| // this means that, whatever else comes after this, it can never match |
| return cb() |
| } |
| |
| // globstar is special |
| if (pattern[n] === minimatch.GLOBSTAR) { |
| // test without the globstar, and with every child both below |
| // and replacing the globstar. |
| var s = [ pattern.slice(0, n).concat(pattern.slice(n + 1)) ] |
| entries.forEach(function (e) { |
| if (e.charAt(0) === "." && !this.dot) return |
| // instead of the globstar |
| s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1))) |
| // below the globstar |
| s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n))) |
| }, this) |
| |
| s = s.filter(function (pattern) { |
| var key = gsKey(pattern) |
| var seen = !this._globstars[key] |
| this._globstars[key] = true |
| return seen |
| }, this) |
| |
| if (!s.length) |
| return cb() |
| |
| // now asyncForEach over this |
| var l = s.length |
| , errState = null |
| s.forEach(function (gsPattern) { |
| this._process(gsPattern, depth + 1, index, function (er) { |
| if (errState) return |
| if (er) return cb(errState = er) |
| if (--l <= 0) return cb() |
| }) |
| }, this) |
| |
| return |
| } |
| |
| // not a globstar |
| // It will only match dot entries if it starts with a dot, or if |
| // dot is set. Stuff like @(.foo|.bar) isn't allowed. |
| var pn = pattern[n] |
| var rawGlob = pattern[n]._glob |
| , dotOk = this.dot || rawGlob.charAt(0) === "." |
| |
| entries = entries.filter(function (e) { |
| return (e.charAt(0) !== "." || dotOk) && |
| e.match(pattern[n]) |
| }) |
| |
| // If n === pattern.length - 1, then there's no need for the extra stat |
| // *unless* the user has specified "mark" or "stat" explicitly. |
| // We know that they exist, since the readdir returned them. |
| if (n === pattern.length - 1 && |
| !this.mark && |
| !this.stat) { |
| entries.forEach(function (e) { |
| if (prefix) { |
| if (prefix !== "/") e = prefix + "/" + e |
| else e = prefix + e |
| } |
| if (e.charAt(0) === "/" && !this.nomount) { |
| e = path.join(this.root, e) |
| } |
| |
| if (process.platform === "win32") |
| e = e.replace(/\\/g, "/") |
| |
| this.matches[index] = this.matches[index] || {} |
| this.matches[index][e] = true |
| this.emitMatch(e) |
| }, this) |
| return cb.call(this) |
| } |
| |
| |
| // now test all the remaining entries as stand-ins for that part |
| // of the pattern. |
| var l = entries.length |
| , errState = null |
| if (l === 0) return cb() // no matches possible |
| entries.forEach(function (e) { |
| var p = pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1)) |
| this._process(p, depth + 1, index, function (er) { |
| if (errState) return |
| if (er) return cb(errState = er) |
| if (--l === 0) return cb.call(this) |
| }) |
| }, this) |
| }) |
| |
| } |
| |
| function gsKey (pattern) { |
| return '**' + pattern.map(function (p) { |
| return (p === minimatch.GLOBSTAR) ? '**' : (''+p) |
| }).join('/') |
| } |
| |
| Glob.prototype._stat = function (f, cb) { |
| assert(this instanceof Glob) |
| var abs = f |
| if (f.charAt(0) === "/") { |
| abs = path.join(this.root, f) |
| } else if (this.changedCwd) { |
| abs = path.resolve(this.cwd, f) |
| } |
| |
| if (f.length > this.maxLength) { |
| var er = new Error("Path name too long") |
| er.code = "ENAMETOOLONG" |
| er.path = f |
| return this._afterStat(f, abs, cb, er) |
| } |
| |
| this.log('stat', [this.cwd, f, '=', abs]) |
| |
| if (!this.stat && this.cache.hasOwnProperty(f)) { |
| var exists = this.cache[f] |
| , isDir = exists && (Array.isArray(exists) || exists === 2) |
| if (this.sync) return cb.call(this, !!exists, isDir) |
| return process.nextTick(cb.bind(this, !!exists, isDir)) |
| } |
| |
| var stat = this.statCache[abs] |
| if (this.sync || stat) { |
| var er |
| try { |
| stat = fs.statSync(abs) |
| } catch (e) { |
| er = e |
| } |
| this._afterStat(f, abs, cb, er, stat) |
| } else { |
| fs.stat(abs, this._afterStat.bind(this, f, abs, cb)) |
| } |
| } |
| |
| Glob.prototype._afterStat = function (f, abs, cb, er, stat) { |
| var exists |
| assert(this instanceof Glob) |
| |
| if (abs.slice(-1) === "/" && stat && !stat.isDirectory()) { |
| this.log("should be ENOTDIR, fake it") |
| |
| er = new Error("ENOTDIR, not a directory '" + abs + "'") |
| er.path = abs |
| er.code = "ENOTDIR" |
| stat = null |
| } |
| |
| var emit = !this.statCache[abs] |
| this.statCache[abs] = stat |
| |
| if (er || !stat) { |
| exists = false |
| } else { |
| exists = stat.isDirectory() ? 2 : 1 |
| if (emit) |
| this.emit('stat', f, stat) |
| } |
| this.cache[f] = this.cache[f] || exists |
| cb.call(this, !!exists, exists === 2) |
| } |
| |
| Glob.prototype._readdir = function (f, cb) { |
| assert(this instanceof Glob) |
| var abs = f |
| if (f.charAt(0) === "/") { |
| abs = path.join(this.root, f) |
| } else if (isAbsolute(f)) { |
| abs = f |
| } else if (this.changedCwd) { |
| abs = path.resolve(this.cwd, f) |
| } |
| |
| if (f.length > this.maxLength) { |
| var er = new Error("Path name too long") |
| er.code = "ENAMETOOLONG" |
| er.path = f |
| return this._afterReaddir(f, abs, cb, er) |
| } |
| |
| this.log('readdir', [this.cwd, f, abs]) |
| if (this.cache.hasOwnProperty(f)) { |
| var c = this.cache[f] |
| if (Array.isArray(c)) { |
| if (this.sync) return cb.call(this, null, c) |
| return process.nextTick(cb.bind(this, null, c)) |
| } |
| |
| if (!c || c === 1) { |
| // either ENOENT or ENOTDIR |
| var code = c ? "ENOTDIR" : "ENOENT" |
| , er = new Error((c ? "Not a directory" : "Not found") + ": " + f) |
| er.path = f |
| er.code = code |
| this.log(f, er) |
| if (this.sync) return cb.call(this, er) |
| return process.nextTick(cb.bind(this, er)) |
| } |
| |
| // at this point, c === 2, meaning it's a dir, but we haven't |
| // had to read it yet, or c === true, meaning it's *something* |
| // but we don't have any idea what. Need to read it, either way. |
| } |
| |
| if (this.sync) { |
| var er, entries |
| try { |
| entries = fs.readdirSync(abs) |
| } catch (e) { |
| er = e |
| } |
| return this._afterReaddir(f, abs, cb, er, entries) |
| } |
| |
| fs.readdir(abs, this._afterReaddir.bind(this, f, abs, cb)) |
| } |
| |
| Glob.prototype._afterReaddir = function (f, abs, cb, er, entries) { |
| assert(this instanceof Glob) |
| if (entries && !er) { |
| this.cache[f] = entries |
| // if we haven't asked to stat everything for suresies, then just |
| // assume that everything in there exists, so we can avoid |
| // having to stat it a second time. This also gets us one step |
| // further into ELOOP territory. |
| if (!this.mark && !this.stat) { |
| entries.forEach(function (e) { |
| if (f === "/") e = f + e |
| else e = f + "/" + e |
| this.cache[e] = true |
| }, this) |
| } |
| |
| return cb.call(this, er, entries) |
| } |
| |
| // now handle errors, and cache the information |
| if (er) switch (er.code) { |
| case "ENOTDIR": // totally normal. means it *does* exist. |
| this.cache[f] = 1 |
| return cb.call(this, er) |
| case "ENOENT": // not terribly unusual |
| case "ELOOP": |
| case "ENAMETOOLONG": |
| case "UNKNOWN": |
| this.cache[f] = false |
| return cb.call(this, er) |
| default: // some unusual error. Treat as failure. |
| this.cache[f] = false |
| if (this.strict) this.emit("error", er) |
| if (!this.silent) console.error("glob error", er) |
| return cb.call(this, er) |
| } |
| } |
| |
| var isAbsolute = process.platform === "win32" ? absWin : absUnix |
| |
| function absWin (p) { |
| if (absUnix(p)) return true |
| // pull off the device/UNC bit from a windows path. |
| // from node's lib/path.js |
| var splitDeviceRe = |
| /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/ |
| , result = splitDeviceRe.exec(p) |
| , device = result[1] || '' |
| , isUnc = device && device.charAt(1) !== ':' |
| , isAbsolute = !!result[2] || isUnc // UNC paths are always absolute |
| |
| return isAbsolute |
| } |
| |
| function absUnix (p) { |
| return p.charAt(0) === "/" || p === "" |
| } |