Comparing version 0.1.0 to 0.1.1
572
index.js
'use strict'; | ||
var path = require('path'); | ||
var util = require('util'); | ||
var toRegex = require('to-regex'); | ||
var Snapdragon = require('snapdragon'); | ||
var debug = require('debug')('nanomatch'); | ||
var Nanomatch = require('./lib/nanomatch'); | ||
var compilers = require('./lib/compilers'); | ||
var parsers = require('./lib/parsers'); | ||
var utils = require('./lib/utils'); | ||
var regexCache = {}; | ||
var cache = require('./lib/cache'); | ||
var MAX_LENGTH = 1024 * 64; | ||
/** | ||
* Convert the given `extglob` pattern into a regex-compatible string. | ||
* The main function takes a list of strings and one or more | ||
* glob patterns to use for matching. | ||
* | ||
* ```js | ||
* var extglob = require('extglob'); | ||
* var str = extglob('*.!(*a)'); | ||
* console.log(str); | ||
* var nanomatch = require('nanomatch'); | ||
* console.log(nanomatch(['a.js', 'a.txt'], ['*.js'])); | ||
* //=> [ 'a.js' ] | ||
* ``` | ||
* @param {String} `str` | ||
* @param {Array} `list` | ||
* @param {String|Array} `patterns` Glob patterns | ||
* @param {Object} `options` | ||
* @return {String} | ||
* @return {Array} Returns an array of matches | ||
* @api public | ||
*/ | ||
function nanomatch(str, options) { | ||
var matcher = new Nanomatch(options); | ||
var ast = matcher.parse(str, options); | ||
return matcher.compile(ast, options); | ||
function nanomatch(list, patterns, options) { | ||
debug('nanomatch <%s>', patterns); | ||
patterns = utils.arrayify(patterns); | ||
list = utils.arrayify(list); | ||
var len = patterns.length; | ||
if (list.length === 0 || len === 0) { | ||
return []; | ||
} | ||
if (len === 1) { | ||
return nanomatch.match(list, patterns[0], options); | ||
} | ||
var opts = utils.extend({}, options); | ||
var negated = false; | ||
var omit = []; | ||
var keep = []; | ||
var idx = -1; | ||
while (++idx < len) { | ||
var pattern = patterns[idx]; | ||
if (typeof pattern === 'string' && pattern.charCodeAt(0) === 33 /* ! */) { | ||
omit.push.apply(omit, nanomatch.match(list, pattern.slice(1), opts)); | ||
negated = true; | ||
} else { | ||
keep.push.apply(keep, nanomatch.match(list, pattern, opts)); | ||
} | ||
} | ||
// minimatch.match parity | ||
if (negated && keep.length === 0) { | ||
keep = list; | ||
} | ||
var matches = utils.diff(keep, omit); | ||
return opts.nodupes !== false ? utils.unique(matches) : matches; | ||
} | ||
/** | ||
* Takes an array of strings and a glob pattern and returns a new | ||
* array that contains only the strings that match the pattern. | ||
* Similar to the main function, but `pattern` must be a string. | ||
* | ||
* ```js | ||
* var nano = require('nanomatch'); | ||
* console.log(nano.match(['a.a', 'a.b', 'a.c'], '*.!(*a)')); | ||
* //=> ['a.b', 'a.c'] | ||
* var nanomatch = require('nanomatch'); | ||
* console.log(nanomatch.match(['a.a', 'a.aa', 'a.b', 'a.c'], '*.a')); | ||
* //=> ['a.a', 'a.aa'] | ||
* ``` | ||
* @param {Array} `arr` Array of strings to match | ||
* @param {Array} `list` Array of strings to match | ||
* @param {String} `pattern` Glob pattern | ||
* @param {Object} `options` | ||
* @return {Array} | ||
* @return {Array} Returns an array of matches | ||
* @api public | ||
*/ | ||
nanomatch.match = function(files, pattern, options) { | ||
var opts = utils.extend({}, options); | ||
var isMatch = nanomatch.matcher(pattern, opts); | ||
var unixify = utils.unixify(opts); | ||
nanomatch.match = function(list, pattern, options) { | ||
debug('match <%s>', pattern); | ||
list = utils.arrayify(list); | ||
var unixify = utils.unixify(options); | ||
var isMatch = memoize('match', pattern, options, nanomatch.matcher); | ||
var matches = []; | ||
files = utils.arrayify(files); | ||
var len = files.length; | ||
var len = list.length; | ||
var idx = -1; | ||
while (++idx < len) { | ||
nanomatch.file = unixify(files[idx]); | ||
if (isMatch(nanomatch.file)) { | ||
matches.push(nanomatch.file); | ||
var file = list[idx]; | ||
if (file === pattern) { | ||
matches.push(file); | ||
continue; | ||
} | ||
var unix = unixify(file); | ||
if (unix === pattern) { | ||
matches.push(options && options.unixify ? unix : file); | ||
continue; | ||
} | ||
if (isMatch(unix)) { | ||
matches.push(options && options.unixify ? unix : file); | ||
} | ||
} | ||
// if not options were passed, return now | ||
if (typeof options === 'undefined') { | ||
return utils.unique(matches); | ||
} | ||
var opts = utils.extend({}, options); | ||
if (matches.length === 0) { | ||
@@ -70,79 +125,171 @@ if (opts.failglob === true) { | ||
if (opts.nonull === true || opts.nullglob === true) { | ||
return [pattern.split('\\').join('')]; | ||
return [opts.unescape ? utils.unescape(pattern) : pattern]; | ||
} | ||
} | ||
// if `ignore` was defined, diff ignored files | ||
// if `opts.ignore` was defined, diff ignored list | ||
if (opts.ignore) { | ||
var ignore = utils.arrayify(opts.ignore); | ||
delete opts.ignore; | ||
var ignored = nanomatch.matchEach(matches, ignore, opts); | ||
matches = utils.diff(matches, ignored); | ||
matches = nanomatch.not(matches, opts.ignore, opts); | ||
} | ||
return opts.nodupes ? utils.unique(matches) : matches; | ||
return opts.nodupes !== false ? utils.unique(matches) : matches; | ||
}; | ||
/** | ||
* Takes an array of strings and a one or more glob patterns and returns a new | ||
* array with strings that match any of the given patterns. | ||
* Creates a matcher function from the given glob `pattern` and `options`. The returned | ||
* function takes a string to match as its only argument. | ||
* | ||
* ```js | ||
* var nano = require('nanomatch'); | ||
* console.log(nano.matchEach(['a.a', 'a.b', 'a.c'], ['*.!(*a)'])); | ||
* //=> ['a.b', 'a.c'] | ||
* var nanomatch = require('nanomatch'); | ||
* var isMatch = nanomatch.matcher('*.!(*a)'); | ||
* | ||
* console.log(isMatch('a.a')); | ||
* //=> false | ||
* console.log(isMatch('a.b')); | ||
* //=> true | ||
* ``` | ||
* @param {Array} `arr` Array of strings to match | ||
* @param {String} `pattern` Glob pattern | ||
* @param {Object} `options` | ||
* @return {Array} | ||
* @param {String} `options` | ||
* @return {Function} Returns a matcher function. | ||
* @api public | ||
*/ | ||
nanomatch.matchEach = function(files, patterns, options) { | ||
if (!Array.isArray(files)) { | ||
return []; | ||
nanomatch.matcher = function(pattern, options) { | ||
function matcher() { | ||
if (typeof pattern === 'function') { | ||
return pattern; | ||
} | ||
var opts = utils.extend({}, options); | ||
var unixify = utils.unixify(opts); | ||
// pattern is a regex | ||
if (pattern instanceof RegExp) { | ||
return function(fp) { | ||
return pattern.test(unixify(fp)); | ||
}; | ||
} | ||
if (typeof pattern !== 'string') { | ||
throw new TypeError('expected pattern to be a string, regex or function'); | ||
} | ||
// pattern is a non-glob string | ||
if (!utils.hasSpecialChars(pattern)) { | ||
return utils.matchPath(unixify(pattern), opts); | ||
} | ||
// pattern is a glob string | ||
var re = nanomatch.makeRe(pattern, options); | ||
// `options.matchBase` or `options.basename` is defined | ||
if (nanomatch.matchBase(pattern, options)) { | ||
return utils.matchBasename(re); | ||
} | ||
// everything else... | ||
return function(fp) { | ||
return re.test(unixify(fp)); | ||
}; | ||
} | ||
if (!Array.isArray(patterns)) { | ||
return nanomatch.match.apply(this, arguments); | ||
return memoize('matcher', pattern, options, matcher); | ||
}; | ||
/** | ||
* Returns true if the specified `string` matches the given glob `pattern`. | ||
* | ||
* ```js | ||
* var nanomatch = require('nanomatch'); | ||
* console.log(nanomatch.isMatch('a.a', '*.a')); | ||
* //=> true | ||
* console.log(nanomatch.isMatch('a.b', '*.a')); | ||
* //=> false | ||
* ``` | ||
* @param {String} `string` String to match | ||
* @param {String} `pattern` Glob pattern | ||
* @param {String} `options` | ||
* @return {Boolean} Returns true if the string matches the glob pattern. | ||
* @api public | ||
*/ | ||
nanomatch.isMatch = function(str, pattern, options) { | ||
if (typeof str === 'undefined') { | ||
throw new TypeError('expected a string'); | ||
} | ||
var opts = utils.extend({cache: true}, options); | ||
var omit = []; | ||
var keep = []; | ||
if (typeof pattern === 'undefined') { | ||
throw new TypeError('expected pattern to be a string, regex or function'); | ||
} | ||
var len = patterns.length; | ||
var idx = -1; | ||
if (pattern === '' || pattern === ' ') { | ||
return str === pattern; | ||
} | ||
while (++idx < len) { | ||
var pattern = patterns[idx]; | ||
if (typeof pattern === 'string' && pattern.charCodeAt(0) === 33 /* ! */) { | ||
omit.push.apply(omit, nanomatch.match(files, pattern.slice(1), opts)); | ||
} else { | ||
keep.push.apply(keep, nanomatch.match(files, pattern, opts)); | ||
} | ||
if (options && nanomatch.matchBase(pattern, options)) { | ||
str = path.basename(str); | ||
} | ||
return utils.diff(keep, omit); | ||
return nanomatch.matcher(pattern, options)(str); | ||
}; | ||
/** | ||
* Returns true if a file path matches any of the | ||
* given patterns. | ||
* Returns a list of strings that do _not_ match any of the given `patterns`. | ||
* | ||
* @param {String} `fp` The filepath to test. | ||
* ```js | ||
* var nanomatch = require('nanomatch'); | ||
* console.log(nanomatch.not(['a.a', 'b.b', 'c.c'], '*.a')); | ||
* //=> ['b.b', 'c.c'] | ||
* ``` | ||
* @param {Array} `list` Array of strings to match. | ||
* @param {String} `pattern` One or more glob patterns. | ||
* @param {Object} `options` | ||
* @return {Array} Returns an array of strings that do not match the given patterns. | ||
* @api public | ||
*/ | ||
nanomatch.not = function(list, patterns, options) { | ||
var opts = utils.extend({}, options); | ||
var ignore = opts.ignore; | ||
delete opts.ignore; | ||
var matches = utils.diff(list.slice(), nanomatch(list, patterns, opts)); | ||
if (ignore) { | ||
return utils.diff(matches, nanomatch(list, ignore)); | ||
} | ||
return matches; | ||
}; | ||
/** | ||
* Returns true if the given `string` matches any of the given glob `patterns`. | ||
* | ||
* ```js | ||
* var nanomatch = require('nanomatch'); | ||
* console.log(nanomatch.any('a.a', ['b.*', '*.a'])); | ||
* //=> true | ||
* console.log(nanomatch.any('a.a', 'b.*')); | ||
* //=> false | ||
* ``` | ||
* @param {String} `str` The string to test. | ||
* @param {String|Array} `patterns` Glob patterns to use. | ||
* @param {Object} `opts` Options to pass to the `matcher()` function. | ||
* @return {String} | ||
* @param {Object} `options` Options to pass to the `matcher()` function. | ||
* @return {Boolean} Returns true if any patterns match `str` | ||
* @api public | ||
*/ | ||
nanomatch.any = function(filepath, patterns, options) { | ||
if (!Array.isArray(patterns) && typeof patterns !== 'string') { | ||
nanomatch.any = function(str, patterns, options) { | ||
if (typeof patterns === 'string') { | ||
patterns = [patterns]; | ||
} | ||
if (!Array.isArray(patterns)) { | ||
throw new TypeError('expected patterns to be a string or array'); | ||
} | ||
debug('match <%s>', patterns); | ||
var unixify = utils.unixify(opts); | ||
var opts = utils.extend({}, options); | ||
patterns = utils.arrayify(patterns); | ||
filepath = unixify(filepath); | ||
str = unixify(str); | ||
var len = patterns.length; | ||
@@ -152,11 +299,7 @@ | ||
var pattern = patterns[i]; | ||
if (!utils.isString(pattern)) { | ||
continue; | ||
} | ||
if (!utils.isGlob(pattern)) { | ||
if (filepath === pattern) { | ||
if (str === pattern) { | ||
return true; | ||
} | ||
if (opts.contains && filepath.indexOf(pattern) !== -1) { | ||
if (opts.contains && str.indexOf(pattern) !== -1) { | ||
return true; | ||
@@ -166,4 +309,3 @@ } | ||
} | ||
if (nanomatch.isMatch(filepath, pattern, opts)) { | ||
if (nanomatch.isMatch(str, pattern, opts)) { | ||
return true; | ||
@@ -176,10 +318,24 @@ } | ||
/** | ||
* Returns true if the filepath matches the | ||
* given pattern. | ||
* Returns true if the given `string` contains the given pattern. Similar to `.isMatch` but | ||
* the pattern can match any part of the string. | ||
* | ||
* ```js | ||
* var nanomatch = require('nanomatch'); | ||
* console.log(nanomatch.contains('aa/bb/cc', '*b')); | ||
* //=> true | ||
* console.log(nanomatch.contains('aa/bb/cc', '*d')); | ||
* //=> false | ||
* ``` | ||
* @param {String} `str` The string to match. | ||
* @param {String} `pattern` Glob pattern to use for matching. | ||
* @param {Object} `options` | ||
* @return {Boolean} Returns true if the patter matches any part of `str`. | ||
* @api public | ||
*/ | ||
nanomatch.contains = function(filepath, pattern, options) { | ||
if (typeof filepath !== 'string') { | ||
throw new TypeError('expected filepath to be a string'); | ||
nanomatch.contains = function(str, pattern, options) { | ||
if (typeof str !== 'string') { | ||
throw new TypeError('expected a string'); | ||
} | ||
if (typeof pattern !== 'string') { | ||
@@ -194,156 +350,185 @@ throw new TypeError('expected pattern to be a string'); | ||
if (opts.contains && !utils.isGlob(pattern)) { | ||
filepath = utils.unixify(opts)(filepath); | ||
return filepath.indexOf(pattern) !== -1; | ||
str = utils.unixify(opts)(str); | ||
return str.indexOf(pattern) !== -1; | ||
} | ||
return nanomatch.matcher(pattern, opts)(filepath); | ||
return nanomatch.matcher(pattern, opts)(str); | ||
}; | ||
/** | ||
* Returns true if the specified `string` matches the given | ||
* glob `pattern`. | ||
* Returns true if the given pattern and options should enable | ||
* the `matchBase` option. | ||
* @return {Boolean} | ||
*/ | ||
nanomatch.matchBase = function(pattern, options) { | ||
if (pattern && pattern.indexOf('/') !== -1 || !options) return false; | ||
return options.basename === true || options.matchBase === true; | ||
}; | ||
/** | ||
* Filter the keys of the given object with the given `glob` pattern | ||
* and `options`. Does not attempt to match nested keys. If you need this feature, | ||
* use [glob-object][] instead. | ||
* | ||
* ```js | ||
* var nano = require('nanomatch'); | ||
* | ||
* console.log(nano.isMatch('a.a', '*.!(*a)')); | ||
* //=> false | ||
* console.log(nano.isMatch('a.b', '*.!(*a)')); | ||
* //=> true | ||
* var nanomatch = require('nanomatch'); | ||
* var obj = { aa: 'a', ab: 'b', ac: 'c' }; | ||
* console.log(nanomatch.matchKeys(obj, '*b')); | ||
* //=> { ab: 'b' } | ||
* ``` | ||
* @param {String} `string` String to match | ||
* @param {String} `pattern` Glob pattern | ||
* @param {String} `options` | ||
* @return {Boolean} | ||
* @param {Object} `object` | ||
* @param {Array|String} `patterns` One or more glob patterns. | ||
* @return {Object} Returns an object with only keys that match the given patterns. | ||
* @api public | ||
*/ | ||
nanomatch.isMatch = function(filepath, pattern, options) { | ||
if (pattern === '' || pattern === ' ') { | ||
return filepath === pattern; | ||
nanomatch.matchKeys = function(obj, patterns, options) { | ||
if (!utils.isObject(obj)) { | ||
throw new TypeError('expected the first argument to be an object'); | ||
} | ||
var opts = utils.extend({}, options); | ||
if (nanomatch.matchBase(pattern, opts)) { | ||
filepath = path.basename(filepath); | ||
} else if (opts.extname === true) { | ||
filepath = path.extname(filepath); | ||
} else if (opts.dirname === true) { | ||
filepath = path.dirname(filepath); | ||
} | ||
var re = nanomatch.makeRe(pattern, utils.extend({prepend: false}, opts)); | ||
return re.test(filepath); | ||
var keys = nanomatch(Object.keys(obj), patterns, options); | ||
return utils.pick(obj, keys); | ||
}; | ||
/** | ||
* Takes a glob pattern and returns a matcher function. The returned | ||
* function takes the string to match as its only argument. | ||
* Create a regular expression from the given glob `pattern`. | ||
* | ||
* ```js | ||
* var nano = require('nanomatch'); | ||
* var isMatch = nanomatch.matcher('*.!(*a)'); | ||
* | ||
* console.log(isMatch('a.a')); | ||
* //=> false | ||
* console.log(isMatch('a.b')); | ||
* //=> true | ||
* var nanomatch = require('nanomatch'); | ||
* console.log(nanomatch.makeRe('*.js')); | ||
* //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ | ||
* ``` | ||
* @param {String} `pattern` Glob pattern | ||
* @param {String} `options` | ||
* @return {Boolean} | ||
* @param {String} `pattern` The pattern to convert to regex. | ||
* @param {Object} `options` | ||
* @return {RegExp} Returns a regex created from the given pattern. | ||
* @api public | ||
*/ | ||
nanomatch.matcher = function(pattern, options) { | ||
// pattern is a function | ||
if (typeof pattern === 'function') { | ||
nanomatch.makeRe = function(pattern, options) { | ||
if (pattern instanceof RegExp) { | ||
return pattern; | ||
} | ||
var opts = utils.extend({}, options); | ||
var unixify = utils.unixify(opts); | ||
// pattern is a regex | ||
if (pattern instanceof RegExp) { | ||
return function(fp) { | ||
return pattern.test(unixify(fp)); | ||
}; | ||
if (pattern.length > MAX_LENGTH) { | ||
throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); | ||
} | ||
if (typeof pattern !== 'string') { | ||
throw new TypeError('expected pattern to be a string, regex or function'); | ||
function makeRe() { | ||
var res = nanomatch.create(pattern, options); | ||
var opts = utils.extend({strictErrors: false, wrap: false}, options); | ||
return toRegex(res.output, opts); | ||
} | ||
// pattern is a non-glob string | ||
if (!utils.hasSpecialChars(pattern)) { | ||
return utils.matchPath(unixify(pattern), opts); | ||
var regex = memoize('makeRe', pattern, options, makeRe); | ||
if (regex.source.length > MAX_LENGTH) { | ||
throw new SyntaxError('potentially malicious regex detected'); | ||
} | ||
// pattern is a glob string | ||
var re = nanomatch.makeRe(pattern, utils.extend({prepend: false}, opts)); | ||
// `matchBase` is defined | ||
if (nanomatch.matchBase(pattern, options)) { | ||
return utils.matchBasename(re); | ||
} | ||
// `matchBase` is not defined | ||
return function(fp) { | ||
return re.test(unixify(fp)); | ||
}; | ||
return regex; | ||
}; | ||
/** | ||
* Returns true if the given pattern and options should enable | ||
* the `matchBase` option. | ||
* @return {Boolean} | ||
*/ | ||
nanomatch.matchBase = function(pattern, options) { | ||
if (pattern && pattern.indexOf('/') !== -1 || !options) return false; | ||
return options.basename === true | ||
|| options.matchBase === true; | ||
}; | ||
/** | ||
* Create a regular expression from the given string `pattern`. | ||
* Parses the given glob `pattern` and returns an object with the compiled `output` | ||
* and optional source `map`. | ||
* | ||
* ```js | ||
* var nanomatch = require('nanomatch'); | ||
* var re = nanomatch.makeRe('[[:alpha:]]'); | ||
* console.log(re); | ||
* //=> /^(?:[a-zA-Z])$/ | ||
* console.log(nanomatch.create('abc/*.js')); | ||
* // { options: { source: 'string', sourcemap: true }, | ||
* // state: {}, | ||
* // compilers: | ||
* // { ... }, | ||
* // output: '(\\.[\\\\\\/])?abc\\/(?!\\.)(?=.)[^\\/]*?\\.js', | ||
* // ast: | ||
* // { type: 'root', | ||
* // errors: [], | ||
* // nodes: | ||
* // [ ... ], | ||
* // dot: false, | ||
* // input: 'abc/*.js' }, | ||
* // parsingErrors: [], | ||
* // map: | ||
* // { version: 3, | ||
* // sources: [ 'string' ], | ||
* // names: [], | ||
* // mappings: 'AAAA,GAAG,EAAC,kBAAC,EAAC,EAAE', | ||
* // sourcesContent: [ 'abc/*.js' ] }, | ||
* // position: { line: 1, column: 28 }, | ||
* // content: {}, | ||
* // files: {}, | ||
* // idx: 6 } | ||
* ``` | ||
* @param {String} `pattern` The pattern to convert to regex. | ||
* @param {String} `pattern` Glob pattern | ||
* @param {Object} `options` | ||
* @return {RegExp} | ||
* @return {Object} Returns an object with the parsed AST, compiled string and optional source map. | ||
* @api public | ||
*/ | ||
nanomatch.makeRe = function(pattern, options) { | ||
var key = pattern; | ||
var regex; | ||
nanomatch.create = function(pattern, options) { | ||
debug('nanomatch.create <%s>', pattern); | ||
options = options || {}; | ||
if (options) { | ||
for (var prop in options) { | ||
if (options.hasOwnProperty(prop)) { | ||
key += ';' + prop + '=' + String(options[prop]); | ||
} | ||
} | ||
var snapdragon = options.snapdragon || new Snapdragon(options); | ||
compilers(snapdragon); | ||
parsers(snapdragon); | ||
var ast = snapdragon.parse(pattern, options); | ||
ast.input = pattern; | ||
var res = snapdragon.compile(ast, options); | ||
return res; | ||
}; | ||
/** | ||
* Memoize a generated regex or function | ||
*/ | ||
function memoize(type, pattern, options, fn) { | ||
if (!utils.isString(pattern)) { | ||
return fn(pattern, options); | ||
} | ||
options = options || {}; | ||
if (options.cache !== false && regexCache.hasOwnProperty(key)) { | ||
return regexCache[key]; | ||
var key = createKey(pattern, options); | ||
if (cache.has(type, key)) { | ||
return cache.get(type, key); | ||
} | ||
var nm = new Nanomatch(options); | ||
regex = regexCache[key] = nm.makeRe(pattern, options); | ||
return regex; | ||
}; | ||
var val = fn(pattern, options); | ||
if (options && options.cache === false) { | ||
return val; | ||
} | ||
val.key = key; | ||
cache.set(type, key, val); | ||
return val; | ||
} | ||
/** | ||
* Expose `Nanomatch` constructor | ||
* Create the key to use for memoization. The key is generated | ||
* by iterating over the options and concatenating key-value pairs | ||
* to the pattern string. | ||
*/ | ||
function createKey(pattern, options) { | ||
var key = pattern; | ||
if (typeof options === 'undefined') { | ||
return key; | ||
} | ||
for (var prop in options) { | ||
if (options.hasOwnProperty(prop)) { | ||
key += ';' + prop + '=' + String(options[prop]); | ||
} | ||
} | ||
return key; | ||
} | ||
/** | ||
* Expose parser, compiler and constructor on `nanomatch` | ||
*/ | ||
nanomatch.compilers = compilers; | ||
nanomatch.parsers = parsers; | ||
/** | ||
* Expose `nanomatch` | ||
* @type {Function} | ||
@@ -353,4 +538,1 @@ */ | ||
module.exports = nanomatch; | ||
module.exports.Nanomatch = Nanomatch; | ||
module.exports.compilers = compilers; | ||
module.exports.parsers = parsers; |
'use strict'; | ||
var utils = require('./utils'); | ||
/** | ||
* Nanomatch compilers | ||
*/ | ||
module.exports = function(nanomatch) { | ||
var state = {}; | ||
var star = '[^/]*?'; | ||
var star = '[^\\/]*?'; | ||
@@ -12,13 +13,2 @@ nanomatch.compiler | ||
/** | ||
* beginning-of-string | ||
*/ | ||
.set('bos', function(node) { | ||
return this.emit(node.val, node); | ||
}) | ||
.set('noop', function(node) { | ||
return this.emit(node.val, node); | ||
}) | ||
/** | ||
* Negation / escaping | ||
@@ -28,17 +18,15 @@ */ | ||
.set('not', function(node) { | ||
return this.emit('', node); | ||
return this.emit(node.val, node); | ||
}) | ||
.set('escape', function(node) { | ||
if (/^\w/.test(node.val)) { | ||
return this.emit(node.val); | ||
} | ||
return this.emit('\\' + node.val, node); | ||
var val = this.options.unescape ? node.ch : node.val; | ||
return this.emit(val, node); | ||
}) | ||
/** | ||
* Text | ||
* Regex | ||
*/ | ||
.set('text', function(node) { | ||
return this.emit(node.val, node); | ||
.set('dollar', function(node) { | ||
return this.emit('\\' + node.val, node); | ||
}) | ||
@@ -51,25 +39,52 @@ | ||
.set('dot', function(node) { | ||
if (node.dotfiles === true) { | ||
this.dotfiles = true; | ||
} | ||
return this.emit('\\' + node.val, node); | ||
}) | ||
.set('dots', function(node) { | ||
return this.emit('[.]{2}', node); | ||
}) | ||
/** | ||
* Slash: "/" | ||
* Slashes: "/" and "\" | ||
*/ | ||
.set('slash', function(node) { | ||
if (node.parsed === '**') { | ||
return this.emit('/?', node); | ||
.set('slash', function(node, nodes, i) { | ||
var parsed = node.parsed; | ||
if (parsed === '**') { | ||
this.output = '(' + this.output; | ||
return this.emit('\\/)?', node); | ||
} | ||
return this.emit('/', node); | ||
if (parsed === '!**') { | ||
return this.emit('\\/?', node); | ||
} | ||
return this.emit('\\/', node); | ||
}) | ||
/** | ||
* Square: "[/]" | ||
* Square brackets | ||
*/ | ||
.set('square', function(node) { | ||
var val = /^\w/.test(node.val) ? node.val : '\\' + node.val; | ||
.set('bracket', function(node) { | ||
var close = node.close; | ||
var open = !node.escaped ? '[' : '\\['; | ||
var prefix = node.prefix; | ||
var inner = node.inner; | ||
var val = node.val; | ||
var len = inner.length; | ||
if (inner !== '\\\\' && len > 1) { | ||
inner = inner.replace(/\\/, '\\\\'); | ||
} | ||
if (inner === ']-') { | ||
inner = '\\]\\-'; | ||
} | ||
if (prefix && inner.indexOf('.') === -1) { | ||
inner += '.'; | ||
} | ||
if (prefix && inner.indexOf('/') === -1) { | ||
inner += '/'; | ||
} | ||
val = open + prefix + inner + close; | ||
return this.emit(val, node); | ||
@@ -79,7 +94,8 @@ }) | ||
/** | ||
* Plus | ||
* Square: "[.]" (only matches a single character in brackets) | ||
*/ | ||
.set('plus', function(node) { | ||
return this.emit('\\' + node.val, node); | ||
.set('square', function(node) { | ||
var val = !/^\w/.test(node.val) ? '\\' + node.val : node.val; | ||
return this.emit(val, node); | ||
}) | ||
@@ -92,10 +108,13 @@ | ||
.set('qmark', function(node) { | ||
var prefix = this.options.dot ? '[^/]' : '[^/.]'; | ||
var val = this.options.dot ? '[^/\\\\]' : '[^/.\\\\]'; | ||
var prev = this.prev(); | ||
if (prev.type === 'text') { | ||
return this.emit(node.val, node); | ||
if (prev.type === 'text' && prev.val) { | ||
return this.emit(val, node); | ||
} | ||
var val = prefix + '{' + node.val.length + '}'; | ||
if (node.val.length > 1) { | ||
val = '.{' + node.val.length + '}'; | ||
} | ||
if (prev.type === 'bos' && !this.options.dot) { | ||
@@ -108,15 +127,38 @@ val = '(?!\\.)' + val; | ||
/** | ||
* Plus | ||
*/ | ||
.set('plus', function(node) { | ||
var prev = node.parsed.slice(-1); | ||
if ((prev === ']' || prev === ')')) { | ||
return this.emit(node.val, node); | ||
} | ||
if (!this.output || (/[?*+]/.test(ch) && node.parent.type !== 'bracket')) { | ||
return this.emit('\\+', node); | ||
} | ||
var ch = this.output.slice(-1); | ||
if (/\w/.test(ch) && !node.inside) { | ||
return this.emit('+\\+?', node); | ||
} | ||
return this.emit('+', node); | ||
}) | ||
/** | ||
* globstar: '**' | ||
*/ | ||
.set('globstar', function(node) { | ||
var prevCh = this.output[this.output.length - 1]; | ||
var nextCh = node.rest.charAt(0); | ||
.set('globstar', function(node, nodes, i) { | ||
var prev = this.prev(); | ||
var type = prev.type; | ||
var val = node.val; | ||
if (prev.type !== 'slash' && prev.type !== 'bos') { | ||
var parsed = node.parsed; | ||
if (parsed.charAt(0) === '!') { | ||
parsed = parsed.slice(1); | ||
} | ||
if (parsed && type !== 'slash' && type !== 'bos') { | ||
val = star; | ||
} else { | ||
val = !this.options.dot | ||
val = this.options.dot !== true | ||
? '(?:(?!(?:\\/|^)\\.).)*?' | ||
@@ -126,6 +168,13 @@ : '(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?'; | ||
if (prevCh === '/' && nextCh === '/') { | ||
var prior = nodes[i - 2] || {}; | ||
if (type === 'slash' && node.rest.charAt(0) === '/' && prior.type !== 'qmark') { | ||
this.output += '?'; | ||
} else if (!node.rest) { | ||
} | ||
if ((type === 'slash' || type === 'bos') && this.options.dot !== true) { | ||
this.output += '(?!\\.)'; | ||
} | ||
return this.emit(val, node); | ||
@@ -139,21 +188,32 @@ }) | ||
.set('star', function(node) { | ||
var prevCh = this.output[this.output.length - 1]; | ||
var nextCh = node.rest.charAt(0); | ||
var isAlpha = prevCh && /[\w.]/.test(prevCh) && nextCh && /[\w.]/.test(nextCh); | ||
var prev = this.prev(); | ||
var next = this.next(); | ||
var type = prev.type; | ||
var prefix = !this.dot && !isAlpha && node.rest | ||
var prefix = !this.dotfiles && type !== 'text' && type !== 'escape' | ||
? (this.options.dot ? '(?!(?:^|\\/)\\.{1,2}(?:$|\\/))(?=.)' : '(?!\\.)') | ||
: ''; | ||
return this.emit(prefix + star, node); | ||
var isStart = type === 'slash' || type === 'bos'; | ||
if (prefix !== '(?!\\.)' && isStart && type !== 'dot') { | ||
prefix += '(?!\\.([.\/]|$))'; | ||
} | ||
if (prefix.indexOf('(?=.)') === -1 && isStart) { | ||
prefix += '(?=.)'; | ||
} | ||
var val = prefix + star; | ||
if (!isStart && next.type !== 'eos') { | ||
val = '(' + val + ')?'; | ||
} | ||
return this.emit(val, node); | ||
}) | ||
/** | ||
* Misc | ||
* Text | ||
*/ | ||
.set('dollar', function(node) { | ||
return this.emit('\\' + node.val, node); | ||
}) | ||
.set('colon', function(node) { | ||
.set('text', function(node) { | ||
return this.emit(node.val, node); | ||
@@ -167,4 +227,7 @@ }) | ||
.set('eos', function(node) { | ||
if (typeof this.ast.strict === 'undefined' && this.options.strictOpen !== true) { | ||
this.output = '(\\.[\\\\\\/])?' + this.output; | ||
} | ||
return this.emit(node.val, node); | ||
}); | ||
}; |
'use strict'; | ||
var utils = require('./utils'); | ||
var regex = require('regex-not'); | ||
var isOdd = require('is-odd'); | ||
/** | ||
* Negation regex cache | ||
*/ | ||
var cache = {}; | ||
/** | ||
* Characters to use in negation regex (we want to "not" match | ||
* characters that are matched by other parsers) | ||
*/ | ||
var NOT_REGEX = '[!*+?$\\[/.\\\\]+'; | ||
/** | ||
* Regex | ||
*/ | ||
var not = createRegex(NOT_REGEX); | ||
/** | ||
* Nanomatch parsers | ||
*/ | ||
module.exports = function(nanomatch) { | ||
nanomatch.parser | ||
/** | ||
* Character parsers | ||
* Beginning-of-string | ||
*/ | ||
.capture('dot', /^\./) | ||
.capture('plus', /^\+(?!\()/) | ||
.capture('slash', /^\//) | ||
.capture('qmark', /^\?+(?!\()/) | ||
.capture('globstar', /^\*{2}(?!\*)/) | ||
.capture('star', /^(?:\*(?!\*)|[*]{3,})/) | ||
.set('square', function() { | ||
.capture('bos', function() { | ||
if (this.parsed) return; | ||
var pos = this.position(); | ||
var m = this.match(/^\[(.)\]/); | ||
var m = this.match(/^\.[\\/]/); | ||
if (!m) return; | ||
var val = '\\.\\/'; | ||
var strict = true; | ||
if (this.options.strictOpen === false) { | ||
val = '(\\.\\/|^)'; | ||
strict = false; | ||
} | ||
this.ast.strictOpen = strict; | ||
return pos({ | ||
type: 'square', | ||
val: m[1] | ||
type: 'bos', | ||
val: val | ||
}); | ||
@@ -31,15 +58,37 @@ }) | ||
/** | ||
* Parse negations | ||
* Escape: "\\." | ||
*/ | ||
.set('not', function() { | ||
.capture('escape', function() { | ||
var pos = this.position(); | ||
var m = this.match(/^\\(.)/); | ||
if (!m) return; | ||
var ch = !/[*?+]/.test(m[1]) ? m[1] : m[0]; | ||
return pos({ | ||
type: 'escape', | ||
val: m[0], | ||
ch: ch | ||
}); | ||
}) | ||
/** | ||
* Negations: "!" | ||
*/ | ||
.capture('not', function() { | ||
var parsed = this.parsed; | ||
var pos = this.position(); | ||
var m = this.match(/^\!/); | ||
if (!m || !m[0]) return; | ||
var m = this.match(/^\!+/); | ||
if (!m) return; | ||
var prev = this.prev(); | ||
var val = m[0]; | ||
var isNegated = isOdd(val.length); | ||
if (parsed === '' && !isNegated) { | ||
val = ''; | ||
} | ||
var node = pos({ | ||
type: 'not', | ||
val: m[0] | ||
val: val | ||
}); | ||
@@ -49,3 +98,3 @@ | ||
// so we need to wrap the result in a negation regex | ||
if (!parsed) { | ||
if (parsed === '' && isNegated && !this.options.nonegate) { | ||
this.bos.val = '(?!^(?:'; | ||
@@ -55,19 +104,128 @@ this.append = ')$).*'; | ||
} | ||
utils.define(node, 'parent', prev); | ||
prev.nodes.push(node); | ||
return node; | ||
}) | ||
.set('text', function() { | ||
/** | ||
* Dot: "." | ||
*/ | ||
.capture('dot', function() { | ||
var parsed = this.parsed; | ||
var pos = this.position(); | ||
var re = /^[-\[({})\]$^`%&;#~=\d\w|,]+/; | ||
var m = this.match(re); | ||
var m = this.match(/^\.+/); | ||
if (!m) return; | ||
var val = m[0]; | ||
this.ast.dot = val === '.' && (parsed === '' || parsed.slice(-1) === '/'); | ||
return pos({ | ||
type: 'dot', | ||
dotfiles: this.ast.dot, | ||
val: val | ||
}); | ||
}) | ||
.capture('plus', /^\+/) | ||
.capture('qmark', /^\?+/) | ||
.capture('globstar', function() { | ||
var pos = this.position(); | ||
var m = this.match(/^\*{2}(?!\*)(?=\/|$)/); | ||
if (!m) return; | ||
while (this.input.slice(0, 4) === '/**/') { | ||
this.input = this.input.slice(4); | ||
} | ||
return pos({ | ||
type: 'globstar', | ||
val: '**' | ||
}); | ||
}) | ||
.capture('star', /^(?:\*(?!\*)|[*]{3,}|[*]{2}(?!\/|$))/) | ||
.capture('slash', /^\//) | ||
.capture('square', function() { | ||
var pos = this.position(); | ||
var m = this.match(/^\[(?![\^!])(.)\]/); | ||
if (!m) return; | ||
return pos({ | ||
type: 'square', | ||
val: m[1] | ||
}); | ||
}) | ||
/** | ||
* Brackets: "[...]" (can be overridden by parsers in expand-brackets) | ||
*/ | ||
.capture('bracket', function() { | ||
var pos = this.position(); | ||
var m = this.match(/^(?:\[([!^]?)([^\]]{2,}|\]\-)(\]|[^*+?]+)|\[)/); | ||
if (!m) return; | ||
var val = m[0]; | ||
var prefix = m[1] ? '^' : ''; | ||
var inner = m[2] || ''; | ||
var close = m[3] || ''; | ||
return pos({ | ||
type: 'bracket', | ||
val: val, | ||
escaped: close !== ']', | ||
prefix: prefix, | ||
inner: inner, | ||
close: close | ||
}); | ||
}) | ||
/** | ||
* Dollar: "$" | ||
*/ | ||
.capture('dollar', function() { | ||
var pos = this.position(); | ||
var m = this.match(/^[$]/); | ||
if (!m) return; | ||
return pos({ | ||
type: 'dollar', | ||
val: m[0] | ||
}); | ||
}) | ||
/** | ||
* Text | ||
*/ | ||
.capture('text', function() { | ||
var pos = this.position(); | ||
var m = this.match(not); | ||
if (!m || !m[0]) return; | ||
return pos({ | ||
type: 'text', | ||
val: m[0] | ||
}); | ||
}) | ||
}); | ||
}; | ||
/** | ||
* Create and cache negation regex | ||
*/ | ||
function createRegex(str) { | ||
if (cache.hasOwnProperty(str)) { | ||
return cache[str]; | ||
} | ||
var opts = {contains: true, strictClose: false}; | ||
var re = regex(str, opts); | ||
cache[str] = re; | ||
return re; | ||
} | ||
/** | ||
* Expose negation string | ||
*/ | ||
module.exports.not = NOT_REGEX; |
120
lib/utils.js
'use strict'; | ||
var isWindows = process.platform === 'win32'; | ||
var path = require('path'); | ||
var utils = module.exports; | ||
utils.fill = require('fill-array'); | ||
utils.unique = require('array-unique'); | ||
utils.define = require('define-property'); | ||
utils.extend = require('extend-shallow'); | ||
utils.repeat = require('repeat-string'); | ||
utils.normalize = require('normalize-path'); | ||
utils.isGlob = require('is-glob'); | ||
utils.diff = require('arr-diff'); | ||
var unixifyCache = {}; | ||
/** | ||
* Create a negation regex from the given string | ||
* @param {String} `str` | ||
* @return {RegExp} | ||
* Utils | ||
*/ | ||
utils.not = function(str) { | ||
return '^((?!(?:' + str + ')).)*'; | ||
}; | ||
exports.define = require('define-property'); | ||
exports.diff = require('arr-diff'); | ||
exports.extend = require('extend-shallow'); | ||
exports.isGlob = require('is-glob'); | ||
exports.isObject = require('isobject'); | ||
exports.normalize = require('normalize-path'); | ||
exports.pick = require('object.pick'); | ||
exports.union = require('arr-union'); | ||
exports.unique = require('array-unique'); | ||
utils.expand = function(str) { | ||
var segs = str.split(',').filter(Boolean); | ||
var arr = segs.slice(); | ||
/** | ||
* Cast `val` to an array | ||
* @return {Array} | ||
*/ | ||
if (arr.length === 1) { | ||
arr = str.split('..'); | ||
if (arr.length > 1) { | ||
segs = utils.fill.apply(null, arr); | ||
} | ||
exports.arrayify = function(val) { | ||
if (!Array.isArray(val)) { | ||
return [val]; | ||
} | ||
return segs; | ||
return val; | ||
}; | ||
utils.arrayify = function(val) { | ||
return val ? (Array.isArray(val) ? val : [val]) : []; | ||
}; | ||
/** | ||
@@ -49,3 +36,3 @@ * Return true if `val` is a non-empty string | ||
utils.isString = function(val) { | ||
exports.isString = function(val) { | ||
return val && typeof val === 'string'; | ||
@@ -55,12 +42,6 @@ }; | ||
/** | ||
* Get the last element from `array` | ||
* @param {Array} `array` | ||
* @return {*} | ||
* Returns true if the given `str` has special characters | ||
*/ | ||
utils.last = function(arr) { | ||
return arr[arr.length - 1]; | ||
}; | ||
utils.hasSpecialChars = function(str) { | ||
exports.hasSpecialChars = function(str) { | ||
return /(?:(^|\/)[!.]|[*?()\[\]{}]|[+@]\()/.test(str); | ||
@@ -77,3 +58,3 @@ }; | ||
utils.matchBasename = function(re) { | ||
exports.matchBasename = function(re) { | ||
return function(filepath) { | ||
@@ -92,6 +73,6 @@ return re.test(filepath) || re.test(path.basename(filepath)); | ||
utils.matchPath = function(pattern, options) { | ||
exports.matchPath = function(pattern, options) { | ||
return (options && options.contains) | ||
? utils.containsPath(pattern, options) | ||
: utils.equalsPath(pattern, options); | ||
? exports.containsPath(pattern, options) | ||
: exports.equalsPath(pattern, options); | ||
}; | ||
@@ -107,3 +88,3 @@ | ||
utils.equalsPath = function(pattern, options) { | ||
exports.equalsPath = function(pattern, options) { | ||
return function(filepath) { | ||
@@ -126,3 +107,3 @@ if (options.nocase === true) { | ||
utils.containsPath = function(pattern) { | ||
exports.containsPath = function(pattern) { | ||
return function(filepath) { | ||
@@ -133,5 +114,17 @@ return pattern.indexOf(filepath) !== -1; | ||
utils.stripPrefix = function(filepath, options) { | ||
var opts = utils.extend({}, options); | ||
if (opts.normalize !== true) { | ||
/** | ||
* Strip backslashes from a pattern | ||
*/ | ||
exports.unescape = function(str) { | ||
return str.replace(/\\([^\\*])/g, '$1'); | ||
}; | ||
/** | ||
* Strip the prefix from a filepath | ||
*/ | ||
exports.stripPrefix = function(filepath, options) { | ||
var opts = exports.extend({}, options); | ||
if (path.sep !== '\\' && opts.normalize !== true && opts.strictOpen !== true) { | ||
return filepath; | ||
@@ -141,3 +134,4 @@ } | ||
filepath = String(filepath || ''); | ||
if (filepath.slice(0, 2) === './') { | ||
var prefix = filepath.slice(0, 2); | ||
if (prefix === './' || prefix === '.\\') { | ||
return filepath.slice(2); | ||
@@ -153,13 +147,15 @@ } | ||
utils.unixify = function(options) { | ||
var opts = utils.extend({}, options); | ||
exports.unixify = function(options) { | ||
var unixify; | ||
var key = ''; | ||
for (var prop in opts) { | ||
if (opts.hasOwnProperty(prop)) { | ||
key += ':' + prop + ':' + String(opts[prop]); | ||
if (options) { | ||
for (var prop in options) { | ||
if (options.hasOwnProperty(prop)) { | ||
key += ':' + prop + ':' + String(options[prop]); | ||
} | ||
} | ||
} | ||
var opts = exports.extend({}, options); | ||
if (unixifyCache.hasOwnProperty(key)) { | ||
@@ -169,5 +165,5 @@ return unixifyCache[key]; | ||
if (path.sep !== '/' || opts.unixify === true) { | ||
if (path.sep !== '/' || opts.unixify === true || opts.normalize === true) { | ||
unixify = function(filepath) { | ||
return utils.normalize(utils.stripPrefix(filepath, opts), false); | ||
return exports.normalize(exports.stripPrefix(filepath, opts), false); | ||
}; | ||
@@ -177,8 +173,14 @@ | ||
unixify = function(filepath) { | ||
return utils.stripPrefix(filepath, opts).replace(/\\([-.\w])/g, '$1'); | ||
return exports.stripPrefix(filepath, opts).replace(/\\([-.\w])/g, '$1'); | ||
}; | ||
} else if (opts.normalize === true) { | ||
unixify = function(filepath) { | ||
return exports.stripPrefix(filepath, opts); | ||
}; | ||
} else { | ||
// noop | ||
unixify = function(filepath) { | ||
return utils.stripPrefix(filepath, opts); | ||
return filepath; | ||
}; | ||
@@ -185,0 +187,0 @@ } |
{ | ||
"name": "nanomatch", | ||
"description": "Fast, minimal glob matcher for node.js. ", | ||
"version": "0.1.0", | ||
"description": "Fast, minimal glob matcher for node.js. Complete Bash 4.3 wildcard support (no support for exglobs, brackets or braces)", | ||
"version": "0.1.1", | ||
"homepage": "https://github.com/jonschlinkert/nanomatch", | ||
@@ -14,5 +14,3 @@ "author": "Jon Schlinkert (https://github.com/jonschlinkert)", | ||
"index.js", | ||
"lib", | ||
"LICENSE", | ||
"README.md" | ||
"lib" | ||
], | ||
@@ -28,2 +26,3 @@ "main": "index.js", | ||
"arr-diff": "^3.0.0", | ||
"arr-union": "^3.1.0", | ||
"array-unique": "^0.3.2", | ||
@@ -33,38 +32,92 @@ "debug": "^2.2.0", | ||
"extend-shallow": "^2.0.1", | ||
"fill-array": "^1.0.0", | ||
"fragment-cache": "^0.1.0", | ||
"is-glob": "^3.0.0", | ||
"is-odd": "^0.1.0", | ||
"isobject": "^2.1.0", | ||
"normalize-path": "^2.0.1", | ||
"repeat-string": "^1.5.4", | ||
"snapdragon": "^0.5.0" | ||
"object.pick": "^1.1.2", | ||
"regex-not": "^1.0.0", | ||
"snapdragon": "^0.7.2", | ||
"to-regex": "^2.1.0" | ||
}, | ||
"devDependencies": { | ||
"cross-spawn": "^4.0.0", | ||
"bash-glob": "^0.1.1", | ||
"delete": "^0.3.2", | ||
"gulp-format-md": "^0.1.10", | ||
"mocha": "^3.0.2", | ||
"yargs-parser": "^3.2.0" | ||
"fs-exists-sync": "^0.1.0", | ||
"gulp": "^3.9.1", | ||
"gulp-eslint": "^3.0.1", | ||
"gulp-format-md": "^0.1.11", | ||
"gulp-istanbul": "^1.1.1", | ||
"gulp-mocha": "^3.0.1", | ||
"gulp-unused": "^0.2.0", | ||
"helper-changelog": "^0.3.0", | ||
"micromatch": "^2.3.11", | ||
"minimatch": "^3.0.3", | ||
"mkdirp": "^0.5.1", | ||
"mocha": "^3.1.0", | ||
"multimatch": "^2.1.0", | ||
"write": "^0.3.2", | ||
"yargs-parser": "^4.0.2" | ||
}, | ||
"keywords": [ | ||
"nanomatch" | ||
"bash", | ||
"expand", | ||
"expansion", | ||
"expression", | ||
"file", | ||
"files", | ||
"filter", | ||
"find", | ||
"glob", | ||
"globbing", | ||
"globs", | ||
"globstar", | ||
"match", | ||
"matcher", | ||
"matches", | ||
"matching", | ||
"micromatch", | ||
"minimatch", | ||
"multimatch", | ||
"nanomatch", | ||
"path", | ||
"pattern", | ||
"patterns", | ||
"regex", | ||
"regexp", | ||
"regular", | ||
"shell", | ||
"wildcard" | ||
], | ||
"verb": { | ||
"toc": false, | ||
"layout": "default", | ||
"toc": true, | ||
"layout": "common-minimal", | ||
"tasks": [ | ||
"readme" | ||
], | ||
"helpers": [ | ||
"helper-changelog" | ||
], | ||
"plugins": [ | ||
"gulp-format-md" | ||
], | ||
"lint": { | ||
"reflinks": true | ||
}, | ||
"related": { | ||
"list": [] | ||
"list": [ | ||
"expand-brackets", | ||
"extglob", | ||
"micromatch" | ||
] | ||
}, | ||
"reflinks": [ | ||
"expand-brackets", | ||
"expand-tilde", | ||
"glob-object", | ||
"micromatch", | ||
"verb", | ||
"verb-generate-readme" | ||
] | ||
], | ||
"lint": { | ||
"reflinks": true | ||
} | ||
} | ||
} | ||
} |
418
README.md
# nanomatch [![NPM version](https://img.shields.io/npm/v/nanomatch.svg?style=flat)](https://www.npmjs.com/package/nanomatch) [![NPM downloads](https://img.shields.io/npm/dm/nanomatch.svg?style=flat)](https://npmjs.org/package/nanomatch) [![Build Status](https://img.shields.io/travis/jonschlinkert/nanomatch.svg?style=flat)](https://travis-ci.org/jonschlinkert/nanomatch) | ||
> Fast, minimal glob matcher for node.js. | ||
> Fast, minimal glob matcher for node.js. Complete Bash 4.3 wildcard support (no support for exglobs, brackets or braces) | ||
## Table of Contents | ||
* [Install](#install) | ||
- [nanomatch [![NPM version](https://badge.fury.io/js/nanomatch.svg)](https://npmjs.org/package/nanomatch) [![Build Status](https://travis-ci.org/jonschlinkert/nanomatch.svg?branch=master)](https://travis-ci.org/jonschlinkert/nanomatch)](#nanomatch-npm-versionhttpsbadgefuryiojsnanomatchsvghttpsnpmjsorgpackagenanomatch-build-statushttpstravis-ciorgjonschlinkertnanomatchsvgbranchmasterhttpstravis-ciorgjonschlinkertnanomatch) | ||
* [Installation](#installation) | ||
* [Usage](#usage) | ||
- [What is nanomatch?](#what-is-nanomatch) | ||
- [Getting started](#getting-started) | ||
- [API](#api) | ||
- [Features](#features) | ||
* [Globbing reference](#globbing-reference) | ||
* [Bash pattern matching](#bash-pattern-matching) | ||
- [Benchmarks](#benchmarks) | ||
* [Running benchmarks](#running-benchmarks) | ||
* [Latest results](#latest-results) | ||
- [History](#history) | ||
- [About](#about) | ||
* [Related projects](#related-projects) | ||
* [Contributing](#contributing) | ||
* [Running tests](#running-tests) | ||
* [Author](#author) | ||
* [License](#license) | ||
* [About](#about) | ||
+ [Related projects](#related-projects) | ||
+ [Contributing](#contributing) | ||
+ [Building docs](#building-docs) | ||
+ [Running tests](#running-tests) | ||
+ [Author](#author) | ||
+ [License](#license-1) | ||
_(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ | ||
## Install | ||
## What is nanomatch? | ||
Install with [npm](https://www.npmjs.com/): | ||
Nanomatch is a fast and accurate glob matcher with full support for standard Bash glob features, including the following "metacharacters": `*`, `**`, `?` and `[...]`. | ||
```sh | ||
$ npm install --save nanomatch | ||
See the [features section](#features) for more info about features. | ||
**How is this different from [micromatch](https://github.com/jonschlinkert/micromatch)?** | ||
Nanomatch only provides wildcard matching, which represents only 1 of the 5 matching "types" offered by micromatch. The others are listed in the [features](#features) section. | ||
Nanomatch will also provide the wildcard matching functionality to micromatch, starting with v3.0.0. | ||
## Getting started | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
// the main export is a function that takes an array of strings to match | ||
// and one or more patterns to use for matching | ||
nanomatch(list, patterns[, options]); | ||
``` | ||
# nanomatch [![NPM version](https://badge.fury.io/js/nanomatch.svg)](https://npmjs.org/package/nanomatch) [![Build Status](https://travis-ci.org/jonschlinkert/nanomatch.svg?branch=master)](https://travis-ci.org/jonschlinkert/nanomatch) | ||
**Params** | ||
> Fast, minimal glob matcher for node.js. | ||
* `list` **{String|Array}**: One or more strings to match against. This is often a list of files. | ||
* `patterns` **{String|Array}**: One or more [glob paterns](#features) to use for matching. | ||
* `options` **{Object}**: Visit the API to learn about available options. | ||
## Installation | ||
**Example** | ||
```sh | ||
$ npm install --save nanomatch | ||
```js | ||
var nm = require('nanomatch'); | ||
console.log(nm(['a', 'b/b', 'c/c/c'], '*')); | ||
//=> ['a'] | ||
console.log(nm(['a', 'b/b', 'c/c/c'], '*/*')); | ||
//=> ['b/b'] | ||
console.log(nm(['a', 'b/b', 'c/c/c'], '**')); | ||
//=> ['a', 'b/b', 'c/c/c'] | ||
``` | ||
## Usage | ||
Additional detail provided in the [API documentation](#api). | ||
## API | ||
### [nanomatch](index.js#L29) | ||
The main function takes a list of strings and one or more glob patterns to use for matching. | ||
**Example** | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
nanomatch(); | ||
console.log(nanomatch(['a.js', 'a.txt'], ['*.js'])); | ||
//=> [ 'a.js' ] | ||
``` | ||
## License | ||
**Params** | ||
Released under the MIT license. © [Jon Schlinkert](https://github.com/jonschlinkert) | ||
* `list` **{Array}** | ||
* `patterns` **{String|Array}**: Glob patterns | ||
* `options` **{Object}** | ||
* `returns` **{Array}**: Returns an array of matches | ||
## About | ||
### [.match](index.js#L85) | ||
### Contributing | ||
Similar to the main function, but `pattern` must be a string. | ||
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). | ||
**Example** | ||
Please read the [contributing guide](.github/contributing.md) for avice on opening issues, pull requests, and coding standards. | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
console.log(nanomatch.match(['a.a', 'a.aa', 'a.b', 'a.c'], '*.a')); | ||
//=> ['a.a', 'a.aa'] | ||
``` | ||
### Building docs | ||
**Params** | ||
_(This document was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme) (a [verb](https://github.com/verbose/verb) generator), please don't edit the readme directly. Any changes to the readme must be made in [.verb.md](.verb.md).)_ | ||
* `list` **{Array}**: Array of strings to match | ||
* `pattern` **{String}**: Glob pattern | ||
* `options` **{Object}** | ||
* `returns` **{Array}**: Returns an array of matches | ||
To generate the readme and API documentation with [verb](https://github.com/verbose/verb): | ||
### [.matcher](index.js#L156) | ||
```sh | ||
$ npm install -g verb verb-generate-readme && verb | ||
Creates a matcher function from the given glob `pattern` and `options`. The returned function takes a string to match as its only argument. | ||
**Example** | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
var isMatch = nanomatch.matcher('*.!(*a)'); | ||
console.log(isMatch('a.a')); | ||
//=> false | ||
console.log(isMatch('a.b')); | ||
//=> true | ||
``` | ||
**Params** | ||
* `pattern` **{String}**: Glob pattern | ||
* `options` **{String}** | ||
* `returns` **{Function}**: Returns a matcher function. | ||
### [.isMatch](index.js#L215) | ||
Returns true if the specified `string` matches the given glob `pattern`. | ||
**Example** | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
console.log(nanomatch.isMatch('a.a', '*.a')); | ||
//=> true | ||
console.log(nanomatch.isMatch('a.b', '*.a')); | ||
//=> false | ||
``` | ||
**Params** | ||
* `string` **{String}**: String to match | ||
* `pattern` **{String}**: Glob pattern | ||
* `options` **{String}** | ||
* `returns` **{Boolean}**: Returns true if the string matches the glob pattern. | ||
### [.not](index.js#L250) | ||
Returns a list of strings that do _not_ match any of the given `patterns`. | ||
**Example** | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
console.log(nanomatch.not(['a.a', 'b.b', 'c.c'], '*.a')); | ||
//=> ['b.b', 'c.c'] | ||
``` | ||
**Params** | ||
* `list` **{Array}**: Array of strings to match. | ||
* `pattern` **{String}**: One or more glob patterns. | ||
* `options` **{Object}** | ||
* `returns` **{Array}**: Returns an array of strings that do not match the given patterns. | ||
### [.any](index.js#L280) | ||
Returns true if the given `string` matches any of the given glob `patterns`. | ||
**Example** | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
console.log(nanomatch.any('a.a', ['b.*', '*.a'])); | ||
//=> true | ||
console.log(nanomatch.any('a.a', 'b.*')); | ||
//=> false | ||
``` | ||
**Params** | ||
* `str` **{String}**: The string to test. | ||
* `patterns` **{String|Array}**: Glob patterns to use. | ||
* `options` **{Object}**: Options to pass to the `matcher()` function. | ||
* `returns` **{Boolean}**: Returns true if any patterns match `str` | ||
### [.contains](index.js#L332) | ||
Returns true if the given `string` contains the given pattern. Similar to `.isMatch` but the pattern can match any part of the string. | ||
**Example** | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
console.log(nanomatch.contains('aa/bb/cc', '*b')); | ||
//=> true | ||
console.log(nanomatch.contains('aa/bb/cc', '*d')); | ||
//=> false | ||
``` | ||
**Params** | ||
* `str` **{String}**: The string to match. | ||
* `pattern` **{String}**: Glob pattern to use for matching. | ||
* `options` **{Object}** | ||
* `returns` **{Boolean}**: Returns true if the patter matches any part of `str`. | ||
### [.matchKeys](index.js#L381) | ||
Filter the keys of the given object with the given `glob` pattern and `options`. Does not attempt to match nested keys. If you need this feature, use [glob-object](https://github.com/jonschlinkert/glob-object) instead. | ||
**Example** | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
var obj = { aa: 'a', ab: 'b', ac: 'c' }; | ||
console.log(nanomatch.matchKeys(obj, '*b')); | ||
//=> { ab: 'b' } | ||
``` | ||
**Params** | ||
* `object` **{Object}** | ||
* `patterns` **{Array|String}**: One or more glob patterns. | ||
* `returns` **{Object}**: Returns an object with only keys that match the given patterns. | ||
### [.makeRe](index.js#L403) | ||
Create a regular expression from the given glob `pattern`. | ||
**Example** | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
console.log(nanomatch.makeRe('*.js')); | ||
//=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ | ||
``` | ||
**Params** | ||
* `pattern` **{String}**: The pattern to convert to regex. | ||
* `options` **{Object}** | ||
* `returns` **{RegExp}**: Returns a regex created from the given pattern. | ||
### [.create](index.js#L463) | ||
Parses the given glob `pattern` and returns an object with the compiled `output` and optional source `map`. | ||
**Example** | ||
```js | ||
var nanomatch = require('nanomatch'); | ||
console.log(nanomatch.create('abc/*.js')); | ||
// { options: { source: 'string', sourcemap: true }, | ||
// state: {}, | ||
// compilers: | ||
// { ... }, | ||
// output: '(\\.[\\\\\\/])?abc\\/(?!\\.)(?=.)[^\\/]*?\\.js', | ||
// ast: | ||
// { type: 'root', | ||
// errors: [], | ||
// nodes: | ||
// [ ... ], | ||
// dot: false, | ||
// input: 'abc/*.js' }, | ||
// parsingErrors: [], | ||
// map: | ||
// { version: 3, | ||
// sources: [ 'string' ], | ||
// names: [], | ||
// mappings: 'AAAA,GAAG,EAAC,kBAAC,EAAC,EAAE', | ||
// sourcesContent: [ 'abc/*.js' ] }, | ||
// position: { line: 1, column: 28 }, | ||
// content: {}, | ||
// files: {}, | ||
// idx: 6 } | ||
``` | ||
**Params** | ||
* `pattern` **{String}**: Glob pattern | ||
* `options` **{Object}** | ||
* `returns` **{Object}**: Returns an object with the parsed AST, compiled string and optional source map. | ||
## Features | ||
Nanomatch has full support for standard Bash glob features, including the following "metacharacters": `*`, `**`, `?` and `[...]`. | ||
### Globbing reference | ||
Here are some examples of how they work: | ||
**Pattern** | **Description** | ||
--- | --- | ||
`*` | Matches any string except for `/`, leading `.`, or `/.` inside a path | ||
`**` | Matches any string including `/`, but not a leading `.` or `/.` inside a path. More than two stars (e.g. `***` is treated the same as one star, and `**` loses its special meaning when it's not the only thing in a path segment, per Bash specifications) | ||
`foo*` | Matches any string beginning with `foo` | ||
`*bar*` | Matches any string containing `bar` (beginning, middle or end) | ||
`*.min.js` | Matches any string ending with `.min.js` | ||
`[abc]*.js` | Matches any string beginning with `a`, `b`, or `c` and ending with `.js` | ||
`abc?` | Matches `abcd` or `abcz` but not `abcde` | ||
The exceptions noted for `*` apply to all patterns that contain a `*`. | ||
**Not supported** | ||
The following extended-globbing features are not supported: | ||
* [brace expansion](https://github.com/jonschlinkert/braces) (e.g. `{a,b,c}`) | ||
* [extglobs](https://github.com/jonschlinkert/extglob) (e.g. `@(a|!(c|d))`) | ||
* [POSIX brackets](https://github.com/jonschlinkert/expand-brackets) (e.g. `[[:alpha:][:digit:]]`) | ||
If you need any of these features consider using [micromatch](https://github.com/jonschlinkert/micromatch) instead. | ||
### Bash pattern matching | ||
Nanomatch is part of a suite of libraries aimed at bringing the power and expressiveness of [Bash's][] matching and expansion capabilities to JavaScript, _and - as you can see by the [benchmarks](#benchmarks) - without sacrificing speed_. | ||
**Related library** | **Matching Type** | **Example** | **Description** | ||
--- | --- | --- | --- | ||
[micromatch](https://github.com/jonschlinkert/micromatch) | Wildcards | `*` | [Filename expansion](https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html#Filename-Expansion), also referred to as globbing and pathname expansion, allows the use of [wildcards](#features) for matching. | ||
[expand-tilde](https://github.com/jonschlinkert/expand-tilde) | Tildes | `~` | [Tilde expansion](https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html#Tilde-Expansion) converts the leading tilde in a file path to the user home directory. | ||
[braces](https://github.com/jonschlinkert/braces) | Braces | `{a,b,c}` | [Brace expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html) | ||
[expand-brackets](https://github.com/jonschlinkert/expand-brackets) | Brackets | `[[:alpha:]]` | [POSIX character classes](https://www.gnu.org/software/grep/manual/html_node/Character-Classes-and-Bracket-Expressions.html) (also referred to as POSIX brackets, or POSIX character classes) | ||
[extglob](https://github.com/jonschlinkert/extglob) | Parens | `!(a|b)` | [Extglobs](https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html#Pattern-Matching) | ||
[micromatch](https://github.com/jonschlinkert/micromatch) | All | all | Micromatch is built on top of the other libraries. | ||
There are many resources available on the web if you want to dive deeper into how these features work in Bash. | ||
## Benchmarks | ||
### Running benchmarks | ||
Install dev dependencies: | ||
```bash | ||
npm i -d && npm benchmark | ||
``` | ||
### Latest results | ||
```bash | ||
Benchmarking: (4 of 4) | ||
· globstar-basic | ||
· negation-basic | ||
· not-glob-basic | ||
· star-basic | ||
# benchmark/fixtures/match/globstar-basic.js (182 bytes) | ||
minimatch x 35,521 ops/sec ±0.99% (82 runs sampled) | ||
multimatch x 29,662 ops/sec ±1.90% (82 runs sampled) | ||
nanomatch x 719,866 ops/sec ±1.53% (84 runs sampled) | ||
fastest is nanomatch | ||
# benchmark/fixtures/match/negation-basic.js (132 bytes) | ||
minimatch x 65,810 ops/sec ±1.11% (85 runs sampled) | ||
multimatch x 24,267 ops/sec ±1.40% (85 runs sampled) | ||
nanomatch x 698,260 ops/sec ±1.42% (84 runs sampled) | ||
fastest is nanomatch | ||
# benchmark/fixtures/match/not-glob-basic.js (93 bytes) | ||
minimatch x 91,445 ops/sec ±1.69% (83 runs sampled) | ||
multimatch x 62,945 ops/sec ±1.20% (84 runs sampled) | ||
nanomatch x 3,077,100 ops/sec ±1.45% (84 runs sampled) | ||
fastest is nanomatch | ||
# benchmark/fixtures/match/star-basic.js (93 bytes) | ||
minimatch x 62,144 ops/sec ±1.67% (85 runs sampled) | ||
multimatch x 46,133 ops/sec ±1.66% (83 runs sampled) | ||
nanomatch x 1,039,345 ops/sec ±1.23% (86 runs sampled) | ||
fastest is nanomatch | ||
``` | ||
## History | ||
### key | ||
Changelog entries are classified using the following labels _(from [keep-a-changelog](https://github.com/olivierlacan/keep-a-changelog)_): | ||
* `added`: for new features | ||
* `changed`: for changes in existing functionality | ||
* `deprecated`: for once-stable features removed in upcoming releases | ||
* `removed`: for deprecated features removed in this release | ||
* `fixed`: for any bug fixes | ||
* `bumped`: updated dependencies, only minor or higher will be listed. | ||
### [0.1.0] - 2016-10-08 | ||
First release. | ||
## About | ||
### Related projects | ||
* [expand-brackets](https://www.npmjs.com/package/expand-brackets): Expand POSIX bracket expressions (character classes) in glob patterns. | [homepage](https://github.com/jonschlinkert/expand-brackets "Expand POSIX bracket expressions (character classes) in glob patterns.") | ||
* [extglob](https://www.npmjs.com/package/extglob): Convert extended globs to regex-compatible strings. Add (almost) the expressive power of regular expressions to… [more](https://github.com/jonschlinkert/extglob) | [homepage](https://github.com/jonschlinkert/extglob "Convert extended globs to regex-compatible strings. Add (almost) the expressive power of regular expressions to glob patterns.") | ||
* [micromatch](https://www.npmjs.com/package/micromatch): Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch. | [homepage](https://github.com/jonschlinkert/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.") | ||
### Contributing | ||
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). | ||
Please read the [contributing guide](.github/contributing.md) for avice on opening issues, pull requests, and coding standards. | ||
### Running tests | ||
@@ -91,2 +439,2 @@ | ||
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.30, on September 05, 2016._ | ||
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.31, on October 08, 2016._ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
46361
9
965
439
15
17
1
+ Addedarr-union@^3.1.0
+ Addedfragment-cache@^0.1.0
+ Addedis-odd@^0.1.0
+ Addedisobject@^2.1.0
+ Addedobject.pick@^1.1.2
+ Addedregex-not@^1.0.0
+ Addedto-regex@^2.1.0
+ Addedarr-union@3.1.0(transitive)
+ Addedassign-symbols@1.0.0(transitive)
+ Addedbase@0.11.2(transitive)
+ Addedcache-base@1.0.1(transitive)
+ Addedclass-utils@0.3.6(transitive)
+ Addedcollection-visit@1.0.0(transitive)
+ Addedcomponent-emitter@1.3.1(transitive)
+ Addedcopy-descriptor@0.1.1(transitive)
+ Addeddefine-property@1.0.0(transitive)
+ Addedextend-shallow@3.0.2(transitive)
+ Addedfor-in@1.0.2(transitive)
+ Addedfragment-cache@0.1.0(transitive)
+ Addedget-value@2.0.6(transitive)
+ Addedhas-value@0.3.11.0.0(transitive)
+ Addedhas-values@0.1.41.0.0(transitive)
+ Addedis-buffer@1.1.6(transitive)
+ Addedis-descriptor@1.0.3(transitive)
+ Addedis-extendable@1.0.1(transitive)
+ Addedis-number@3.0.0(transitive)
+ Addedis-odd@0.1.2(transitive)
+ Addedis-plain-object@2.0.4(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedisobject@2.1.03.0.1(transitive)
+ Addedkind-of@3.2.24.0.0(transitive)
+ Addedlazy-cache@2.0.2(transitive)
+ Addedmap-cache@0.2.2(transitive)
+ Addedmap-visit@1.0.0(transitive)
+ Addedmixin-deep@1.3.2(transitive)
+ Addedobject-copy@0.1.0(transitive)
+ Addedobject-visit@1.0.1(transitive)
+ Addedobject.pick@1.3.0(transitive)
+ Addedpascalcase@0.1.1(transitive)
+ Addedregex-not@0.1.21.0.2(transitive)
+ Addedret@0.1.15(transitive)
+ Addedsafe-regex@1.1.0(transitive)
+ Addedset-getter@0.1.1(transitive)
+ Addedset-value@2.0.1(transitive)
+ Addedsnapdragon@0.7.3(transitive)
+ Addedsplit-string@3.1.0(transitive)
+ Addedstatic-extend@0.1.2(transitive)
+ Addedto-object-path@0.3.0(transitive)
+ Addedto-regex@2.1.0(transitive)
+ Addedunion-value@1.0.1(transitive)
+ Addedunset-value@1.0.0(transitive)
+ Addeduse@2.0.2(transitive)
- Removedfill-array@^1.0.0
- Removedrepeat-string@^1.5.4
- Removedfill-array@1.0.0(transitive)
- Removedrepeat-string@1.6.1(transitive)
- Removedsnapdragon@0.5.0(transitive)
Updatedsnapdragon@^0.7.2