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;