UNPKG

7.79 kBJavaScriptView Raw
1/*!
2 * micromatch <https://github.com/jonschlinkert/micromatch>
3 *
4 * Copyright (c) 2014-2015, Jon Schlinkert.
5 * Licensed under the MIT License.
6 */
7
8'use strict';
9
10var utils = require('./utils');
11var Glob = require('./glob');
12
13/**
14 * Expose `expand`
15 */
16
17module.exports = expand;
18
19/**
20 * Expand a glob pattern to resolve braces and
21 * similar patterns before converting to regex.
22 *
23 * @param {String|Array} `pattern`
24 * @param {Array} `files`
25 * @param {Options} `opts`
26 * @return {Array}
27 */
28
29function expand(pattern, options) {
30 var opts = options || {};
31 var glob = new Glob(pattern, opts);
32
33 function replace(re, str) {
34 glob.repl(re, esc(str));
35 pattern = glob.pattern;
36 }
37
38 // return early if the glob pattern tests `true`
39 if (specialCase(pattern) && opts.safemode) {
40 return new RegExp(utils.escapeRe(pattern), 'g');
41 }
42
43 if (opts.nonegate !== true) {
44 opts.negated = glob.negated;
45 }
46
47 glob.repl('/.', '/\\.');
48
49 // expand braces, e.g `{1..5}`
50 glob.track('before brackets');
51 glob.brackets();
52 glob.track('before braces');
53 glob.braces();
54 glob.track('after braces');
55 glob.parse();
56
57 glob.repl('[]', '\\[\\]');
58 glob.repl('(?', '__QMARK_GROUP__');
59
60 // parse the glob pattern into tokens
61 var tok = glob.tokens;
62 if (tok.is.dotfile) {
63 opts.dot = true;
64 }
65
66 if (!tok.is.glob) {
67 return {
68 pattern: utils.escapePath(glob.pattern),
69 tokens: tok,
70 options: opts
71 };
72 }
73
74 // windows drives
75 replace(/^(\w):([\\\/]+?)/gi, lookahead + '$1:$2');
76
77 // negate slashes in exclusion ranges
78 if (/\[\^/.test(glob.pattern)) {
79 glob.pattern = negateSlash(glob.pattern);
80 }
81
82 if (glob.pattern === '**' && opts.globstar !== false) {
83 glob.pattern = globstar(opts);
84
85 } else {
86 if (/^\*\.\w*$/.test(glob.pattern)) {
87 glob.repl('*', star(opts.dot) + '\\');
88 glob.repl('__QMARK_GROUP__', '(?');
89 return glob;
90 }
91
92 // '/*/*/*' => '(?:/*){3}'
93 glob.repl(/(\/\*)+/g, function (match) {
94 var len = match.length / 2;
95 if (len === 1) { return match; }
96 return '(?:\\/*){' + len + '}';
97 });
98
99 glob.pattern = balance(glob.pattern, '[', ']');
100
101 // use heuristics to replace common escape patterns
102 glob.escape(glob.pattern);
103
104 // if the glob is for one directory deep, we can
105 // simplify the parsing and generated regex
106 if (tok.path.dirname === '') {
107 return expandFilename(glob, opts);
108 }
109
110 // if the pattern has `**`
111 if (tok.is.globstar) {
112 glob.pattern = collapse(glob.pattern, '**/');
113 glob.pattern = collapse(glob.pattern, '/**');
114 glob.pattern = optionalGlobstar(glob.pattern);
115
116 // reduce extraneous globstars
117 glob.repl(/(^|[^\\])\*{2,}([^\\]|$)/g, '$1**$2');
118
119 // 'foo/**'
120 replace(/(\w+)\*\*(?!\/)/g, '(?=.)$1[^/]*?');
121 replace('**/', '.*\\/');
122 replace('**', globstar(opts));
123 replace('/*', '\\/?' + nodot + '[^\\/]*?');
124 }
125
126 // ends with /*
127 replace(/\/\*$/g, '\\/' + stardot(opts));
128 // ends with *, no slashes
129 replace(/(?!\/)\*$/g, boxQ);
130 // has '*'
131 replace('*', stardot(opts));
132
133 replace('?.', '?\\.');
134 replace('?:', '?:');
135
136 glob.repl(/\?+/g, function (match) {
137 var len = match.length;
138 if (len === 1) {
139 return box;
140 }
141 return box + '{' + len + '}';
142 });
143
144 // escape '.abc' => '\\.abc'
145 glob.repl(/\.([*\w]+)/g, '\\.$1');
146 // fix '[^\\\\/]'
147 glob.repl(/\[\^[\\/]+\]/g, box);
148 // '///' => '\/'
149 glob.repl(/\/+/g, '\\/');
150 // '\\\\\\' => '\\'
151 glob.repl(/\\{2,}/g, '\\');
152 }
153
154 glob.repl('__QMARK_GROUP__', '(?');
155 glob.unescape(glob.pattern);
156 glob.repl('__UNESC_STAR__', '*');
157 glob.repl('%~', '?');
158 glob.repl('%%', '*');
159 glob.repl('?.', '?\\.');
160 glob.repl('[^\\/]', '[^/]');
161 return glob;
162}
163
164/**
165 * Expand the filename part of the glob into a regex
166 * compatible string
167 *
168 * @param {String} glob
169 * @param {Object} tok Tokens
170 * @param {Options} opts
171 * @return {Object}
172 */
173
174function expandFilename(glob, opts) {
175 var tok = glob.tokens;
176 switch (glob.pattern) {
177 case '.':
178 glob.pattern = '\\.';
179 break;
180 case '.*':
181 glob.pattern = '\\..*';
182 break;
183 case '*.*':
184 glob.pattern = star(opts.dot) + '\\.[^/]*?';
185 break;
186 case '*':
187 glob.pattern = star(opts.dot);
188 break;
189 default:
190 if (tok.path.basename === '*') {
191 glob.pattern = star(opts.dot) + '\\' + tok.path.extname;
192 } else {
193 glob.repl(/(?!\()\?/g, '[^/]');
194 if (tok.path.filename.charAt(0) !== '.') {
195 opts.dot = true;
196 }
197 glob.repl('*', star(opts.dot));
198 }
199 }
200
201 glob.repl('__QMARK_GROUP__', '(?');
202 glob.unescape(glob.pattern);
203 glob.repl('__UNESC_STAR__', '*');
204 return glob;
205}
206
207/**
208 * Special cases
209 */
210
211function specialCase(glob) {
212 if (glob === '\\') {
213 return true;
214 }
215 return false;
216}
217
218/**
219 * Collapse repeated character sequences.
220 *
221 * ```js
222 * collapse('a/../../../b', '../');
223 * //=> 'a/../b'
224 * ```
225 *
226 * @param {String} `str`
227 * @param {String} `ch`
228 * @return {String}
229 */
230
231function collapse(str, ch, repeat) {
232 var res = str.split(ch);
233 var len = res.length;
234
235 var isFirst = res[0] === '';
236 var isLast = res[res.length - 1] === '';
237 res = res.filter(Boolean);
238
239 if (isFirst) {
240 res.unshift('');
241 }
242 if (isLast) {
243 res.push('');
244 }
245 var diff = len - res.length;
246 if (repeat && diff >= 1) {
247 ch = '(?:' + ch + '){' + (diff + 1) + '}';
248 }
249 return res.join(ch);
250}
251
252/**
253 * Make globstars optional, as in glob spec:
254 *
255 * ```js
256 * optionalGlobstar('a\/**\/b');
257 * //=> '(?:a\/b|a\/**\/b)'
258 * ```
259 *
260 * @param {String} `str`
261 * @return {String}
262 */
263
264function optionalGlobstar(glob) {
265 // globstars preceded and followed by a word character
266 if (/\w\/\*\*\/\w/.test(glob)) {
267 var tmp = glob.split('/**/').join('/');
268 glob = '(?:' + tmp + '|' + glob + ')';
269
270 // leading globstars
271 } else if (/^\*\*\/\w/.test(glob)) {
272 glob = glob.split(/^\*\*\//).join('(^|.+\\/)');
273 }
274 return glob;
275}
276
277/**
278 * Negate slashes in exclusion ranges, per glob spec:
279 *
280 * ```js
281 * negateSlash('[^foo]');
282 * //=> '[^\\/foo]'
283 * ```
284 *
285 * @param {[type]} str [description]
286 * @return {[type]}
287 */
288
289function negateSlash(str) {
290 var re = /\[\^([^\]]*?)\]/g;
291 return str.replace(re, function (match, inner) {
292 if (inner.indexOf('/') === -1) {
293 inner = '\\/' + inner;
294 }
295 return '[^' + inner + ']';
296 })
297}
298
299/**
300 * Escape imbalanced braces/bracket
301 */
302
303function balance(str, a, b) {
304 var aarr = str.split(a);
305 var alen = aarr.join('').length;
306 var blen = str.split(b).join('').length;
307
308 if (alen !== blen) {
309 str = aarr.join('\\' + a);
310 return str.split(b).join('\\' + b);
311 }
312 return str;
313}
314
315/**
316 * Escape utils
317 */
318
319function esc(str) {
320 str = str.split('?').join('%~');
321 str = str.split('*').join('%%');
322 return str;
323}
324
325/**
326 * Special patterns to be converted to regex.
327 * Heuristics are used to simplify patterns
328 * and speed up processing.
329 */
330
331var box = '[^/]';
332var boxQ = '[^/]*?';
333var lookahead = '(?=.)';
334var nodot = '(?!\\.)(?=.)';
335
336var ex = {};
337ex.dotfileGlob = '(?:^|\\/)(?:\\.{1,2})(?:$|\\/)';
338ex.stardot = '(?!' + ex.dotfileGlob + ')(?=.)[^/]*?';
339ex.twoStarDot = '(?:(?!' + ex.dotfileGlob + ').)*?';
340
341/**
342 * Create a regex for `*`. If `dot` is true,
343 * or the pattern does not begin with a leading
344 * star, then return the simple regex.
345 */
346
347function star(dotfile) {
348 return dotfile ? boxQ : nodot + boxQ;
349}
350
351function dotstarbase(dotfile) {
352 var re = dotfile ? ex.dotfileGlob : '\\.';
353 return '(?!' + re + ')' + lookahead;
354}
355
356function globstar(opts) {
357 if (opts.dot) { return ex.twoStarDot; }
358 return '(?:(?!(?:^|\\/)\\.).)*?';
359}
360
361function stardot(opts) {
362 return dotstarbase(opts && opts.dot) + '[^/]*?';
363}