You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
9.7 KiB
342 lines
9.7 KiB
'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;
|
|
|