/*! * 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 { // '/*/*/*' => '(?:/*){3}' glob._replace(/(\/\*)+/g, function(match) { var len = match.length / 2; if (len === 1) { return match; } return '(?:\\/*){' + len + '}'; }); 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 '(?:(?!(?:\\/|^)\\.).)*?'; }