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.

400 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)])
})
}