mirror of
https://github.com/electron/node-gyp.git
synced 2025-09-16 14:03:39 +02:00
402 lines
10 KiB
JavaScript
402 lines
10 KiB
JavaScript
module.exports = glob
|
|
|
|
var fs = require("graceful-fs")
|
|
, minimatch = require("minimatch")
|
|
, Minimatch = minimatch.Minimatch
|
|
, inherits = require("inherits")
|
|
, EE = require("events").EventEmitter
|
|
, FastList = require("fast-list")
|
|
, path = require("path")
|
|
, isDir = {}
|
|
|
|
// Globbing is a *little* bit different than just matching, in some
|
|
// key ways.
|
|
//
|
|
// First, and importantly, it matters a great deal whether a pattern
|
|
// is "absolute" or "relative". Absolute patterns are patterns that
|
|
// start with / on unix, or a full device/unc path on windows.
|
|
//
|
|
// Second, globs interact with the actual filesystem, so being able
|
|
// to stop searching as soon as a match is no longer possible is of
|
|
// the utmost importance. It would not do to traverse a large file
|
|
// tree, and then eliminate all but one of the options, if it could
|
|
// be possible to skip the traversal early.
|
|
|
|
// Get a Minimatch object from the pattern and options. Then, starting
|
|
// from the options.root or the cwd, read the dir, and do a partial
|
|
// match on all the files if it's a dir, or a regular match if it's not.
|
|
|
|
|
|
function glob (pattern, options, cb) {
|
|
if (typeof options === "function") cb = options, options = {}
|
|
if (!options) options = {}
|
|
|
|
if (typeof options === "number") {
|
|
deprecated()
|
|
return
|
|
}
|
|
|
|
var m = new Glob(pattern, options, cb)
|
|
|
|
if (options.sync) {
|
|
return m.found
|
|
} else {
|
|
return m
|
|
}
|
|
}
|
|
|
|
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 || {}
|
|
|
|
if (!options.hasOwnProperty("maxDepth")) options.maxDepth = 1000
|
|
if (!options.hasOwnProperty("maxLength")) options.maxLength = 4096
|
|
|
|
var cwd = this.cwd = options.cwd =
|
|
options.cwd || process.cwd()
|
|
|
|
this.root = options.root =
|
|
options.root || path.resolve(cwd, "/")
|
|
|
|
if (!pattern) {
|
|
throw new Error("must provide pattern")
|
|
}
|
|
|
|
var mm = this.minimatch = new Minimatch(pattern, options)
|
|
options = this.options = mm.options
|
|
pattern = this.pattern = mm.pattern
|
|
|
|
this.error = null
|
|
this.aborted = false
|
|
|
|
this.matches = new FastList()
|
|
EE.call(this)
|
|
var me = this
|
|
|
|
this._checkedRoot = false
|
|
|
|
// if we have any patterns starting with /, then we need to
|
|
// start at the root. If we don't, then we can take a short
|
|
// cut and just start at the cwd.
|
|
var start = this.cwd
|
|
for (var i = 0, l = this.minimatch.set.length; i < l; i ++) {
|
|
if (this.minimatch.set[i].absolute) {
|
|
start = this.root
|
|
break
|
|
}
|
|
}
|
|
|
|
if (me.options.debug) {
|
|
console.error("start =", start)
|
|
}
|
|
|
|
this._process(start, 1, this._finish.bind(this))
|
|
}
|
|
|
|
Glob.prototype._finish = _finish
|
|
function _finish () {
|
|
var me = this
|
|
if (me.options.debug) {
|
|
console.error("!!! GLOB top level cb", me)
|
|
}
|
|
if (me.options.nonull && me.matches.length === 0) {
|
|
return me.emit("end", [pattern])
|
|
}
|
|
|
|
var found = me.found = me.matches.slice()
|
|
|
|
found = me.found = found.map(function (m) {
|
|
if (m.indexOf(me.options.cwd) === 0) {
|
|
m = m.substr(me.options.cwd.length + 1)
|
|
}
|
|
return m
|
|
})
|
|
|
|
if (!me.options.mark) return next()
|
|
|
|
// mark all directories with a /.
|
|
// This may involve some stat calls for things that are unknown.
|
|
var needStat = []
|
|
found = me.found = found.map(function (f) {
|
|
if (isDir[f] === undefined) needStat.push(f)
|
|
else if (isDir[f] && f.slice(-1) !== "/") f += "/"
|
|
return f
|
|
})
|
|
var c = needStat.length
|
|
if (c === 0) return next()
|
|
|
|
var stat = me.options.follow ? "stat" : "lstat"
|
|
needStat.forEach(function (f) {
|
|
if (me.options.sync) {
|
|
try {
|
|
afterStat(f)(null, fs[stat + "Sync"](f))
|
|
} catch (er) {
|
|
afterStat(f)(er)
|
|
}
|
|
} else fs[stat](f, afterStat(f))
|
|
})
|
|
|
|
function afterStat (f) { return function (er, st) {
|
|
// ignore errors. if the user only wants to show
|
|
// existing files, then set options.stat to exclude anything
|
|
// that doesn't exist.
|
|
if (st && st.isDirectory() && f.substr(-1) !== "/") {
|
|
var i = found.indexOf(f)
|
|
if (i !== -1) {
|
|
found.splice(i, 1, f + "/")
|
|
}
|
|
}
|
|
if (-- c <= 0) return next()
|
|
}}
|
|
|
|
function next () {
|
|
if (!me.options.nosort) {
|
|
found = found.sort(alphasort)
|
|
}
|
|
me.emit("end", found)
|
|
}
|
|
}
|
|
|
|
function alphasort (a, b) {
|
|
a = a.toLowerCase()
|
|
b = b.toLowerCase()
|
|
return a > b ? 1 : a < b ? -1 : 0
|
|
}
|
|
|
|
Glob.prototype.abort = abort
|
|
function abort () {
|
|
this.aborted = true
|
|
this.emit("abort")
|
|
}
|
|
|
|
|
|
Glob.prototype._process = _process
|
|
function _process (f, depth, cb) {
|
|
if (this.aborted) return cb()
|
|
|
|
var me = this
|
|
|
|
// if f matches, then it's a match. emit it, move on.
|
|
// if it *partially* matches, then it might be a dir.
|
|
//
|
|
// possible optimization: don't just minimatch everything
|
|
// against the full pattern. if a bit of the pattern is
|
|
// not magical, it'd be good to reduce the number of stats
|
|
// that had to be made. so, in the pattern: "a/*/b", we could
|
|
// readdir a, then stat a/<child>/b in all of them.
|
|
//
|
|
// however, that'll require a lot of muddying between minimatch
|
|
// and glob, and at least for the time being, it's kind of nice to
|
|
// keep them a little bit separate.
|
|
|
|
// if this thing is a match, then add to the matches list.
|
|
var match = me.minimatch.match(f)
|
|
if (!match) {
|
|
if (me.options.debug) {
|
|
console.error("not a match", f)
|
|
}
|
|
return me._processPartial(f, depth, cb)
|
|
}
|
|
|
|
if (match) {
|
|
if (me.options.debug) {
|
|
console.error(" %s matches %s", f, me.pattern)
|
|
}
|
|
// make sure it exists if asked.
|
|
if (me.options.stat) {
|
|
var stat = me.options.follow ? "stat" : "lstat"
|
|
if (me.options.sync) {
|
|
try {
|
|
afterStat(f)(null, fs[stat + "Sync"](f))
|
|
} catch (ex) {
|
|
afterStat(f)(ex)
|
|
}
|
|
} else fs[stat](f, afterStat(f))
|
|
} else if (me.options.sync) {
|
|
emitMatch()
|
|
} else {
|
|
process.nextTick(emitMatch)
|
|
}
|
|
|
|
return
|
|
|
|
function afterStat (f) { return function (er, st) {
|
|
if (er) return cb()
|
|
isDir[f] = st.isDirectory()
|
|
emitMatch()
|
|
}}
|
|
|
|
function emitMatch () {
|
|
if (me.options.debug) {
|
|
console.error("emitting match", f)
|
|
}
|
|
me.matches.push(f)
|
|
me.emit("match", f)
|
|
// move on, since it might also be a partial match
|
|
// eg, a/**/c matches both a/c and a/c/d/c
|
|
me._processPartial(f, depth, cb)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
Glob.prototype._processPartial = _processPartial
|
|
function _processPartial (f, depth, cb) {
|
|
if (this.aborted) return cb()
|
|
|
|
var me = this
|
|
|
|
var partial = me.minimatch.match(f, true)
|
|
if (!partial) {
|
|
if (me.options.debug) {
|
|
console.error("not a partial", f)
|
|
}
|
|
|
|
// if not a match or partial match, just move on.
|
|
return cb()
|
|
}
|
|
|
|
// partial match
|
|
// however, this only matters if it's a dir.
|
|
//if (me.options.debug)
|
|
if (me.options.debug) {
|
|
console.error("got a partial", f)
|
|
}
|
|
me.emit("partial", f)
|
|
|
|
me._processDir(f, depth, cb)
|
|
}
|
|
|
|
Glob.prototype._processDir = _processDir
|
|
function _processDir (f, depth, cb) {
|
|
if (this.aborted) return cb()
|
|
|
|
// If we're already at the maximum depth, then don't read the dir.
|
|
if (depth >= this.options.maxDepth) return cb()
|
|
|
|
// if the path is at the maximum length, then don't proceed, either.
|
|
if (f.length >= this.options.maxLength) return cb()
|
|
|
|
// now the fun stuff.
|
|
// if it's a dir, then we'll read all the children, and process them.
|
|
// if it's not a dir, or we can't access it, then it'll fail.
|
|
// We log a warning for EACCES and EPERM, but ENOTDIR and ENOENT are
|
|
// expected and fine.
|
|
cb = this._afterReaddir(f, depth, cb)
|
|
if (this.options.sync) return this._processDirSync(f, depth, cb)
|
|
fs.readdir(f, cb)
|
|
}
|
|
|
|
Glob.prototype._processDirSync = _processDirSync
|
|
function _processDirSync (f, depth, cb) {
|
|
try {
|
|
cb(null, fs.readdirSync(f))
|
|
} catch (ex) {
|
|
cb(ex)
|
|
}
|
|
}
|
|
|
|
Glob.prototype._afterReaddir = _afterReaddir
|
|
function _afterReaddir (f, depth, cb) {
|
|
var me = this
|
|
return function afterReaddir (er, children) {
|
|
if (er) switch (er.code) {
|
|
case "UNKNOWN": // probably too deep
|
|
case "ENOTDIR": // completely expected and normal.
|
|
isDir[f] = false
|
|
return cb()
|
|
case "ENOENT": // should never happen.
|
|
default: // some other kind of problem.
|
|
if (!me.options.silent) console.error("glob error", er)
|
|
if (me.options.strict) return cb(er)
|
|
return cb()
|
|
}
|
|
|
|
// at this point, we know it's a dir, so save a stat later if
|
|
// mark is set.
|
|
isDir[f] = true
|
|
|
|
me._processChildren(f, depth, children, cb)
|
|
}
|
|
}
|
|
|
|
Glob.prototype._processChildren = _processChildren
|
|
function _processChildren (f, depth, children, cb) {
|
|
var me = this
|
|
|
|
// note: the file ending with / might match, but only if
|
|
// it's a directory, which we know it is at this point.
|
|
// For example, /a/b/ or /a/b/** would match /a/b/ but not
|
|
// /a/b. Note: it'll get the trailing "/" strictly based
|
|
// on the "mark" param, but that happens later.
|
|
// This is slightly different from bash's glob.
|
|
if (!me.minimatch.match(f) && me.minimatch.match(f + "/")) {
|
|
me.matches.push(f)
|
|
me.emit("match", f)
|
|
}
|
|
|
|
if (-1 === children.indexOf(".")) children.push(".")
|
|
if (-1 === children.indexOf("..")) children.push("..")
|
|
|
|
var count = children.length
|
|
if (me.options.debug) {
|
|
console.error("count=%d %s", count, f, children)
|
|
}
|
|
|
|
if (count === 0) {
|
|
if (me.options.debug) {
|
|
console.error("no children?", children, f)
|
|
}
|
|
return then()
|
|
}
|
|
|
|
children.forEach(function (c) {
|
|
if (f === "/") c = f + c
|
|
else c = f + "/" + c
|
|
|
|
if (me.options.debug) {
|
|
console.error(" processing", c)
|
|
}
|
|
me._process(c, depth + 1, then)
|
|
})
|
|
|
|
function then (er) {
|
|
count --
|
|
if (me.options.debug) {
|
|
console.error("%s THEN %s", f, count, count <= 0 ? "done" : "not done")
|
|
}
|
|
if (me.error) return
|
|
if (er) return me.emit("error", me.error = er)
|
|
if (count <= 0) cb()
|
|
}
|
|
}
|