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.
399 lines
9.4 KiB
399 lines
9.4 KiB
// This is a JavaScript implementation of the fnmatch-like
|
|
// stuff that git uses in its .gitignore files.
|
|
// See `man 5 gitignore`.
|
|
|
|
module.exports = minimatch
|
|
|
|
var path = require("path")
|
|
, LRU = require("lru-cache")
|
|
|
|
minimatch.filter = function (pattern, options) {
|
|
options = options || {}
|
|
return function (p, i, list) {
|
|
return minimatch(p, pattern, options)
|
|
}
|
|
}
|
|
|
|
minimatch.match = function (list, pattern, options) {
|
|
if (!options) options = {}
|
|
var ret = list.filter(minimatch.filter(pattern, options))
|
|
|
|
// set the null flag to allow empty match sets
|
|
// Note that minimatch itself, and filter(), do not
|
|
// respect this flag, only minimatch.match(list, pattern) does.
|
|
if (!options.null && !ret.length) {
|
|
return [pattern]
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
function minimatch (p, pattern, options) {
|
|
if (typeof pattern !== "string") {
|
|
throw new TypeError("glob pattern string required")
|
|
}
|
|
|
|
options = options || {}
|
|
|
|
// to set the cache, just replace with a different obj
|
|
// supporting set(k,v) and v=get(k) methods.
|
|
var cache = options.cache || minimatch.cache
|
|
if (!cache) cache = minimatch.cache = new LRU(1000)
|
|
|
|
// "" only matches ""
|
|
if (!pattern) return p === ""
|
|
|
|
// comments.
|
|
if (pattern.trim().charAt(0) === "#") return false
|
|
|
|
// check the cache
|
|
var re = cache.get(pattern)
|
|
if (!re && re !== false) {
|
|
cache.set(pattern, re = minimatch.makeRe(pattern, options))
|
|
}
|
|
|
|
if (options.debug) {
|
|
console.error(pattern + "\t" + re, JSON.stringify(p))
|
|
}
|
|
|
|
// some kind of invalid thing
|
|
if (!re) return false
|
|
|
|
|
|
// patterns that end in / can only match dirs
|
|
// however, dirs also match the same thing that *doesn't*
|
|
// end in a slash.
|
|
var match =
|
|
// a/ should not match a/*, but will match */
|
|
// accomplish this by not applying the regexp
|
|
// directly, unless the pattern would match
|
|
// trailing slash'ed things, or the thing isn't
|
|
// a trailing slash, or slashes are opted-in
|
|
( ( options.slash ||
|
|
p.substr(-1) !== "/" ||
|
|
pattern.substr(-1) === "/" )
|
|
&& !!p.match(re) )
|
|
|
|
// a/ should match * or a
|
|
|| ( p.substr(-1) === "/" &&
|
|
!!p.slice(0, -1).match(re) )
|
|
|
|
// a pattern with *no* slashes will match against
|
|
// either the full path, or just the basename.
|
|
|| ( options.matchBase &&
|
|
pattern.indexOf("/") === -1 &&
|
|
path.basename(p).match(re) )
|
|
|
|
//console.error(" MINIMATCH: %j %j %j %j",
|
|
// re.toString(), pattern, p, match)
|
|
return match
|
|
}
|
|
|
|
minimatch.makeRe = makeRe
|
|
function makeRe (pattern, options) {
|
|
options = options || {}
|
|
|
|
var braceDepth = 0
|
|
, re = ""
|
|
, escaping = false
|
|
, oneStar = "[^\\/]*?"
|
|
, twoStar = ".*?"
|
|
, reSpecials = "().*{}+?[]^$/\\"
|
|
, patternListStack = []
|
|
, stateChar
|
|
, negate = false
|
|
, negating = false
|
|
, inClass = false
|
|
, reClassStart = []
|
|
|
|
for ( var i = 0, len = pattern.length, c
|
|
; (i < len) && (c = pattern.charAt(i))
|
|
; i ++ ) {
|
|
|
|
if (options.debug) {
|
|
console.error("%s\t%s %s %j", pattern, i, re, c)
|
|
}
|
|
|
|
switch (c) {
|
|
case "\\":
|
|
if (stateChar) {
|
|
if (stateChar === "*") re += oneStar
|
|
else re += "\\" + stateChar
|
|
stateChar = false
|
|
}
|
|
if (escaping) {
|
|
re += "\\\\" // must match literal \
|
|
escaping = false
|
|
} else {
|
|
escaping = true
|
|
}
|
|
continue
|
|
|
|
case "!":
|
|
if (i === 0 || negating) {
|
|
negate = !negate
|
|
negating = true
|
|
break
|
|
} else {
|
|
negating = false
|
|
}
|
|
// fallthrough
|
|
case "+":
|
|
case "@":
|
|
case "*":
|
|
case "?":
|
|
if (options.debug) {
|
|
console.error("%s\t%s %s %j <-- stateChar", pattern, i, re, c)
|
|
}
|
|
|
|
negating = false
|
|
if (escaping) {
|
|
re += "\\" + c
|
|
escaping = false
|
|
} else if (inClass) {
|
|
re += c
|
|
} else if (c === "*" && stateChar === "*") { // **
|
|
re += twoStar
|
|
stateChar = false
|
|
} else {
|
|
if (stateChar) {
|
|
if (stateChar === "*") re += oneStar
|
|
else if (stateChar === "?") re += "."
|
|
else re += "\\" + stateChar
|
|
}
|
|
stateChar = c
|
|
}
|
|
continue
|
|
|
|
case "(":
|
|
if (escaping) {
|
|
re += "\\("
|
|
escaping = false
|
|
} else if (inClass) {
|
|
re += "("
|
|
} else if (stateChar) {
|
|
plType = stateChar
|
|
patternListStack.push(plType)
|
|
re += stateChar === "!" ? "(?!" : "(?:"
|
|
stateChar = false
|
|
} else {
|
|
re += "\\("
|
|
}
|
|
continue
|
|
|
|
case ")":
|
|
if (escaping || inClass) {
|
|
re += "\\)"
|
|
escaping = false
|
|
} else if (patternListStack.length) {
|
|
re += ")"
|
|
plType = patternListStack.pop()
|
|
switch (plType) {
|
|
case "?":
|
|
case "+":
|
|
case "*": re += plType
|
|
case "!":
|
|
case "@": break
|
|
}
|
|
} else {
|
|
re += "\\)"
|
|
}
|
|
continue
|
|
|
|
case "|":
|
|
if (escaping || inClass) {
|
|
re += "\\|"
|
|
escaping = false
|
|
} else if (patternListStack.length) {
|
|
re += "|"
|
|
} else {
|
|
re += "\\|"
|
|
}
|
|
continue
|
|
|
|
// these are mostly the same in regexp and glob :)
|
|
case "[":
|
|
if (stateChar) {
|
|
// some state-tracking char was before the [
|
|
switch (stateChar) {
|
|
case "*":
|
|
re += oneStar
|
|
break
|
|
case "?":
|
|
re += "."
|
|
break
|
|
default:
|
|
re += "\\"+stateChar
|
|
break
|
|
}
|
|
stateChar = false
|
|
}
|
|
|
|
if (escaping || inClass) {
|
|
re += "\\" + c
|
|
escaping = false
|
|
} else {
|
|
inClass = true
|
|
classStart = i
|
|
reClassStart = re.length
|
|
re += c
|
|
}
|
|
continue
|
|
|
|
case "]":
|
|
// a right bracket shall lose its special
|
|
// meaning and represent itself in
|
|
// a bracket expression if it occurs
|
|
// first in the list. -- POSIX.2 2.8.3.2
|
|
if (i === classStart + 1) escaping = true
|
|
|
|
if (escaping || !inClass) {
|
|
re += "\\" + c
|
|
escaping = false
|
|
} else {
|
|
inClass = false
|
|
re += c
|
|
}
|
|
continue
|
|
|
|
case "{":
|
|
if (escaping || inClass) {
|
|
re += "\\{"
|
|
escaping = false
|
|
} else {
|
|
re += "(?:"
|
|
braceDepth ++
|
|
}
|
|
continue
|
|
|
|
case "}":
|
|
if (escaping || inClass || braceDepth === 0) {
|
|
re += "\\}"
|
|
escaping = false
|
|
} else {
|
|
re += ")"
|
|
braceDepth --
|
|
}
|
|
continue
|
|
|
|
case ",":
|
|
if (escaping || inClass || braceDepth === 0) {
|
|
re += ","
|
|
escaping = false
|
|
} else {
|
|
re += "|"
|
|
}
|
|
continue
|
|
|
|
default:
|
|
if (stateChar) {
|
|
// we had some state-tracking character
|
|
// that wasn't consumed by this pass.
|
|
switch (stateChar) {
|
|
case "*":
|
|
re += oneStar
|
|
break
|
|
case "?":
|
|
re += "."
|
|
break
|
|
default:
|
|
re += "\\"+stateChar
|
|
break
|
|
}
|
|
stateChar = false
|
|
}
|
|
|
|
if (escaping) {
|
|
// no need
|
|
escaping = false
|
|
} else if (reSpecials.indexOf(c) !== -1
|
|
&& !(c === "^" && inClass)) {
|
|
re += "\\"
|
|
}
|
|
|
|
re += c
|
|
|
|
} // switch
|
|
|
|
if (negating && c !== "!") negating = false
|
|
|
|
} // for
|
|
|
|
// handle trailing things that only matter at the very end.
|
|
if (stateChar) {
|
|
// we had some state-tracking character
|
|
// that wasn't consumed by this pass.
|
|
switch (stateChar) {
|
|
case "*":
|
|
re += oneStar
|
|
break
|
|
case "?":
|
|
re += "."
|
|
break
|
|
default:
|
|
re += "\\"+stateChar
|
|
break
|
|
}
|
|
stateChar = false
|
|
} else if (escaping) {
|
|
re += "\\\\"
|
|
}
|
|
|
|
// "[abc" is valid, equivalent to "\[abc"
|
|
if (inClass) {
|
|
// split where the last [ was, and escape it
|
|
// this is a huge pita. We now have to re-walk
|
|
// the contents of the would-be class to re-translate
|
|
// any characters that were passed through as-is
|
|
var cs = re.substr(reClassStart + 1)
|
|
, csOpts = Object.create(options)
|
|
csOpts.partial = true
|
|
|
|
re = re.substr(0, reClassStart) + "\\["
|
|
+ makeRe(cs, csOpts)
|
|
}
|
|
|
|
if (options.partial) return re
|
|
|
|
// don't match "." files unless pattern starts with "."
|
|
if (!options.dot && pattern.charAt(0) !== ".") {
|
|
re = "(?!\\.)" + re
|
|
}
|
|
|
|
// must match entire pattern
|
|
// ending in a * or ** will make it less strict.
|
|
re = "^" + re + "$"
|
|
|
|
// fail on the pattern, but allow anything otherwise.
|
|
if (negate) re = "^(?!" + re + ").*$"
|
|
|
|
// really insane glob patterns can cause bad things.
|
|
var flags = ""
|
|
if (options.nocase) flags += "i"
|
|
|
|
if (options.debug) {
|
|
console.error("/%s/%s", re, flags)
|
|
}
|
|
|
|
try {
|
|
return new RegExp(re, flags)
|
|
} catch(ex) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
// more tests in test/*.js
|
|
var tests = ["{a,b{c,d}}"
|
|
,"a.*$?"
|
|
,"\\{a,b{c,d}}"
|
|
,"a/{c/,}d/{e/,f/{g,h,i}/}k"
|
|
,"!*.bak"
|
|
,"!!*.bak"
|
|
,"!!!*.bak"
|
|
,"\\a\\b\\c\\d"
|
|
]
|
|
tests.forEach(function (t) {
|
|
console.log([t,makeRe(t)])
|
|
})
|
|
}
|
|
|