/*! * braces * * Copyright (c) 2014-2015, Jon Schlinkert. * Licensed under the MIT license. */ 'use strict'; /** * Module dependencies */ var expand = require('expand-range'); var repeat = require('repeat-element'); var tokens = require('preserve'); /** * Expose `braces` */ module.exports = function(str, options) { if (typeof str !== 'string') { throw new Error('braces expects a string'); } return braces(str, options); }; /** * Expand `{foo,bar}` or `{1..5}` braces in the * given `string`. * * @param {String} `str` * @param {Array} `arr` * @param {Object} `options` * @return {Array} */ function braces(str, arr, options) { if (str === '') { return []; } if (!Array.isArray(arr)) { options = arr; arr = []; } var opts = options || {}; arr = arr || []; if (typeof opts.nodupes === 'undefined') { opts.nodupes = true; } var fn = opts.fn; var es6; if (typeof opts === 'function') { fn = opts; opts = {}; } if (!(patternRe instanceof RegExp)) { patternRe = patternRegex(); } var matches = str.match(patternRe) || []; var m = matches[0]; switch(m) { case '\\,': return escapeCommas(str, arr, opts); case '\\.': return escapeDots(str, arr, opts); case '\/.': return escapePaths(str, arr, opts); case ' ': return splitWhitespace(str); case '{,}': return exponential(str, opts, braces); case '{}': return emptyBraces(str, arr, opts); case '\\{': case '\\}': return escapeBraces(str, arr, opts); case '${': if (!/\{[^{]+\{/.test(str)) { return arr.concat(str); } else { es6 = true; str = tokens.before(str, es6Regex()); } } if (!(braceRe instanceof RegExp)) { braceRe = braceRegex(); } var match = braceRe.exec(str); if (match == null) { return [str]; } var outter = match[1]; var inner = match[2]; if (inner === '') { return [str]; } var segs, segsLength; if (inner.indexOf('..') !== -1) { segs = expand(inner, opts, fn) || inner.split(','); segsLength = segs.length; } else if (inner[0] === '"' || inner[0] === '\'') { return arr.concat(str.split(/['"]/).join('')); } else { segs = inner.split(','); if (opts.makeRe) { return braces(str.replace(outter, wrap(segs, '|')), opts); } segsLength = segs.length; if (segsLength === 1 && opts.bash) { segs[0] = wrap(segs[0], '\\'); } } var len = segs.length; var i = 0, val; while (len--) { var path = segs[i++]; if (/(\.[^.\/])/.test(path)) { if (segsLength > 1) { return segs; } else { return [str]; } } val = splice(str, outter, path); if (/\{[^{}]+?\}/.test(val)) { arr = braces(val, arr, opts); } else if (val !== '') { if (opts.nodupes && arr.indexOf(val) !== -1) { continue; } arr.push(es6 ? tokens.after(val) : val); } } if (opts.strict) { return filter(arr, filterEmpty); } return arr; } /** * Expand exponential ranges * * `a{,}{,}` => ['a', 'a', 'a', 'a'] */ function exponential(str, options, fn) { if (typeof options === 'function') { fn = options; options = null; } var opts = options || {}; var esc = '__ESC_EXP__'; var exp = 0; var res; var parts = str.split('{,}'); if (opts.nodupes) { return fn(parts.join(''), opts); } exp = parts.length - 1; res = fn(parts.join(esc), opts); var len = res.length; var arr = []; var i = 0; while (len--) { var ele = res[i++]; var idx = ele.indexOf(esc); if (idx === -1) { arr.push(ele); } else { ele = ele.split('__ESC_EXP__').join(''); if (!!ele && opts.nodupes !== false) { arr.push(ele); } else { var num = Math.pow(2, exp); arr.push.apply(arr, repeat(ele, num)); } } } return arr; } /** * Wrap a value with parens, brackets or braces, * based on the given character/separator. * * @param {String|Array} `val` * @param {String} `ch` * @return {String} */ function wrap(val, ch) { if (ch === '|') { return '(' + val.join(ch) + ')'; } if (ch === ',') { return '{' + val.join(ch) + '}'; } if (ch === '-') { return '[' + val.join(ch) + ']'; } if (ch === '\\') { return '\\{' + val + '\\}'; } } /** * Handle empty braces: `{}` */ function emptyBraces(str, arr, opts) { return braces(str.split('{}').join('\\{\\}'), arr, opts); } /** * Filter out empty-ish values */ function filterEmpty(ele) { return !!ele && ele !== '\\'; } /** * Handle patterns with whitespace */ function splitWhitespace(str) { var segs = str.split(' '); var len = segs.length; var res = []; var i = 0; while (len--) { res.push.apply(res, braces(segs[i++])); } return res; } /** * Handle escaped braces: `\\{foo,bar}` */ function escapeBraces(str, arr, opts) { if (!/\{[^{]+\{/.test(str)) { return arr.concat(str.split('\\').join('')); } else { str = str.split('\\{').join('__LT_BRACE__'); str = str.split('\\}').join('__RT_BRACE__'); return map(braces(str, arr, opts), function(ele) { ele = ele.split('__LT_BRACE__').join('{'); return ele.split('__RT_BRACE__').join('}'); }); } } /** * Handle escaped dots: `{1\\.2}` */ function escapeDots(str, arr, opts) { if (!/[^\\]\..+\\\./.test(str)) { return arr.concat(str.split('\\').join('')); } else { str = str.split('\\.').join('__ESC_DOT__'); return map(braces(str, arr, opts), function(ele) { return ele.split('__ESC_DOT__').join('.'); }); } } /** * Handle escaped dots: `{1\\.2}` */ function escapePaths(str, arr, opts) { str = str.split('\/.').join('__ESC_PATH__'); return map(braces(str, arr, opts), function(ele) { return ele.split('__ESC_PATH__').join('\/.'); }); } /** * Handle escaped commas: `{a\\,b}` */ function escapeCommas(str, arr, opts) { if (!/\w,/.test(str)) { return arr.concat(str.split('\\').join('')); } else { str = str.split('\\,').join('__ESC_COMMA__'); return map(braces(str, arr, opts), function(ele) { return ele.split('__ESC_COMMA__').join(','); }); } } /** * Regex for common patterns */ function patternRegex() { return /\${|( (?=[{,}])|(?=[{,}]) )|{}|{,}|\\,(?=.*[{}])|\/\.(?=.*[{}])|\\\.(?={)|\\{|\\}/; } /** * Braces regex. */ function braceRegex() { return /.*(\\?\{([^}]+)\})/; } /** * es6 delimiter regex. */ function es6Regex() { return /\$\{([^}]+)\}/; } var braceRe; var patternRe; /** * Faster alternative to `String.replace()` when the * index of the token to be replaces can't be supplied */ function splice(str, token, replacement) { var i = str.indexOf(token); return str.substr(0, i) + replacement + str.substr(i + token.length); } /** * Fast array map */ function map(arr, fn) { if (arr == null) { return []; } var len = arr.length; var res = new Array(len); var i = -1; while (++i < len) { res[i] = fn(arr[i], i, arr); } return res; } /** * Fast array filter */ function filter(arr, cb) { if (arr == null) return []; if (typeof cb !== 'function') { throw new TypeError('braces: filter expects a callback function.'); } var len = arr.length; var res = arr.slice(); var i = 0; while (len--) { if (!cb(arr[len], i++)) { res.splice(len, 1); } } return res; }