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.
304 lines
7.2 KiB
304 lines
7.2 KiB
/*! |
|
* 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 '(?:(?!(?:\\/|^)\\.).)*?'; |
|
}
|
|
|