2 * micromatch <https://github.com/jonschlinkert/micromatch>
4 * Copyright (c) 2014-2015, Jon Schlinkert.
5 * Licensed under the MIT License.
10 var expand = require('./lib/expand');
11 var utils = require('./lib/utils');
14 * The main function. Pass an array of filepaths,
15 * and a string or array of glob patterns
17 * @param {Array|String} `files`
18 * @param {Array|String} `patterns`
19 * @param {Object} `opts`
20 * @return {Array} Array of matches
23 function micromatch(files, patterns, opts) {
24 if (!files || !patterns) return [];
27 if (typeof opts.cache === 'undefined') {
31 if (!Array.isArray(patterns)) {
32 return match(files, patterns, opts);
35 var len = patterns.length, i = 0;
36 var omit = [], keep = [];
39 var glob = patterns[i++];
40 if (typeof glob === 'string' && glob.charCodeAt(0) === 33 /* ! */) {
41 omit.push.apply(omit, match(files, glob.slice(1), opts));
43 keep.push.apply(keep, match(files, glob, opts));
46 return utils.diff(keep, omit);
50 * Return an array of files that match the given glob pattern.
52 * This function is called by the main `micromatch` function If you only
53 * need to pass a single pattern you might get very minor speed improvements
54 * using this function.
56 * @param {Array} `files`
57 * @param {String} `pattern`
58 * @param {Object} `options`
62 function match(files, pattern, opts) {
63 if (utils.typeOf(files) !== 'string' && !Array.isArray(files)) {
64 throw new Error(msg('match', 'files', 'a string or array'));
67 files = utils.arrayify(files);
70 var negate = opts.negate || false;
73 if (typeof pattern === 'string') {
74 negate = pattern.charAt(0) === '!';
76 pattern = pattern.slice(1);
79 // we need to remove the character regardless,
80 // so the above logic is still needed
81 if (opts.nonegate === true) {
86 var _isMatch = matcher(pattern, opts);
87 var len = files.length, i = 0;
91 var file = files[i++];
92 var fp = utils.unixify(file, opts);
94 if (!_isMatch(fp)) { continue; }
98 if (res.length === 0) {
99 if (opts.failglob === true) {
100 throw new Error('micromatch.match() found no matches for: "' + orig + '".');
103 if (opts.nonull || opts.nullglob) {
104 res.push(utils.unescapeGlob(orig));
108 // if `negate` was defined, diff negated files
109 if (negate) { res = utils.diff(files, res); }
111 // if `ignore` was defined, diff ignored filed
112 if (opts.ignore && opts.ignore.length) {
113 pattern = opts.ignore;
114 opts = utils.omit(opts, ['ignore']);
115 res = utils.diff(res, micromatch(res, pattern, opts));
119 return utils.unique(res);
125 * Returns a function that takes a glob pattern or array of glob patterns
126 * to be used with `Array#filter()`. (Internally this function generates
127 * the matching function using the [matcher] method).
130 * var fn = mm.filter('[a-c]');
131 * ['a', 'b', 'c', 'd', 'e'].filter(fn);
132 * //=> ['a', 'b', 'c']
134 * @param {String|Array} `patterns` Can be a glob or array of globs.
135 * @param {Options} `opts` Options to pass to the [matcher] method.
136 * @return {Function} Filter function to be passed to `Array#filter()`.
139 function filter(patterns, opts) {
140 if (!Array.isArray(patterns) && typeof patterns !== 'string') {
141 throw new TypeError(msg('filter', 'patterns', 'a string or array'));
144 patterns = utils.arrayify(patterns);
145 var len = patterns.length, i = 0;
146 var patternMatchers = Array(len);
148 patternMatchers[i] = matcher(patterns[i++], opts);
151 return function(fp) {
152 if (fp == null) return [];
153 var len = patternMatchers.length, i = 0;
156 fp = utils.unixify(fp, opts);
158 var fn = patternMatchers[i++];
169 * Returns true if the filepath contains the given
170 * pattern. Can also return a function for matching.
173 * isMatch('foo.md', '*.md', {});
176 * isMatch('*.md', {})('foo.md')
179 * @param {String} `fp`
180 * @param {String} `pattern`
181 * @param {Object} `opts`
185 function isMatch(fp, pattern, opts) {
186 if (typeof fp !== 'string') {
187 throw new TypeError(msg('isMatch', 'filepath', 'a string'));
190 fp = utils.unixify(fp, opts);
191 if (utils.typeOf(pattern) === 'object') {
192 return matcher(fp, pattern);
194 return matcher(pattern, opts)(fp);
198 * Returns true if the filepath matches the
202 function contains(fp, pattern, opts) {
203 if (typeof fp !== 'string') {
204 throw new TypeError(msg('contains', 'pattern', 'a string'));
208 opts.contains = (pattern !== '');
209 fp = utils.unixify(fp, opts);
211 if (opts.contains && !utils.isGlob(pattern)) {
212 return fp.indexOf(pattern) !== -1;
214 return matcher(pattern, opts)(fp);
218 * Returns true if a file path matches any of the
221 * @param {String} `fp` The filepath to test.
222 * @param {String|Array} `patterns` Glob patterns to use.
223 * @param {Object} `opts` Options to pass to the `matcher()` function.
227 function any(fp, patterns, opts) {
228 if (!Array.isArray(patterns) && typeof patterns !== 'string') {
229 throw new TypeError(msg('any', 'patterns', 'a string or array'));
232 patterns = utils.arrayify(patterns);
233 var len = patterns.length;
235 fp = utils.unixify(fp, opts);
237 var isMatch = matcher(patterns[len], opts);
246 * Filter the keys of an object with the given `glob` pattern
249 * @param {Object} `object`
250 * @param {Pattern} `object`
254 function matchKeys(obj, glob, options) {
255 if (utils.typeOf(obj) !== 'object') {
256 throw new TypeError(msg('matchKeys', 'first argument', 'an object'));
259 var fn = matcher(glob, options);
262 for (var key in obj) {
263 if (obj.hasOwnProperty(key) && fn(key)) {
271 * Return a function for matching based on the
272 * given `pattern` and `options`.
274 * @param {String} `pattern`
275 * @param {Object} `options`
279 function matcher(pattern, opts) {
280 // pattern is a function
281 if (typeof pattern === 'function') {
284 // pattern is a regex
285 if (pattern instanceof RegExp) {
286 return function(fp) {
287 return pattern.test(fp);
291 if (typeof pattern !== 'string') {
292 throw new TypeError(msg('matcher', 'pattern', 'a string, regex, or function'));
295 // strings, all the way down...
296 pattern = utils.unixify(pattern, opts);
298 // pattern is a non-glob string
299 if (!utils.isGlob(pattern)) {
300 return utils.matchPath(pattern, opts);
302 // pattern is a glob string
303 var re = makeRe(pattern, opts);
305 // `matchBase` is defined
306 if (opts && opts.matchBase) {
307 return utils.hasFilename(re, opts);
309 // `matchBase` is not defined
310 return function(fp) {
311 fp = utils.unixify(fp, opts);
317 * Create and cache a regular expression for matching
320 * If the leading character in the `glob` is `!`, a negation
323 * @param {String} `glob`
324 * @param {Object} `options`
328 function toRegex(glob, options) {
329 // clone options to prevent mutating the original object
330 var opts = Object.create(options || {});
331 var flags = opts.flags || '';
332 if (opts.nocase && flags.indexOf('i') === -1) {
336 var parsed = expand(glob, opts);
338 // pass in tokens to avoid parsing more than once
339 opts.negated = opts.negated || parsed.negated;
340 opts.negate = opts.negated;
341 glob = wrapGlob(parsed.pattern, opts);
345 re = new RegExp(glob, flags);
348 err.reason = 'micromatch invalid regex: (' + re + ')';
349 if (opts.strict) throw new SyntaxError(err);
352 // we're only here if a bad pattern was used and the user
353 // passed `options.silent`, so match nothing
358 * Create the regex to do the matching. If the leading
359 * character in the `glob` is `!` a negation regex is returned.
361 * @param {String} `glob`
362 * @param {Boolean} `negate`
365 function wrapGlob(glob, opts) {
366 var prefix = (opts && !opts.contains) ? '^' : '';
367 var after = (opts && !opts.contains) ? '$' : '';
368 glob = ('(?:' + glob + ')' + after);
369 if (opts && opts.negate) {
370 return prefix + ('(?!^' + glob + ').*$');
372 return prefix + glob;
376 * Create and cache a regular expression for matching file paths.
377 * If the leading character in the `glob` is `!`, a negation
380 * @param {String} `glob`
381 * @param {Object} `options`
385 function makeRe(glob, opts) {
386 if (utils.typeOf(glob) !== 'string') {
387 throw new Error(msg('makeRe', 'glob', 'a string'));
389 return utils.cache(toRegex, glob, opts);
393 * Make error messages consistent. Follows this format:
396 * msg(methodName, argNumber, nativeType);
398 * msg('matchKeys', 'first', 'an object');
401 * @param {String} `method`
402 * @param {String} `num`
403 * @param {String} `type`
407 function msg(method, what, type) {
408 return 'micromatch.' + method + '(): ' + what + ' should be ' + type + '.';
415 /* eslint no-multi-spaces: 0 */
416 micromatch.any = any;
417 micromatch.braces = micromatch.braceExpand = utils.braces;
418 micromatch.contains = contains;
419 micromatch.expand = expand;
420 micromatch.filter = filter;
421 micromatch.isMatch = isMatch;
422 micromatch.makeRe = makeRe;
423 micromatch.match = match;
424 micromatch.matcher = matcher;
425 micromatch.matchKeys = matchKeys;
428 * Expose `micromatch`
431 module.exports = micromatch;