305 lines
7.2 KiB
JavaScript
305 lines
7.2 KiB
JavaScript
/*!
|
|
* micromatch <https://github.com/jonschlinkert/micromatch>
|
|
*
|
|
* Copyright (c) 2014-2015, Jon Schlinkert.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var utils = require('./utils');
|
|
var Glob = require('./glob');
|
|
|
|
/**
|
|
* Expose `expand`
|
|
*/
|
|
|
|
module.exports = expand;
|
|
|
|
/**
|
|
* Expand a glob pattern to resolve braces and
|
|
* similar patterns before converting to regex.
|
|
*
|
|
* @param {String|Array} `pattern`
|
|
* @param {Array} `files`
|
|
* @param {Options} `opts`
|
|
* @return {Array}
|
|
*/
|
|
|
|
function expand(pattern, options) {
|
|
if (typeof pattern !== 'string') {
|
|
throw new TypeError('micromatch.expand(): argument should be a string.');
|
|
}
|
|
|
|
var glob = new Glob(pattern, options || {});
|
|
var opts = glob.options;
|
|
|
|
if (!utils.isGlob(pattern)) {
|
|
glob.pattern = glob.pattern.replace(/([\/.])/g, '\\$1');
|
|
return glob;
|
|
}
|
|
|
|
glob.pattern = glob.pattern.replace(/(\+)(?!\()/g, '\\$1');
|
|
glob.pattern = glob.pattern.split('$').join('\\$');
|
|
|
|
if (typeof opts.braces !== 'boolean' && typeof opts.nobraces !== 'boolean') {
|
|
opts.braces = true;
|
|
}
|
|
|
|
if (glob.pattern === '.*') {
|
|
return {
|
|
pattern: '\\.' + star,
|
|
tokens: tok,
|
|
options: opts
|
|
};
|
|
}
|
|
|
|
if (glob.pattern === '*') {
|
|
return {
|
|
pattern: oneStar(opts.dot),
|
|
tokens: tok,
|
|
options: opts
|
|
};
|
|
}
|
|
|
|
// parse the glob pattern into tokens
|
|
glob.parse();
|
|
var tok = glob.tokens;
|
|
tok.is.negated = opts.negated;
|
|
|
|
// dotfile handling
|
|
if ((opts.dotfiles === true || tok.is.dotfile) && opts.dot !== false) {
|
|
opts.dotfiles = true;
|
|
opts.dot = true;
|
|
}
|
|
|
|
if ((opts.dotdirs === true || tok.is.dotdir) && opts.dot !== false) {
|
|
opts.dotdirs = true;
|
|
opts.dot = true;
|
|
}
|
|
|
|
// check for braces with a dotfile pattern
|
|
if (/[{,]\./.test(glob.pattern)) {
|
|
opts.makeRe = false;
|
|
opts.dot = true;
|
|
}
|
|
|
|
if (opts.nonegate !== true) {
|
|
opts.negated = glob.negated;
|
|
}
|
|
|
|
// if the leading character is a dot or a slash, escape it
|
|
if (glob.pattern.charAt(0) === '.' && glob.pattern.charAt(1) !== '/') {
|
|
glob.pattern = '\\' + glob.pattern;
|
|
}
|
|
|
|
/**
|
|
* Extended globs
|
|
*/
|
|
|
|
// expand braces, e.g `{1..5}`
|
|
glob.track('before braces');
|
|
if (tok.is.braces) {
|
|
glob.braces();
|
|
}
|
|
glob.track('after braces');
|
|
|
|
// expand extglobs, e.g `foo/!(a|b)`
|
|
glob.track('before extglob');
|
|
if (tok.is.extglob) {
|
|
glob.extglob();
|
|
}
|
|
glob.track('after extglob');
|
|
|
|
// expand brackets, e.g `[[:alpha:]]`
|
|
glob.track('before brackets');
|
|
if (tok.is.brackets) {
|
|
glob.brackets();
|
|
}
|
|
glob.track('after brackets');
|
|
|
|
// special patterns
|
|
glob._replace('[!', '[^');
|
|
glob._replace('(?', '(%~');
|
|
glob._replace(/\[\]/, '\\[\\]');
|
|
glob._replace('/[', '/' + (opts.dot ? dotfiles : nodot) + '[', true);
|
|
glob._replace('/?', '/' + (opts.dot ? dotfiles : nodot) + '[^/]', true);
|
|
glob._replace('/.', '/(?=.)\\.', true);
|
|
|
|
// windows drives
|
|
glob._replace(/^(\w):([\\\/]+?)/gi, '(?=.)$1:$2', true);
|
|
|
|
// negate slashes in exclusion ranges
|
|
if (glob.pattern.indexOf('[^') !== -1) {
|
|
glob.pattern = negateSlash(glob.pattern);
|
|
}
|
|
|
|
if (opts.globstar !== false && glob.pattern === '**') {
|
|
glob.pattern = globstar(opts.dot);
|
|
|
|
} else {
|
|
glob.pattern = balance(glob.pattern, '[', ']');
|
|
glob.escape(glob.pattern);
|
|
|
|
// if the pattern has `**`
|
|
if (tok.is.globstar) {
|
|
glob.pattern = collapse(glob.pattern, '/**');
|
|
glob.pattern = collapse(glob.pattern, '**/');
|
|
glob._replace('/**/', '(?:/' + globstar(opts.dot) + '/|/)', true);
|
|
glob._replace(/\*{2,}/g, '**');
|
|
|
|
// 'foo/*'
|
|
glob._replace(/(\w+)\*(?!\/)/g, '$1[^/]*?', true);
|
|
glob._replace(/\*\*\/\*(\w)/g, globstar(opts.dot) + '\\/' + (opts.dot ? dotfiles : nodot) + '[^/]*?$1', true);
|
|
|
|
if (opts.dot !== true) {
|
|
glob._replace(/\*\*\/(.)/g, '(?:**\\/|)$1');
|
|
}
|
|
|
|
// 'foo/**' or '{**,*}', but not 'foo**'
|
|
if (tok.path.dirname !== '' || /,\*\*|\*\*,/.test(glob.orig)) {
|
|
glob._replace('**', globstar(opts.dot), true);
|
|
}
|
|
}
|
|
|
|
// ends with /*
|
|
glob._replace(/\/\*$/, '\\/' + oneStar(opts.dot), true);
|
|
// ends with *, no slashes
|
|
glob._replace(/(?!\/)\*$/, star, true);
|
|
// has 'n*.' (partial wildcard w/ file extension)
|
|
glob._replace(/([^\/]+)\*/, '$1' + oneStar(true), true);
|
|
// has '*'
|
|
glob._replace('*', oneStar(opts.dot), true);
|
|
glob._replace('?.', '?\\.', true);
|
|
glob._replace('?:', '?:', true);
|
|
|
|
glob._replace(/\?+/g, function(match) {
|
|
var len = match.length;
|
|
if (len === 1) {
|
|
return qmark;
|
|
}
|
|
return qmark + '{' + len + '}';
|
|
});
|
|
|
|
// escape '.abc' => '\\.abc'
|
|
glob._replace(/\.([*\w]+)/g, '\\.$1');
|
|
// fix '[^\\\\/]'
|
|
glob._replace(/\[\^[\\\/]+\]/g, qmark);
|
|
// '///' => '\/'
|
|
glob._replace(/\/+/g, '\\/');
|
|
// '\\\\\\' => '\\'
|
|
glob._replace(/\\{2,}/g, '\\');
|
|
}
|
|
|
|
// unescape previously escaped patterns
|
|
glob.unescape(glob.pattern);
|
|
glob._replace('__UNESC_STAR__', '*');
|
|
|
|
// escape dots that follow qmarks
|
|
glob._replace('?.', '?\\.');
|
|
|
|
// remove unnecessary slashes in character classes
|
|
glob._replace('[^\\/]', qmark);
|
|
|
|
if (glob.pattern.length > 1) {
|
|
if (/^[\[?*]/.test(glob.pattern)) {
|
|
// only prepend the string if we don't want to match dotfiles
|
|
glob.pattern = (opts.dot ? dotfiles : nodot) + glob.pattern;
|
|
}
|
|
}
|
|
|
|
return glob;
|
|
}
|
|
|
|
/**
|
|
* Collapse repeated character sequences.
|
|
*
|
|
* ```js
|
|
* collapse('a/../../../b', '../');
|
|
* //=> 'a/../b'
|
|
* ```
|
|
*
|
|
* @param {String} `str`
|
|
* @param {String} `ch` Character sequence to collapse
|
|
* @return {String}
|
|
*/
|
|
|
|
function collapse(str, ch) {
|
|
var res = str.split(ch);
|
|
var isFirst = res[0] === '';
|
|
var isLast = res[res.length - 1] === '';
|
|
res = res.filter(Boolean);
|
|
if (isFirst) res.unshift('');
|
|
if (isLast) res.push('');
|
|
return res.join(ch);
|
|
}
|
|
|
|
/**
|
|
* Negate slashes in exclusion ranges, per glob spec:
|
|
*
|
|
* ```js
|
|
* negateSlash('[^foo]');
|
|
* //=> '[^\\/foo]'
|
|
* ```
|
|
*
|
|
* @param {String} `str` glob pattern
|
|
* @return {String}
|
|
*/
|
|
|
|
function negateSlash(str) {
|
|
return str.replace(/\[\^([^\]]*?)\]/g, function(match, inner) {
|
|
if (inner.indexOf('/') === -1) {
|
|
inner = '\\/' + inner;
|
|
}
|
|
return '[^' + inner + ']';
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Escape imbalanced braces/bracket. This is a very
|
|
* basic, naive implementation that only does enough
|
|
* to serve the purpose.
|
|
*/
|
|
|
|
function balance(str, a, b) {
|
|
var aarr = str.split(a);
|
|
var alen = aarr.join('').length;
|
|
var blen = str.split(b).join('').length;
|
|
|
|
if (alen !== blen) {
|
|
str = aarr.join('\\' + a);
|
|
return str.split(b).join('\\' + b);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* Special patterns to be converted to regex.
|
|
* Heuristics are used to simplify patterns
|
|
* and speed up processing.
|
|
*/
|
|
|
|
/* eslint no-multi-spaces: 0 */
|
|
var qmark = '[^/]';
|
|
var star = qmark + '*?';
|
|
var nodot = '(?!\\.)(?=.)';
|
|
var dotfileGlob = '(?:\\/|^)\\.{1,2}($|\\/)';
|
|
var dotfiles = '(?!' + dotfileGlob + ')(?=.)';
|
|
var twoStarDot = '(?:(?!' + dotfileGlob + ').)*?';
|
|
|
|
/**
|
|
* Create a regex for `*`.
|
|
*
|
|
* If `dot` is true, or the pattern does not begin with
|
|
* a leading star, then return the simpler regex.
|
|
*/
|
|
|
|
function oneStar(dotfile) {
|
|
return dotfile ? '(?!' + dotfileGlob + ')(?=.)' + star : (nodot + star);
|
|
}
|
|
|
|
function globstar(dotfile) {
|
|
if (dotfile) { return twoStarDot; }
|
|
return '(?:(?!(?:\\/|^)\\.).)*?';
|
|
}
|