codekingpro/portable-devtools
115k
1'use strict';
2
3const scan = require('./scan');
4const parse = require('./parse');
5const utils = require('./utils');
6const constants = require('./constants');
7const isObject = val => val && typeof val === 'object' && !Array.isArray(val);
8
9/**
10 * Creates a matcher function from one or more glob patterns. The
11 * returned function takes a string to match as its first argument,
12 * and returns true if the string is a match. The returned matcher
13 * function also takes a boolean as the second argument that, when true,
14 * returns an object with additional information.
15 *
16 * ```js
17 * const picomatch = require('picomatch');
18 * // picomatch(glob[, options]);
19 *
20 * const isMatch = picomatch('*.!(*a)');
21 * console.log(isMatch('a.a')); //=> false
22 * console.log(isMatch('a.b')); //=> true
23 * ```
24 * @name picomatch
25 * @param {String|Array} `globs` One or more glob patterns.
26 * @param {Object=} `options`
27 * @return {Function=} Returns a matcher function.
28 * @api public
29 */
30
31const picomatch = (glob, options, returnState = false) => {
32 if (Array.isArray(glob)) {
33 const fns = glob.map(input => picomatch(input, options, returnState));
34 const arrayMatcher = str => {
35 for (const isMatch of fns) {
36 const state = isMatch(str);
37 if (state) return state;
38 }
39 return false;
40 };
41 return arrayMatcher;
42 }
43
44 const isState = isObject(glob) && glob.tokens && glob.input;
45
46 if (glob === '' || (typeof glob !== 'string' && !isState)) {
47 throw new TypeError('Expected pattern to be a non-empty string');
48 }
49
50 const opts = options || {};
51 const posix = opts.windows;
52 const regex = isState
53 ? picomatch.compileRe(glob, options)
54 : picomatch.makeRe(glob, options, false, true);
55
56 const state = regex.state;
57 delete regex.state;
58
59 let isIgnored = () => false;
60 if (opts.ignore) {
61 const ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null };
62 isIgnored = picomatch(opts.ignore, ignoreOpts, returnState);
63 }
64
65 const matcher = (input, returnObject = false) => {
66 const { isMatch, match, output } = picomatch.test(input, regex, options, { glob, posix });
67 const result = { glob, state, regex, posix, input, output, match, isMatch };
68
69 if (typeof opts.onResult === 'function') {
70 opts.onResult(result);
71 }
72
73 if (isMatch === false) {
74 result.isMatch = false;
75 return returnObject ? result : false;
76 }
77
78 if (isIgnored(input)) {
79 if (typeof opts.onIgnore === 'function') {
80 opts.onIgnore(result);
81 }
82 result.isMatch = false;
83 return returnObject ? result : false;
84 }
85
86 if (typeof opts.onMatch === 'function') {
87 opts.onMatch(result);
88 }
89 return returnObject ? result : true;
90 };
91
92 if (returnState) {
93 matcher.state = state;
94 }
95
96 return matcher;
97};
98
99/**
100 * Test `input` with the given `regex`. This is used by the main
101 * `picomatch()` function to test the input string.
102 *
103 * ```js
104 * const picomatch = require('picomatch');
105 * // picomatch.test(input, regex[, options]);
106 *
107 * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/));
108 * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' }
109 * ```
110 * @param {String} `input` String to test.
111 * @param {RegExp} `regex`
112 * @return {Object} Returns an object with matching info.
113 * @api public
114 */
115
116picomatch.test = (input, regex, options, { glob, posix } = {}) => {
117 if (typeof input !== 'string') {
118 throw new TypeError('Expected input to be a string');
119 }
120
121 if (input === '') {
122 return { isMatch: false, output: '' };
123 }
124
125 const opts = options || {};
126 const format = opts.format || (posix ? utils.toPosixSlashes : null);
127 let match = input === glob;
128 let output = (match && format) ? format(input) : input;
129
130 if (match === false) {
131 output = format ? format(input) : input;
132 match = output === glob;
133 }
134
135 if (match === false || opts.capture === true) {
136 if (opts.matchBase === true || opts.basename === true) {
137 match = picomatch.matchBase(input, regex, options, posix);
138 } else {
139 match = regex.exec(output);
140 }
141 }
142
143 return { isMatch: Boolean(match), match, output };
144};
145
146/**
147 * Match the basename of a filepath.
148 *
149 * ```js
150 * const picomatch = require('picomatch');
151 * // picomatch.matchBase(input, glob[, options]);
152 * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true
153 * ```
154 * @param {String} `input` String to test.
155 * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe).
156 * @return {Boolean}
157 * @api public
158 */
159
160picomatch.matchBase = (input, glob, options) => {
161 const regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options);
162 return regex.test(utils.basename(input));
163};
164
165/**
166 * Returns true if **any** of the given glob `patterns` match the specified `string`.
167 *
168 * ```js
169 * const picomatch = require('picomatch');
170 * // picomatch.isMatch(string, patterns[, options]);
171 *
172 * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true
173 * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false
174 * ```
175 * @param {String|Array} str The string to test.
176 * @param {String|Array} patterns One or more glob patterns to use for matching.
177 * @param {Object} [options] See available [options](#options).
178 * @return {Boolean} Returns true if any patterns match `str`
179 * @api public
180 */
181
182picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str);
183
184/**
185 * Parse a glob pattern to create the source string for a regular
186 * expression.
187 *
188 * ```js
189 * const picomatch = require('picomatch');
190 * const result = picomatch.parse(pattern[, options]);
191 * ```
192 * @param {String} `pattern`
193 * @param {Object} `options`
194 * @return {Object} Returns an object with useful properties and output to be used as a regex source string.
195 * @api public
196 */
197
198picomatch.parse = (pattern, options) => {
199 if (Array.isArray(pattern)) return pattern.map(p => picomatch.parse(p, options));
200 return parse(pattern, { ...options, fastpaths: false });
201};
202
203/**
204 * Scan a glob pattern to separate the pattern into segments.
205 *
206 * ```js
207 * const picomatch = require('picomatch');
208 * // picomatch.scan(input[, options]);
209 *
210 * const result = picomatch.scan('!./foo/*.js');
211 * console.log(result);
212 * { prefix: '!./',
213 * input: '!./foo/*.js',
214 * start: 3,
215 * base: 'foo',
216 * glob: '*.js',
217 * isBrace: false,
218 * isBracket: false,
219 * isGlob: true,
220 * isExtglob: false,
221 * isGlobstar: false,
222 * negated: true }
223 * ```
224 * @param {String} `input` Glob pattern to scan.
225 * @param {Object} `options`
226 * @return {Object} Returns an object with
227 * @api public
228 */
229
230picomatch.scan = (input, options) => scan(input, options);
231
232/**
233 * Compile a regular expression from the `state` object returned by the
234 * [parse()](#parse) method.
235 *
236 * @param {Object} `state`
237 * @param {Object} `options`
238 * @param {Boolean} `returnOutput` Intended for implementors, this argument allows you to return the raw output from the parser.
239 * @param {Boolean} `returnState` Adds the state to a `state` property on the returned regex. Useful for implementors and debugging.
240 * @return {RegExp}
241 * @api public
242 */
243
244picomatch.compileRe = (state, options, returnOutput = false, returnState = false) => {
245 if (returnOutput === true) {
246 return state.output;
247 }
248
249 const opts = options || {};
250 const prepend = opts.contains ? '' : '^';
251 const append = opts.contains ? '' : '$';
252
253 let source = `${prepend}(?:${state.output})${append}`;
254 if (state && state.negated === true) {
255 source = `^(?!${source}).*$`;
256 }
257
258 const regex = picomatch.toRegex(source, options);
259 if (returnState === true) {
260 regex.state = state;
261 }
262
263 return regex;
264};
265
266/**
267 * Create a regular expression from a parsed glob pattern.
268 *
269 * ```js
270 * const picomatch = require('picomatch');
271 * const state = picomatch.parse('*.js');
272 * // picomatch.compileRe(state[, options]);
273 *
274 * console.log(picomatch.compileRe(state));
275 * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
276 * ```
277 * @param {String} `state` The object returned from the `.parse` method.
278 * @param {Object} `options`
279 * @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.
280 * @param {Boolean} `returnState` Implementors may use this argument to return the state from the parsed glob with the returned regular expression.
281 * @return {RegExp} Returns a regex created from the given pattern.
282 * @api public
283 */
284
285picomatch.makeRe = (input, options = {}, returnOutput = false, returnState = false) => {
286 if (!input || typeof input !== 'string') {
287 throw new TypeError('Expected a non-empty string');
288 }
289
290 let parsed = { negated: false, fastpaths: true };
291
292 if (options.fastpaths !== false && (input[0] === '.' || input[0] === '*')) {
293 parsed.output = parse.fastpaths(input, options);
294 }
295
296 if (!parsed.output) {
297 parsed = parse(input, options);
298 }
299
300 return picomatch.compileRe(parsed, options, returnOutput, returnState);
301};
302
303/**
304 * Create a regular expression from the given regex source string.
305 *
306 * ```js
307 * const picomatch = require('picomatch');
308 * // picomatch.toRegex(source[, options]);
309 *
310 * const { output } = picomatch.parse('*.js');
311 * console.log(picomatch.toRegex(output));
312 * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
313 * ```
314 * @param {String} `source` Regular expression source string.
315 * @param {Object} `options`
316 * @return {RegExp}
317 * @api public
318 */
319
320picomatch.toRegex = (source, options) => {
321 try {
322 const opts = options || {};
323 return new RegExp(source, opts.flags || (opts.nocase ? 'i' : ''));
324 } catch (err) {
325 if (options && options.debug === true) throw err;
326 return /$^/;
327 }
328};
329
330/**
331 * Picomatch constants.
332 * @return {Object}
333 */
334
335picomatch.constants = constants;
336
337/**
338 * Expose "picomatch"
339 */
340
341module.exports = picomatch;
342 