2 * braces <https://github.com/jonschlinkert/braces>
4 * Copyright (c) 2014-2015, Jon Schlinkert.
5 * Licensed under the MIT license.
14 var expand = require('expand-range');
15 var repeat = require('repeat-element');
16 var tokens = require('preserve');
22 module.exports = function(str, options) {
23 if (typeof str !== 'string') {
24 throw new Error('braces expects a string');
26 return braces(str, options);
30 * Expand `{foo,bar}` or `{1..5}` braces in the
33 * @param {String} `str`
34 * @param {Array} `arr`
35 * @param {Object} `options`
39 function braces(str, arr, options) {
44 if (!Array.isArray(arr)) {
49 var opts = options || {};
52 if (typeof opts.nodupes === 'undefined') {
59 if (typeof opts === 'function') {
64 if (!(patternRe instanceof RegExp)) {
65 patternRe = patternRegex();
68 var matches = str.match(patternRe) || [];
73 return escapeCommas(str, arr, opts);
75 return escapeDots(str, arr, opts);
77 return escapePaths(str, arr, opts);
79 return splitWhitespace(str);
81 return exponential(str, opts, braces);
83 return emptyBraces(str, arr, opts);
86 return escapeBraces(str, arr, opts);
88 if (!/\{[^{]+\{/.test(str)) {
89 return arr.concat(str);
92 str = tokens.before(str, es6Regex());
96 if (!(braceRe instanceof RegExp)) {
97 braceRe = braceRegex();
100 var match = braceRe.exec(str);
105 var outter = match[1];
106 var inner = match[2];
107 if (inner === '') { return [str]; }
109 var segs, segsLength;
111 if (inner.indexOf('..') !== -1) {
112 segs = expand(inner, opts, fn) || inner.split(',');
113 segsLength = segs.length;
115 } else if (inner[0] === '"' || inner[0] === '\'') {
116 return arr.concat(str.split(/['"]/).join(''));
119 segs = inner.split(',');
121 return braces(str.replace(outter, wrap(segs, '|')), opts);
124 segsLength = segs.length;
125 if (segsLength === 1 && opts.bash) {
126 segs[0] = wrap(segs[0], '\\');
130 var len = segs.length;
134 var path = segs[i++];
136 if (/(\.[^.\/])/.test(path)) {
137 if (segsLength > 1) {
144 val = splice(str, outter, path);
146 if (/\{[^{}]+?\}/.test(val)) {
147 arr = braces(val, arr, opts);
148 } else if (val !== '') {
149 if (opts.nodupes && arr.indexOf(val) !== -1) { continue; }
150 arr.push(es6 ? tokens.after(val) : val);
154 if (opts.strict) { return filter(arr, filterEmpty); }
159 * Expand exponential ranges
161 * `a{,}{,}` => ['a', 'a', 'a', 'a']
164 function exponential(str, options, fn) {
165 if (typeof options === 'function') {
170 var opts = options || {};
171 var esc = '__ESC_EXP__';
175 var parts = str.split('{,}');
177 return fn(parts.join(''), opts);
180 exp = parts.length - 1;
181 res = fn(parts.join(esc), opts);
182 var len = res.length;
188 var idx = ele.indexOf(esc);
194 ele = ele.split('__ESC_EXP__').join('');
195 if (!!ele && opts.nodupes !== false) {
199 var num = Math.pow(2, exp);
200 arr.push.apply(arr, repeat(ele, num));
208 * Wrap a value with parens, brackets or braces,
209 * based on the given character/separator.
211 * @param {String|Array} `val`
212 * @param {String} `ch`
216 function wrap(val, ch) {
218 return '(' + val.join(ch) + ')';
221 return '{' + val.join(ch) + '}';
224 return '[' + val.join(ch) + ']';
227 return '\\{' + val + '\\}';
232 * Handle empty braces: `{}`
235 function emptyBraces(str, arr, opts) {
236 return braces(str.split('{}').join('\\{\\}'), arr, opts);
240 * Filter out empty-ish values
243 function filterEmpty(ele) {
244 return !!ele && ele !== '\\';
248 * Handle patterns with whitespace
251 function splitWhitespace(str) {
252 var segs = str.split(' ');
253 var len = segs.length;
258 res.push.apply(res, braces(segs[i++]));
264 * Handle escaped braces: `\\{foo,bar}`
267 function escapeBraces(str, arr, opts) {
268 if (!/\{[^{]+\{/.test(str)) {
269 return arr.concat(str.split('\\').join(''));
271 str = str.split('\\{').join('__LT_BRACE__');
272 str = str.split('\\}').join('__RT_BRACE__');
273 return map(braces(str, arr, opts), function(ele) {
274 ele = ele.split('__LT_BRACE__').join('{');
275 return ele.split('__RT_BRACE__').join('}');
281 * Handle escaped dots: `{1\\.2}`
284 function escapeDots(str, arr, opts) {
285 if (!/[^\\]\..+\\\./.test(str)) {
286 return arr.concat(str.split('\\').join(''));
288 str = str.split('\\.').join('__ESC_DOT__');
289 return map(braces(str, arr, opts), function(ele) {
290 return ele.split('__ESC_DOT__').join('.');
296 * Handle escaped dots: `{1\\.2}`
299 function escapePaths(str, arr, opts) {
300 str = str.split('\/.').join('__ESC_PATH__');
301 return map(braces(str, arr, opts), function(ele) {
302 return ele.split('__ESC_PATH__').join('\/.');
307 * Handle escaped commas: `{a\\,b}`
310 function escapeCommas(str, arr, opts) {
311 if (!/\w,/.test(str)) {
312 return arr.concat(str.split('\\').join(''));
314 str = str.split('\\,').join('__ESC_COMMA__');
315 return map(braces(str, arr, opts), function(ele) {
316 return ele.split('__ESC_COMMA__').join(',');
322 * Regex for common patterns
325 function patternRegex() {
326 return /\${|( (?=[{,}])|(?=[{,}]) )|{}|{,}|\\,(?=.*[{}])|\/\.(?=.*[{}])|\\\.(?={)|\\{|\\}/;
333 function braceRegex() {
334 return /.*(\\?\{([^}]+)\})/;
338 * es6 delimiter regex.
341 function es6Regex() {
342 return /\$\{([^}]+)\}/;
349 * Faster alternative to `String.replace()` when the
350 * index of the token to be replaces can't be supplied
353 function splice(str, token, replacement) {
354 var i = str.indexOf(token);
355 return str.substr(0, i) + replacement
356 + str.substr(i + token.length);
363 function map(arr, fn) {
368 var len = arr.length;
369 var res = new Array(len);
373 res[i] = fn(arr[i], i, arr);
383 function filter(arr, cb) {
384 if (arr == null) return [];
385 if (typeof cb !== 'function') {
386 throw new TypeError('braces: filter expects a callback function.');
389 var len = arr.length;
390 var res = arr.slice();
394 if (!cb(arr[len], i++)) {