mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
680 lines
18 KiB
680 lines
18 KiB
// 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("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 options === "function") {
|
|
cb = options
|
|
options = null
|
|
}
|
|
|
|
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 === ""
|
|
}
|
|
|