UNPKG

9.58 kBJavaScriptView Raw
1/*!
2 * micromatch <https://github.com/jonschlinkert/micromatch>
3 *
4 * Copyright (c) 2014-2015, Jon Schlinkert.
5 * Licensed under the MIT License.
6 */
7
8'use strict';
9
10var diff = require('arr-diff');
11var debug = require('debug')('micromatch');
12var typeOf = require('kind-of');
13var omit = require('object.omit');
14var cache = require('regex-cache');
15var isGlob = require('is-glob');
16var expand = require('./lib/expand');
17var utils = require('./lib/utils');
18
19/**
20 * The main function. Pass an array of filepaths,
21 * and a string or array of glob patterns
22 *
23 * @param {Array|String} `files`
24 * @param {Array|String} `patterns`
25 * @param {Object} `opts`
26 * @return {Array} Array of matches
27 */
28
29function micromatch(files, patterns, opts) {
30 if (!files || !patterns) return [];
31 opts = opts || {};
32
33 if (typeof opts.cache === 'undefined') {
34 opts.cache = true;
35 }
36
37 if (!Array.isArray(patterns)) {
38 return match(files, patterns, opts);
39 }
40
41 var len = patterns.length, i = 0;
42 var omit = [], keep = [];
43
44 while (len--) {
45 var glob = patterns[i++];
46 if (glob.charCodeAt(0) === 33 /* ! */) {
47 omit.push.apply(omit, match(files, glob.slice(1), opts));
48 } else {
49 keep.push.apply(keep, match(files, glob, opts));
50 }
51 }
52 return diff(keep, omit);
53}
54
55/**
56 * Pass an array of files and a glob pattern as a string.
57 *
58 * This function is called by the main `micromatch` function
59 * If you only need to pass a single pattern you might get
60 * very minor speed improvements using this function.
61 *
62 * @param {Array} `files`
63 * @param {Array} `pattern`
64 * @param {Object} `options`
65 * @return {Array}
66 */
67
68function match(files, pattern, opts) {
69 if (typeOf(files) !== 'string' && !Array.isArray(files)) {
70 throw new Error(msg('match', 'files', 'a string or array'));
71 }
72
73 files = utils.arrayify(files);
74 opts = opts || {};
75
76 var negate = opts.negate || false;
77 var orig = pattern;
78
79 if (typeof pattern === 'string' && opts.nonegate !== true) {
80 negate = pattern.charAt(0) === '!';
81 if (negate) {
82 pattern = pattern.slice(1);
83 }
84 }
85
86 var _isMatch = matcher(pattern, opts);
87 var len = files.length, i = 0;
88 var res = [];
89
90 while (i < len) {
91 var file = files[i++];
92 var fp = utils.unixify(file, opts);
93
94 if (!_isMatch(fp)) { continue; }
95 res.push(fp);
96 }
97
98 if (res.length === 0) {
99 if (opts.failglob === true) {
100 throw new Error('micromatch.match() found no matches for: "' + orig + '".');
101 }
102
103 if (opts.nonull || opts.nullglob) {
104 res.push(utils.unescapeGlob(orig));
105 }
106 }
107
108 // if `negate` was defined, diff negated files
109 if (negate) { res = diff(files, res); }
110
111 // if `ignore` was defined, diff ignored filed
112 if (opts.ignore && opts.ignore.length) {
113 pattern = opts.ignore;
114 opts = omit(opts, ['ignore']);
115 return diff(res, micromatch(res, pattern, opts));
116 }
117 return res;
118}
119
120/**
121 * Returns a function that takes a glob pattern or array of glob patterns
122 * to be used with `Array#filter()`. (Internally this function generates
123 * the matching function using the [matcher] method).
124 *
125 * ```js
126 * var fn = mm.filter('[a-c]');
127 * ['a', 'b', 'c', 'd', 'e'].filter(fn);
128 * //=> ['a', 'b', 'c']
129 * ```
130 *
131 * @param {String|Array} `patterns` Can be a glob or array of globs.
132 * @param {Options} `opts` Options to pass to the [matcher] method.
133 * @return {Function} Filter function to be passed to `Array#filter()`.
134 */
135
136function filter(patterns, opts) {
137 if (!Array.isArray(patterns) && typeof patterns !== 'string') {
138 throw new TypeError(msg('filter', 'patterns', 'a string or array'));
139 }
140
141 patterns = utils.arrayify(patterns);
142 return function (fp) {
143 if (fp == null) return [];
144 var len = patterns.length, i = 0;
145 var res = true;
146
147 fp = utils.unixify(fp, opts);
148 while (i < len) {
149 var fn = matcher(patterns[i++], opts);
150 if (!fn(fp)) {
151 res = false;
152 break;
153 }
154 }
155 return res;
156 };
157}
158
159/**
160 * Returns true if the filepath contains the given
161 * pattern. Can also return a function for matching.
162 *
163 * ```js
164 * isMatch('foo.md', '*.md', {});
165 * //=> true
166 *
167 * isMatch('*.md', {})('foo.md')
168 * //=> true
169 * ```
170 *
171 * @param {String} `fp`
172 * @param {String} `pattern`
173 * @param {Object} `opts`
174 * @return {Boolean}
175 */
176
177function isMatch(fp, pattern, opts) {
178 if (typeof fp !== 'string') {
179 throw new TypeError(msg('isMatch', 'filepath', 'a string'));
180 }
181
182 fp = utils.unixify(fp, opts);
183 if (typeOf(pattern) === 'object') {
184 return matcher(fp, pattern);
185 }
186 return matcher(pattern, opts)(fp);
187}
188
189/**
190 * Returns true if the filepath matches the
191 * given pattern.
192 */
193
194function contains(fp, pattern, opts) {
195 if (typeof fp !== 'string') {
196 throw new TypeError(msg('contains', 'pattern', 'a string'));
197 }
198
199 opts = opts || {};
200 opts.contains = (pattern !== '');
201 fp = utils.unixify(fp, opts);
202
203 if (opts.contains && !isGlob(pattern)) {
204 return fp.indexOf(pattern) !== -1;
205 }
206 return matcher(pattern, opts)(fp);
207}
208
209/**
210 * Returns true if a file path matches any of the
211 * given patterns.
212 *
213 * @param {String} `fp` The filepath to test.
214 * @param {String|Array} `patterns` Glob patterns to use.
215 * @param {Object} `opts` Options to pass to the `matcher()` function.
216 * @return {String}
217 */
218
219function any(fp, patterns, opts) {
220 if (!Array.isArray(patterns) && typeof patterns !== 'string') {
221 throw new TypeError(msg('any', 'patterns', 'a string or array'));
222 }
223
224 patterns = utils.arrayify(patterns);
225 var len = patterns.length;
226
227 fp = utils.unixify(fp, opts);
228 while (len--) {
229 var isMatch = matcher(patterns[len], opts);
230 if (isMatch(fp)) {
231 return true;
232 }
233 }
234 return false;
235}
236
237/**
238 * Filter the keys of an object with the given `glob` pattern
239 * and `options`
240 *
241 * @param {Object} `object`
242 * @param {Pattern} `object`
243 * @return {Array}
244 */
245
246function matchKeys(obj, glob, options) {
247 if (typeOf(obj) !== 'object') {
248 throw new TypeError(msg('matchKeys', 'first argument', 'an object'));
249 }
250
251 var fn = matcher(glob, options);
252 var res = {};
253
254 for (var key in obj) {
255 if (obj.hasOwnProperty(key) && fn(key)) {
256 res[key] = obj[key];
257 }
258 }
259 return res;
260}
261
262/**
263 * Return a function for matching based on the
264 * given `pattern` and `options`.
265 *
266 * @param {String} `pattern`
267 * @param {Object} `options`
268 * @return {Function}
269 */
270
271function matcher(pattern, opts) {
272 // pattern is a function
273 if (typeof pattern === 'function') {
274 return pattern;
275 }
276 // pattern is a regex
277 if (pattern instanceof RegExp) {
278 return function(fp) {
279 return pattern.test(fp);
280 };
281 }
282
283 // strings, all the way down...
284 pattern = utils.unixify(pattern, opts);
285
286 // pattern is a non-glob string
287 if (!isGlob(pattern)) {
288 return utils.matchPath(pattern, opts);
289 }
290 // pattern is a glob string
291 var re = makeRe(pattern, opts);
292
293 // `matchBase` is defined
294 if (opts && opts.matchBase) {
295 return utils.hasFilename(re, opts);
296 }
297 // `matchBase` is not defined
298 return function(fp) {
299 fp = utils.unixify(fp, opts);
300 return re.test(fp);
301 };
302}
303
304/**
305 * Create and cache a regular expression for matching
306 * file paths.
307 *
308 * If the leading character in the `glob` is `!`, a negation
309 * regex is returned.
310 *
311 * @param {String} `glob`
312 * @param {Object} `options`
313 * @return {RegExp}
314 */
315
316function toRegex(glob, options) {
317 if (typeOf(glob) !== 'string') {
318 throw new Error(msg('toRegex', 'glob', 'a string'));
319 }
320
321 // clone options to prevent mutating the original object
322 var opts = Object.create(options || {});
323 var flags = opts.flags || '';
324 if (opts.nocase && flags.indexOf('i') === -1) {
325 flags += 'i';
326 }
327
328 var parsed = expand(glob, opts);
329
330 // pass in tokens to avoid parsing more than once
331 opts.negated = opts.negated || parsed.negated;
332 opts.negate = opts.negated;
333 glob = wrapGlob(parsed.pattern, opts);
334 var re;
335
336 try {
337 re = new RegExp(glob, flags);
338 return re;
339 } catch (err) {
340 var msg = 'micromatch invalid regex: (' + re + ')';
341 if (opts.strict) throw new SyntaxError(msg + err);
342 }
343 return /$^/;
344}
345
346/**
347 * Create the regex to do the matching. If the leading
348 * character in the `glob` is `!` a negation regex is returned.
349 *
350 * @param {String} `glob`
351 * @param {Boolean} `negate`
352 */
353
354function wrapGlob(glob, opts) {
355 var prefix = (opts && !opts.contains) ? '^' : '';
356 var after = (opts && !opts.contains) ? '$' : '';
357 glob = ('(?:' + glob + ')' + after);
358 if (opts && opts.negate) {
359 return prefix + ('(?!^' + glob + ').*$');
360 }
361 return prefix + glob;
362}
363
364/**
365 * Wrap `toRegex` to memoize the generated regex
366 * the string and options don't change
367 */
368
369function makeRe(glob, opts) {
370 return cache(toRegex, glob, opts);
371}
372
373/**
374 * Make error messages consistent. Follows this format:
375 *
376 * ```js
377 * msg(methodName, argNumber, nativeType);
378 * // example:
379 * msg('matchKeys', 'first', 'an object');
380 * ```
381 *
382 * @param {String} `method`
383 * @param {String} `num`
384 * @param {String} `type`
385 * @return {String}
386 */
387
388function msg(method, what, type) {
389 return 'micromatch.' + method + '(): ' + what + ' should be ' + type + '.';
390}
391
392/**
393 * Public methods
394 */
395
396micromatch.any = any;
397micromatch.braces = micromatch.braceExpand = require('braces');
398micromatch.contains = contains;
399micromatch.expand = expand;
400micromatch.filter = filter;
401micromatch.isMatch = isMatch;
402micromatch.makeRe = makeRe;
403micromatch.match = match;
404micromatch.matcher = matcher;
405micromatch.matchKeys = matchKeys;
406
407/**
408 * Expose `micromatch`
409 */
410
411module.exports = micromatch;