343 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			343 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | const path = require('path'); | ||
|  | const scan = require('./scan'); | ||
|  | const parse = require('./parse'); | ||
|  | const utils = require('./utils'); | ||
|  | const constants = require('./constants'); | ||
|  | const isObject = val => val && typeof val === 'object' && !Array.isArray(val); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Creates a matcher function from one or more glob patterns. The | ||
|  |  * returned function takes a string to match as its first argument, | ||
|  |  * and returns true if the string is a match. The returned matcher | ||
|  |  * function also takes a boolean as the second argument that, when true, | ||
|  |  * returns an object with additional information. | ||
|  |  * | ||
|  |  * ```js
 | ||
|  |  * const picomatch = require('picomatch'); | ||
|  |  * // picomatch(glob[, options]);
 | ||
|  |  * | ||
|  |  * const isMatch = picomatch('*.!(*a)'); | ||
|  |  * console.log(isMatch('a.a')); //=> false
 | ||
|  |  * console.log(isMatch('a.b')); //=> true
 | ||
|  |  * ```
 | ||
|  |  * @name picomatch | ||
|  |  * @param {String|Array} `globs` One or more glob patterns. | ||
|  |  * @param {Object=} `options` | ||
|  |  * @return {Function=} Returns a matcher function. | ||
|  |  * @api public | ||
|  |  */ | ||
|  | 
 | ||
|  | const picomatch = (glob, options, returnState = false) => { | ||
|  |   if (Array.isArray(glob)) { | ||
|  |     const fns = glob.map(input => picomatch(input, options, returnState)); | ||
|  |     const arrayMatcher = str => { | ||
|  |       for (const isMatch of fns) { | ||
|  |         const state = isMatch(str); | ||
|  |         if (state) return state; | ||
|  |       } | ||
|  |       return false; | ||
|  |     }; | ||
|  |     return arrayMatcher; | ||
|  |   } | ||
|  | 
 | ||
|  |   const isState = isObject(glob) && glob.tokens && glob.input; | ||
|  | 
 | ||
|  |   if (glob === '' || (typeof glob !== 'string' && !isState)) { | ||
|  |     throw new TypeError('Expected pattern to be a non-empty string'); | ||
|  |   } | ||
|  | 
 | ||
|  |   const opts = options || {}; | ||
|  |   const posix = utils.isWindows(options); | ||
|  |   const regex = isState | ||
|  |     ? picomatch.compileRe(glob, options) | ||
|  |     : picomatch.makeRe(glob, options, false, true); | ||
|  | 
 | ||
|  |   const state = regex.state; | ||
|  |   delete regex.state; | ||
|  | 
 | ||
|  |   let isIgnored = () => false; | ||
|  |   if (opts.ignore) { | ||
|  |     const ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; | ||
|  |     isIgnored = picomatch(opts.ignore, ignoreOpts, returnState); | ||
|  |   } | ||
|  | 
 | ||
|  |   const matcher = (input, returnObject = false) => { | ||
|  |     const { isMatch, match, output } = picomatch.test(input, regex, options, { glob, posix }); | ||
|  |     const result = { glob, state, regex, posix, input, output, match, isMatch }; | ||
|  | 
 | ||
|  |     if (typeof opts.onResult === 'function') { | ||
|  |       opts.onResult(result); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (isMatch === false) { | ||
|  |       result.isMatch = false; | ||
|  |       return returnObject ? result : false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (isIgnored(input)) { | ||
|  |       if (typeof opts.onIgnore === 'function') { | ||
|  |         opts.onIgnore(result); | ||
|  |       } | ||
|  |       result.isMatch = false; | ||
|  |       return returnObject ? result : false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (typeof opts.onMatch === 'function') { | ||
|  |       opts.onMatch(result); | ||
|  |     } | ||
|  |     return returnObject ? result : true; | ||
|  |   }; | ||
|  | 
 | ||
|  |   if (returnState) { | ||
|  |     matcher.state = state; | ||
|  |   } | ||
|  | 
 | ||
|  |   return matcher; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Test `input` with the given `regex`. This is used by the main | ||
|  |  * `picomatch()` function to test the input string. | ||
|  |  * | ||
|  |  * ```js
 | ||
|  |  * const picomatch = require('picomatch'); | ||
|  |  * // picomatch.test(input, regex[, options]);
 | ||
|  |  * | ||
|  |  * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/)); | ||
|  |  * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' }
 | ||
|  |  * ```
 | ||
|  |  * @param {String} `input` String to test. | ||
|  |  * @param {RegExp} `regex` | ||
|  |  * @return {Object} Returns an object with matching info. | ||
|  |  * @api public | ||
|  |  */ | ||
|  | 
 | ||
|  | picomatch.test = (input, regex, options, { glob, posix } = {}) => { | ||
|  |   if (typeof input !== 'string') { | ||
|  |     throw new TypeError('Expected input to be a string'); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (input === '') { | ||
|  |     return { isMatch: false, output: '' }; | ||
|  |   } | ||
|  | 
 | ||
|  |   const opts = options || {}; | ||
|  |   const format = opts.format || (posix ? utils.toPosixSlashes : null); | ||
|  |   let match = input === glob; | ||
|  |   let output = (match && format) ? format(input) : input; | ||
|  | 
 | ||
|  |   if (match === false) { | ||
|  |     output = format ? format(input) : input; | ||
|  |     match = output === glob; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (match === false || opts.capture === true) { | ||
|  |     if (opts.matchBase === true || opts.basename === true) { | ||
|  |       match = picomatch.matchBase(input, regex, options, posix); | ||
|  |     } else { | ||
|  |       match = regex.exec(output); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return { isMatch: Boolean(match), match, output }; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Match the basename of a filepath. | ||
|  |  * | ||
|  |  * ```js
 | ||
|  |  * const picomatch = require('picomatch'); | ||
|  |  * // picomatch.matchBase(input, glob[, options]);
 | ||
|  |  * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true
 | ||
|  |  * ```
 | ||
|  |  * @param {String} `input` String to test. | ||
|  |  * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe). | ||
|  |  * @return {Boolean} | ||
|  |  * @api public | ||
|  |  */ | ||
|  | 
 | ||
|  | picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => { | ||
|  |   const regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options); | ||
|  |   return regex.test(path.basename(input)); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Returns true if **any** of the given glob `patterns` match the specified `string`. | ||
|  |  * | ||
|  |  * ```js
 | ||
|  |  * const picomatch = require('picomatch'); | ||
|  |  * // picomatch.isMatch(string, patterns[, options]);
 | ||
|  |  * | ||
|  |  * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true
 | ||
|  |  * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false
 | ||
|  |  * ```
 | ||
|  |  * @param {String|Array} str The string to test. | ||
|  |  * @param {String|Array} patterns One or more glob patterns to use for matching. | ||
|  |  * @param {Object} [options] See available [options](#options). | ||
|  |  * @return {Boolean} Returns true if any patterns match `str` | ||
|  |  * @api public | ||
|  |  */ | ||
|  | 
 | ||
|  | picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Parse a glob pattern to create the source string for a regular | ||
|  |  * expression. | ||
|  |  * | ||
|  |  * ```js
 | ||
|  |  * const picomatch = require('picomatch'); | ||
|  |  * const result = picomatch.parse(pattern[, options]); | ||
|  |  * ```
 | ||
|  |  * @param {String} `pattern` | ||
|  |  * @param {Object} `options` | ||
|  |  * @return {Object} Returns an object with useful properties and output to be used as a regex source string. | ||
|  |  * @api public | ||
|  |  */ | ||
|  | 
 | ||
|  | picomatch.parse = (pattern, options) => { | ||
|  |   if (Array.isArray(pattern)) return pattern.map(p => picomatch.parse(p, options)); | ||
|  |   return parse(pattern, { ...options, fastpaths: false }); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Scan a glob pattern to separate the pattern into segments. | ||
|  |  * | ||
|  |  * ```js
 | ||
|  |  * const picomatch = require('picomatch'); | ||
|  |  * // picomatch.scan(input[, options]);
 | ||
|  |  * | ||
|  |  * const result = picomatch.scan('!./foo/*.js'); | ||
|  |  * console.log(result); | ||
|  |  * { prefix: '!./', | ||
|  |  *   input: '!./foo/*.js', | ||
|  |  *   start: 3, | ||
|  |  *   base: 'foo', | ||
|  |  *   glob: '*.js', | ||
|  |  *   isBrace: false, | ||
|  |  *   isBracket: false, | ||
|  |  *   isGlob: true, | ||
|  |  *   isExtglob: false, | ||
|  |  *   isGlobstar: false, | ||
|  |  *   negated: true } | ||
|  |  * ```
 | ||
|  |  * @param {String} `input` Glob pattern to scan. | ||
|  |  * @param {Object} `options` | ||
|  |  * @return {Object} Returns an object with | ||
|  |  * @api public | ||
|  |  */ | ||
|  | 
 | ||
|  | picomatch.scan = (input, options) => scan(input, options); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Compile a regular expression from the `state` object returned by the | ||
|  |  * [parse()](#parse) method. | ||
|  |  * | ||
|  |  * @param {Object} `state` | ||
|  |  * @param {Object} `options` | ||
|  |  * @param {Boolean} `returnOutput` Intended for implementors, this argument allows you to return the raw output from the parser. | ||
|  |  * @param {Boolean} `returnState` Adds the state to a `state` property on the returned regex. Useful for implementors and debugging. | ||
|  |  * @return {RegExp} | ||
|  |  * @api public | ||
|  |  */ | ||
|  | 
 | ||
|  | picomatch.compileRe = (state, options, returnOutput = false, returnState = false) => { | ||
|  |   if (returnOutput === true) { | ||
|  |     return state.output; | ||
|  |   } | ||
|  | 
 | ||
|  |   const opts = options || {}; | ||
|  |   const prepend = opts.contains ? '' : '^'; | ||
|  |   const append = opts.contains ? '' : '$'; | ||
|  | 
 | ||
|  |   let source = `${prepend}(?:${state.output})${append}`; | ||
|  |   if (state && state.negated === true) { | ||
|  |     source = `^(?!${source}).*$`; | ||
|  |   } | ||
|  | 
 | ||
|  |   const regex = picomatch.toRegex(source, options); | ||
|  |   if (returnState === true) { | ||
|  |     regex.state = state; | ||
|  |   } | ||
|  | 
 | ||
|  |   return regex; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Create a regular expression from a parsed glob pattern. | ||
|  |  * | ||
|  |  * ```js
 | ||
|  |  * const picomatch = require('picomatch'); | ||
|  |  * const state = picomatch.parse('*.js'); | ||
|  |  * // picomatch.compileRe(state[, options]);
 | ||
|  |  * | ||
|  |  * console.log(picomatch.compileRe(state)); | ||
|  |  * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
 | ||
|  |  * ```
 | ||
|  |  * @param {String} `state` The object returned from the `.parse` method. | ||
|  |  * @param {Object} `options` | ||
|  |  * @param {Boolean} `returnOutput` Implementors may use this argument to return the compiled output, instead of a regular expression. This is not exposed on the options to prevent end-users from mutating the result. | ||
|  |  * @param {Boolean} `returnState` Implementors may use this argument to return the state from the parsed glob with the returned regular expression. | ||
|  |  * @return {RegExp} Returns a regex created from the given pattern. | ||
|  |  * @api public | ||
|  |  */ | ||
|  | 
 | ||
|  | picomatch.makeRe = (input, options = {}, returnOutput = false, returnState = false) => { | ||
|  |   if (!input || typeof input !== 'string') { | ||
|  |     throw new TypeError('Expected a non-empty string'); | ||
|  |   } | ||
|  | 
 | ||
|  |   let parsed = { negated: false, fastpaths: true }; | ||
|  | 
 | ||
|  |   if (options.fastpaths !== false && (input[0] === '.' || input[0] === '*')) { | ||
|  |     parsed.output = parse.fastpaths(input, options); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!parsed.output) { | ||
|  |     parsed = parse(input, options); | ||
|  |   } | ||
|  | 
 | ||
|  |   return picomatch.compileRe(parsed, options, returnOutput, returnState); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Create a regular expression from the given regex source string. | ||
|  |  * | ||
|  |  * ```js
 | ||
|  |  * const picomatch = require('picomatch'); | ||
|  |  * // picomatch.toRegex(source[, options]);
 | ||
|  |  * | ||
|  |  * const { output } = picomatch.parse('*.js'); | ||
|  |  * console.log(picomatch.toRegex(output)); | ||
|  |  * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
 | ||
|  |  * ```
 | ||
|  |  * @param {String} `source` Regular expression source string. | ||
|  |  * @param {Object} `options` | ||
|  |  * @return {RegExp} | ||
|  |  * @api public | ||
|  |  */ | ||
|  | 
 | ||
|  | picomatch.toRegex = (source, options) => { | ||
|  |   try { | ||
|  |     const opts = options || {}; | ||
|  |     return new RegExp(source, opts.flags || (opts.nocase ? 'i' : '')); | ||
|  |   } catch (err) { | ||
|  |     if (options && options.debug === true) throw err; | ||
|  |     return /$^/; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Picomatch constants. | ||
|  |  * @return {Object} | ||
|  |  */ | ||
|  | 
 | ||
|  | picomatch.constants = constants; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Expose "picomatch" | ||
|  |  */ | ||
|  | 
 | ||
|  | module.exports = picomatch; |