UNPKG

9.05 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 while (i < len) {
148 var fn = matcher(patterns[i++], opts);
149 if (!fn(fp)) {
150 res = false;
151 break;
152 }
153 }
154 return res;
155 };
156}
157
158/**
159 * Returns true if the filepath contains the given
160 * pattern. Can also return a function for matching.
161 *
162 * ```js
163 * isMatch('foo.md', '*.md', {});
164 * //=> true
165 *
166 * isMatch('*.md', {})('foo.md')
167 * //=> true
168 * ```
169 *
170 * @param {String} `fp`
171 * @param {String} `pattern`
172 * @param {Object} `opts`
173 * @return {Boolean}
174 */
175
176function isMatch(fp, pattern, opts) {
177 if (typeof fp !== 'string') {
178 throw new TypeError(msg('isMatch', 'filepath', 'a string'));
179 }
180
181 if (typeOf(pattern) === 'object') {
182 return matcher(fp, pattern);
183 }
184 return matcher(pattern, opts)(fp);
185}
186
187/**
188 * Returns true if the filepath matches the
189 * given pattern.
190 */
191
192function contains(fp, pattern, opts) {
193 opts = opts || {};
194 opts.contains = (pattern !== '');
195 if (opts.contains && !isGlob(pattern)) {
196 return fp.indexOf(pattern) !== -1;
197 }
198 return matcher(pattern, opts)(fp);
199}
200
201/**
202 * Returns true if a file path matches any of the
203 * given patterns.
204 *
205 * @param {String} `fp` The filepath to test.
206 * @param {String|Array} `patterns` Glob patterns to use.
207 * @param {Object} `opts` Options to pass to the `matcher()` function.
208 * @return {String}
209 */
210
211function any(fp, patterns, opts) {
212 if (!Array.isArray(patterns) && typeof patterns !== 'string') {
213 throw new TypeError(msg('any', 'patterns', 'a string or array'));
214 }
215
216 patterns = utils.arrayify(patterns);
217 var len = patterns.length;
218
219 while (len--) {
220 var isMatch = matcher(patterns[len], opts);
221 if (isMatch(fp)) {
222 return true;
223 }
224 }
225 return false;
226}
227
228/**
229 * Filter the keys in an object.
230 *
231 * @param {*} val
232 * @return {Array}
233 */
234
235function matchKeys(obj, pattern, options) {
236 if (typeOf(obj) !== 'object') {
237 throw new TypeError(msg('matchKeys', 'first argument', 'an object'));
238 }
239
240 var fn = matcher(pattern, options);
241 var res = {};
242
243 for (var key in obj) {
244 if (obj.hasOwnProperty(key) && fn(key)) {
245 res[key] = obj[key];
246 }
247 }
248 return res;
249}
250
251/**
252 * Return a function for matching based on the
253 * given `pattern` and `options`.
254 *
255 * @param {String} `pattern`
256 * @param {Object} `options`
257 * @return {Function}
258 */
259
260function matcher(pattern, opts) {
261 // pattern is a function
262 if (typeof pattern === 'function') {
263 return pattern;
264 }
265 // pattern is a regex
266 if (pattern instanceof RegExp) {
267 return function(fp) {
268 return pattern.test(fp);
269 };
270 }
271 // pattern is a non-glob string
272 if (!isGlob(pattern)) {
273 return utils.matchPath(pattern, opts);
274 }
275 // pattern is a glob string
276 var re = makeRe(pattern, opts);
277
278 // `matchBase` is defined
279 if (opts && opts.matchBase) {
280 return utils.hasFilename(re, opts);
281 }
282 // `matchBase` is not defined
283 return function(fp) {
284 return re.test(fp);
285 };
286}
287
288/**
289 * Create and cache a regular expression for matching
290 * file paths.
291 *
292 * If the leading character in the `glob` is `!` a negation
293 * regex is returned.
294 *
295 * @param {String} glob
296 * @param {Object} options
297 * @return {RegExp}
298 */
299
300function toRegex(glob, options) {
301 if (typeOf(glob) !== 'string') {
302 throw new Error(msg('toRegex', 'glob', 'a string'));
303 }
304
305 // clone options to prevent mutating the original object
306 var opts = Object.create(options || {});
307 var flags = opts.flags || '';
308 if (opts.nocase && flags.indexOf('i') === -1) {
309 flags += 'i';
310 }
311
312 var parsed = expand(glob, opts);
313
314 // pass in tokens to avoid parsing more than once
315 opts.negated = opts.negated || parsed.negated;
316 opts.negate = opts.negated;
317 glob = wrapGlob(parsed.pattern, opts);
318
319 try {
320 return new RegExp(glob, flags);
321 } catch (err) {
322 debug('toRegex', err);
323 }
324 return /$^/;
325}
326
327/**
328 * Wrap `toRegex` to memoize the generated regex
329 * the string and options don't change
330 */
331
332function makeRe(glob, opts) {
333 return cache(toRegex, glob, opts);
334}
335
336/**
337 * Create the regex to do the matching. If
338 * the leading character in the `glob` is `!`
339 * a negation regex is returned.
340 *
341 * @param {String} `glob`
342 * @param {Boolean} `negate`
343 */
344
345function wrapGlob(glob, opts) {
346 var prefix = (opts && !opts.contains) ? '^' : '';
347 var after = (opts && !opts.contains) ? '$' : '';
348 glob = ('(?:' + glob + ')' + after);
349 if (opts && opts.negate) {
350 return prefix + ('(?!^' + glob + ').*$');
351 }
352 return prefix + glob;
353}
354
355/**
356 * Make error messages consistent. Follows this format:
357 *
358 * ```js
359 * msg(methodName, argNumber, nativeType);
360 * // example:
361 * msg('matchKeys', 'first', 'an object');
362 * ```
363 *
364 * @param {String} `method`
365 * @param {String} `num`
366 * @param {String} `type`
367 * @return {String}
368 */
369
370function msg(method, what, type) {
371 return 'micromatch.' + method + '(): ' + what + ' should be ' + type + '.';
372}
373
374/**
375 * Public methods
376 */
377
378micromatch.any = any;
379micromatch.braces = micromatch.braceExpand = require('braces');
380micromatch.contains = contains;
381micromatch.expand = expand;
382micromatch.filter = filter;
383micromatch.isMatch = isMatch;
384micromatch.makeRe = makeRe;
385micromatch.match = match;
386micromatch.matcher = matcher;
387micromatch.matchKeys = matchKeys;
388
389/**
390 * Expose `micromatch`
391 */
392
393module.exports = micromatch;