chain-able-find
Version:
find files synchronously, easily, with a chainable interface
146 lines (124 loc) • 3.97 kB
JavaScript
// @TODO: use chain-able matcher
// https://raw.githubusercontent.com/fitzgen/glob-to-regexp/master/index.js
module.exports = function(glob, opts) {
if (typeof glob !== 'string') {
throw new TypeError('Expected a string')
}
var str = String(glob)
// The regexp we are building, as a string.
var reStr = ''
// Whether we are matching so called "extended" globs (like bash) and should
// support single character matching, matching ranges of characters, group
// matching, etc.
var extended = opts ? !!opts.extended : false
// When globstar is _false_ (default), '/foo/*' is translated a regexp like
// '^\/foo\/.*$' which will match any string beginning with '/foo/'
// When globstar is _true_, '/foo/*' is translated to regexp like
// '^\/foo\/[^/]*$' which will match any string beginning with '/foo/' BUT
// which does not have a '/' to the right of it.
// E.g. with '/foo/*' these will match: '/foo/bar', '/foo/bar.txt' but
// these will not '/foo/bar/baz', '/foo/bar/baz.txt'
// Lastely, when globstar is _true_, '/foo/**' is equivelant to '/foo/*' when
// globstar is _false_
var globstar = opts ? !!opts.globstar : false
// If we are doing extended matching, this boolean is true when we are inside
// a group (eg {*.html,*.js}), and false otherwise.
var inGroup = false
// RegExp flags (eg "i" ) to pass in to RegExp constructor.
var flags = opts && typeof opts.flags === 'string' ? opts.flags : ''
var negated = false
var c
for (var i = 0, len = str.length; i < len; i++) {
c = str[i]
switch (c) {
case '\\':
case '/':
case '$':
case '^':
case '+':
case '.':
case '(':
case ')':
case '=':
case '|':
reStr += '\\' + c
break
case '!':
negated = true
break
case '?':
if (extended) {
reStr += '.'
break
}
case '[':
case ']':
if (extended) {
reStr += c
break
}
case '{':
if (extended) {
inGroup = true
reStr += '('
break
}
case '}':
if (extended) {
inGroup = false
reStr += ')'
break
}
case ',':
if (inGroup) {
reStr += '|'
break
}
reStr += '\\' + c
break
case '*':
// Move over all consecutive "*"'s.
// Also store the previous and next characters
var prevChar = str[i - 1]
var starCount = 1
while (str[i + 1] === '*') {
starCount++
i++
}
var nextChar = str[i + 1]
if (!globstar) {
// globstar is disabled, so treat any number of "*" as one
reStr += '.*'
} else {
// globstar is enabled, so determine if this is a globstar segment
var isGlobstar =
starCount > 1 && // multiple "*"'s
(prevChar === '/' || prevChar === undefined) && // from the start of the segment
(nextChar === '/' || nextChar === undefined) // to the end of the segment
if (isGlobstar) {
// it's a globstar, so match zero or more path segments
reStr += '((?:[^/]*(?:/|$))*)'
i++ // move over the "/"
} else {
// it's not a globstar, so only match one path segment
reStr += '([^/]*)'
}
}
break
default:
reStr += c
}
}
// When regexp 'g' flag is specified don't
// constrain the regular expression with ^ & $
if (!flags || !~flags.indexOf('g')) {
// @NOTE added this negation
// http://stackoverflow.com/questions/977251/regular-expressions-and-negating-a-whole-character-group
if (negated === true) {
// reStr += `(?!.*${match}).+`
reStr = `(?!${reStr}).+`
}
reStr = '^' + reStr + '$'
}
return new RegExp(reStr, flags)
}