index.js
  1  'use strict';
  2  
  3  const util = require('util');
  4  const braces = require('braces');
  5  const picomatch = require('picomatch');
  6  const utils = require('picomatch/lib/utils');
  7  const isEmptyString = val => val === '' || val === './';
  8  
  9  /**
 10   * Returns an array of strings that match one or more glob patterns.
 11   *
 12   * ```js
 13   * const mm = require('micromatch');
 14   * // mm(list, patterns[, options]);
 15   *
 16   * console.log(mm(['a.js', 'a.txt'], ['*.js']));
 17   * //=> [ 'a.js' ]
 18   * ```
 19   * @param {String|Array<string>} `list` List of strings to match.
 20   * @param {String|Array<string>} `patterns` One or more glob patterns to use for matching.
 21   * @param {Object} `options` See available [options](#options)
 22   * @return {Array} Returns an array of matches
 23   * @summary false
 24   * @api public
 25   */
 26  
 27  const micromatch = (list, patterns, options) => {
 28    patterns = [].concat(patterns);
 29    list = [].concat(list);
 30  
 31    let omit = new Set();
 32    let keep = new Set();
 33    let items = new Set();
 34    let negatives = 0;
 35  
 36    let onResult = state => {
 37      items.add(state.output);
 38      if (options && options.onResult) {
 39        options.onResult(state);
 40      }
 41    };
 42  
 43    for (let i = 0; i < patterns.length; i++) {
 44      let isMatch = picomatch(String(patterns[i]), { ...options, onResult }, true);
 45      let negated = isMatch.state.negated || isMatch.state.negatedExtglob;
 46      if (negated) negatives++;
 47  
 48      for (let item of list) {
 49        let matched = isMatch(item, true);
 50  
 51        let match = negated ? !matched.isMatch : matched.isMatch;
 52        if (!match) continue;
 53  
 54        if (negated) {
 55          omit.add(matched.output);
 56        } else {
 57          omit.delete(matched.output);
 58          keep.add(matched.output);
 59        }
 60      }
 61    }
 62  
 63    let result = negatives === patterns.length ? [...items] : [...keep];
 64    let matches = result.filter(item => !omit.has(item));
 65  
 66    if (options && matches.length === 0) {
 67      if (options.failglob === true) {
 68        throw new Error(`No matches found for "${patterns.join(', ')}"`);
 69      }
 70  
 71      if (options.nonull === true || options.nullglob === true) {
 72        return options.unescape ? patterns.map(p => p.replace(/\\/g, '')) : patterns;
 73      }
 74    }
 75  
 76    return matches;
 77  };
 78  
 79  /**
 80   * Backwards compatibility
 81   */
 82  
 83  micromatch.match = micromatch;
 84  
 85  /**
 86   * Returns a matcher function from the given glob `pattern` and `options`.
 87   * The returned function takes a string to match as its only argument and returns
 88   * true if the string is a match.
 89   *
 90   * ```js
 91   * const mm = require('micromatch');
 92   * // mm.matcher(pattern[, options]);
 93   *
 94   * const isMatch = mm.matcher('*.!(*a)');
 95   * console.log(isMatch('a.a')); //=> false
 96   * console.log(isMatch('a.b')); //=> true
 97   * ```
 98   * @param {String} `pattern` Glob pattern
 99   * @param {Object} `options`
100   * @return {Function} Returns a matcher function.
101   * @api public
102   */
103  
104  micromatch.matcher = (pattern, options) => picomatch(pattern, options);
105  
106  /**
107   * Returns true if **any** of the given glob `patterns` match the specified `string`.
108   *
109   * ```js
110   * const mm = require('micromatch');
111   * // mm.isMatch(string, patterns[, options]);
112   *
113   * console.log(mm.isMatch('a.a', ['b.*', '*.a'])); //=> true
114   * console.log(mm.isMatch('a.a', 'b.*')); //=> false
115   * ```
116   * @param {String} `str` The string to test.
117   * @param {String|Array} `patterns` One or more glob patterns to use for matching.
118   * @param {Object} `[options]` See available [options](#options).
119   * @return {Boolean} Returns true if any patterns match `str`
120   * @api public
121   */
122  
123  micromatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str);
124  
125  /**
126   * Backwards compatibility
127   */
128  
129  micromatch.any = micromatch.isMatch;
130  
131  /**
132   * Returns a list of strings that _**do not match any**_ of the given `patterns`.
133   *
134   * ```js
135   * const mm = require('micromatch');
136   * // mm.not(list, patterns[, options]);
137   *
138   * console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a'));
139   * //=> ['b.b', 'c.c']
140   * ```
141   * @param {Array} `list` Array of strings to match.
142   * @param {String|Array} `patterns` One or more glob pattern to use for matching.
143   * @param {Object} `options` See available [options](#options) for changing how matches are performed
144   * @return {Array} Returns an array of strings that **do not match** the given patterns.
145   * @api public
146   */
147  
148  micromatch.not = (list, patterns, options = {}) => {
149    patterns = [].concat(patterns).map(String);
150    let result = new Set();
151    let items = [];
152  
153    let onResult = state => {
154      if (options.onResult) options.onResult(state);
155      items.push(state.output);
156    };
157  
158    let matches = micromatch(list, patterns, { ...options, onResult });
159  
160    for (let item of items) {
161      if (!matches.includes(item)) {
162        result.add(item);
163      }
164    }
165    return [...result];
166  };
167  
168  /**
169   * Returns true if the given `string` contains the given pattern. Similar
170   * to [.isMatch](#isMatch) but the pattern can match any part of the string.
171   *
172   * ```js
173   * var mm = require('micromatch');
174   * // mm.contains(string, pattern[, options]);
175   *
176   * console.log(mm.contains('aa/bb/cc', '*b'));
177   * //=> true
178   * console.log(mm.contains('aa/bb/cc', '*d'));
179   * //=> false
180   * ```
181   * @param {String} `str` The string to match.
182   * @param {String|Array} `patterns` Glob pattern to use for matching.
183   * @param {Object} `options` See available [options](#options) for changing how matches are performed
184   * @return {Boolean} Returns true if any of the patterns matches any part of `str`.
185   * @api public
186   */
187  
188  micromatch.contains = (str, pattern, options) => {
189    if (typeof str !== 'string') {
190      throw new TypeError(`Expected a string: "${util.inspect(str)}"`);
191    }
192  
193    if (Array.isArray(pattern)) {
194      return pattern.some(p => micromatch.contains(str, p, options));
195    }
196  
197    if (typeof pattern === 'string') {
198      if (isEmptyString(str) || isEmptyString(pattern)) {
199        return false;
200      }
201  
202      if (str.includes(pattern) || (str.startsWith('./') && str.slice(2).includes(pattern))) {
203        return true;
204      }
205    }
206  
207    return micromatch.isMatch(str, pattern, { ...options, contains: true });
208  };
209  
210  /**
211   * Filter the keys of the given object with the given `glob` pattern
212   * and `options`. Does not attempt to match nested keys. If you need this feature,
213   * use [glob-object][] instead.
214   *
215   * ```js
216   * const mm = require('micromatch');
217   * // mm.matchKeys(object, patterns[, options]);
218   *
219   * const obj = { aa: 'a', ab: 'b', ac: 'c' };
220   * console.log(mm.matchKeys(obj, '*b'));
221   * //=> { ab: 'b' }
222   * ```
223   * @param {Object} `object` The object with keys to filter.
224   * @param {String|Array} `patterns` One or more glob patterns to use for matching.
225   * @param {Object} `options` See available [options](#options) for changing how matches are performed
226   * @return {Object} Returns an object with only keys that match the given patterns.
227   * @api public
228   */
229  
230  micromatch.matchKeys = (obj, patterns, options) => {
231    if (!utils.isObject(obj)) {
232      throw new TypeError('Expected the first argument to be an object');
233    }
234    let keys = micromatch(Object.keys(obj), patterns, options);
235    let res = {};
236    for (let key of keys) res[key] = obj[key];
237    return res;
238  };
239  
240  /**
241   * Returns true if some of the strings in the given `list` match any of the given glob `patterns`.
242   *
243   * ```js
244   * const mm = require('micromatch');
245   * // mm.some(list, patterns[, options]);
246   *
247   * console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js']));
248   * // true
249   * console.log(mm.some(['foo.js'], ['*.js', '!foo.js']));
250   * // false
251   * ```
252   * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found.
253   * @param {String|Array} `patterns` One or more glob patterns to use for matching.
254   * @param {Object} `options` See available [options](#options) for changing how matches are performed
255   * @return {Boolean} Returns true if any `patterns` matches any of the strings in `list`
256   * @api public
257   */
258  
259  micromatch.some = (list, patterns, options) => {
260    let items = [].concat(list);
261  
262    for (let pattern of [].concat(patterns)) {
263      let isMatch = picomatch(String(pattern), options);
264      if (items.some(item => isMatch(item))) {
265        return true;
266      }
267    }
268    return false;
269  };
270  
271  /**
272   * Returns true if every string in the given `list` matches
273   * any of the given glob `patterns`.
274   *
275   * ```js
276   * const mm = require('micromatch');
277   * // mm.every(list, patterns[, options]);
278   *
279   * console.log(mm.every('foo.js', ['foo.js']));
280   * // true
281   * console.log(mm.every(['foo.js', 'bar.js'], ['*.js']));
282   * // true
283   * console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js']));
284   * // false
285   * console.log(mm.every(['foo.js'], ['*.js', '!foo.js']));
286   * // false
287   * ```
288   * @param {String|Array} `list` The string or array of strings to test.
289   * @param {String|Array} `patterns` One or more glob patterns to use for matching.
290   * @param {Object} `options` See available [options](#options) for changing how matches are performed
291   * @return {Boolean} Returns true if all `patterns` matches all of the strings in `list`
292   * @api public
293   */
294  
295  micromatch.every = (list, patterns, options) => {
296    let items = [].concat(list);
297  
298    for (let pattern of [].concat(patterns)) {
299      let isMatch = picomatch(String(pattern), options);
300      if (!items.every(item => isMatch(item))) {
301        return false;
302      }
303    }
304    return true;
305  };
306  
307  /**
308   * Returns true if **all** of the given `patterns` match
309   * the specified string.
310   *
311   * ```js
312   * const mm = require('micromatch');
313   * // mm.all(string, patterns[, options]);
314   *
315   * console.log(mm.all('foo.js', ['foo.js']));
316   * // true
317   *
318   * console.log(mm.all('foo.js', ['*.js', '!foo.js']));
319   * // false
320   *
321   * console.log(mm.all('foo.js', ['*.js', 'foo.js']));
322   * // true
323   *
324   * console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js']));
325   * // true
326   * ```
327   * @param {String|Array} `str` The string to test.
328   * @param {String|Array} `patterns` One or more glob patterns to use for matching.
329   * @param {Object} `options` See available [options](#options) for changing how matches are performed
330   * @return {Boolean} Returns true if any patterns match `str`
331   * @api public
332   */
333  
334  micromatch.all = (str, patterns, options) => {
335    if (typeof str !== 'string') {
336      throw new TypeError(`Expected a string: "${util.inspect(str)}"`);
337    }
338  
339    return [].concat(patterns).every(p => picomatch(p, options)(str));
340  };
341  
342  /**
343   * Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match.
344   *
345   * ```js
346   * const mm = require('micromatch');
347   * // mm.capture(pattern, string[, options]);
348   *
349   * console.log(mm.capture('test/*.js', 'test/foo.js'));
350   * //=> ['foo']
351   * console.log(mm.capture('test/*.js', 'foo/bar.css'));
352   * //=> null
353   * ```
354   * @param {String} `glob` Glob pattern to use for matching.
355   * @param {String} `input` String to match
356   * @param {Object} `options` See available [options](#options) for changing how matches are performed
357   * @return {Array|null} Returns an array of captures if the input matches the glob pattern, otherwise `null`.
358   * @api public
359   */
360  
361  micromatch.capture = (glob, input, options) => {
362    let posix = utils.isWindows(options);
363    let regex = picomatch.makeRe(String(glob), { ...options, capture: true });
364    let match = regex.exec(posix ? utils.toPosixSlashes(input) : input);
365  
366    if (match) {
367      return match.slice(1).map(v => v === void 0 ? '' : v);
368    }
369  };
370  
371  /**
372   * Create a regular expression from the given glob `pattern`.
373   *
374   * ```js
375   * const mm = require('micromatch');
376   * // mm.makeRe(pattern[, options]);
377   *
378   * console.log(mm.makeRe('*.js'));
379   * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/
380   * ```
381   * @param {String} `pattern` A glob pattern to convert to regex.
382   * @param {Object} `options`
383   * @return {RegExp} Returns a regex created from the given pattern.
384   * @api public
385   */
386  
387  micromatch.makeRe = (...args) => picomatch.makeRe(...args);
388  
389  /**
390   * Scan a glob pattern to separate the pattern into segments. Used
391   * by the [split](#split) method.
392   *
393   * ```js
394   * const mm = require('micromatch');
395   * const state = mm.scan(pattern[, options]);
396   * ```
397   * @param {String} `pattern`
398   * @param {Object} `options`
399   * @return {Object} Returns an object with
400   * @api public
401   */
402  
403  micromatch.scan = (...args) => picomatch.scan(...args);
404  
405  /**
406   * Parse a glob pattern to create the source string for a regular
407   * expression.
408   *
409   * ```js
410   * const mm = require('micromatch');
411   * const state = mm(pattern[, options]);
412   * ```
413   * @param {String} `glob`
414   * @param {Object} `options`
415   * @return {Object} Returns an object with useful properties and output to be used as regex source string.
416   * @api public
417   */
418  
419  micromatch.parse = (patterns, options) => {
420    let res = [];
421    for (let pattern of [].concat(patterns || [])) {
422      for (let str of braces(String(pattern), options)) {
423        res.push(picomatch.parse(str, options));
424      }
425    }
426    return res;
427  };
428  
429  /**
430   * Process the given brace `pattern`.
431   *
432   * ```js
433   * const { braces } = require('micromatch');
434   * console.log(braces('foo/{a,b,c}/bar'));
435   * //=> [ 'foo/(a|b|c)/bar' ]
436   *
437   * console.log(braces('foo/{a,b,c}/bar', { expand: true }));
438   * //=> [ 'foo/a/bar', 'foo/b/bar', 'foo/c/bar' ]
439   * ```
440   * @param {String} `pattern` String with brace pattern to process.
441   * @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options.
442   * @return {Array}
443   * @api public
444   */
445  
446  micromatch.braces = (pattern, options) => {
447    if (typeof pattern !== 'string') throw new TypeError('Expected a string');
448    if ((options && options.nobrace === true) || !/\{.*\}/.test(pattern)) {
449      return [pattern];
450    }
451    return braces(pattern, options);
452  };
453  
454  /**
455   * Expand braces
456   */
457  
458  micromatch.braceExpand = (pattern, options) => {
459    if (typeof pattern !== 'string') throw new TypeError('Expected a string');
460    return micromatch.braces(pattern, { ...options, expand: true });
461  };
462  
463  /**
464   * Expose micromatch
465   */
466  
467  module.exports = micromatch;