1 var concatMap = require('concat-map');
2 var balanced = require('balanced-match');
4 module.exports = expandTop;
6 var escSlash = '\0SLASH'+Math.random()+'\0';
7 var escOpen = '\0OPEN'+Math.random()+'\0';
8 var escClose = '\0CLOSE'+Math.random()+'\0';
9 var escComma = '\0COMMA'+Math.random()+'\0';
10 var escPeriod = '\0PERIOD'+Math.random()+'\0';
12 function numeric(str) {
13 return parseInt(str, 10) == str
18 function escapeBraces(str) {
19 return str.split('\\\\').join(escSlash)
20 .split('\\{').join(escOpen)
21 .split('\\}').join(escClose)
22 .split('\\,').join(escComma)
23 .split('\\.').join(escPeriod);
26 function unescapeBraces(str) {
27 return str.split(escSlash).join('\\')
28 .split(escOpen).join('{')
29 .split(escClose).join('}')
30 .split(escComma).join(',')
31 .split(escPeriod).join('.');
35 // Basically just str.split(","), but handling cases
36 // where we have nested braced sections, which should be
37 // treated as individual members, like {a,{b,c},d}
38 function parseCommaParts(str) {
43 var m = balanced('{', '}', str);
46 return str.split(',');
51 var p = pre.split(',');
53 p[p.length-1] += '{' + body + '}';
54 var postParts = parseCommaParts(post);
56 p[p.length-1] += postParts.shift();
57 p.push.apply(p, postParts);
60 parts.push.apply(parts, p);
65 function expandTop(str) {
69 return expand(escapeBraces(str), true).map(unescapeBraces);
72 function identity(e) {
76 function embrace(str) {
77 return '{' + str + '}';
79 function isPadded(el) {
80 return /^-?0\d/.test(el);
90 function expand(str, isTop) {
93 var m = balanced('{', '}', str);
94 if (!m || /\$$/.test(m.pre)) return [str];
96 var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
97 var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
98 var isSequence = isNumericSequence || isAlphaSequence;
99 var isOptions = /^(.*,)+(.+)?$/.test(m.body);
100 if (!isSequence && !isOptions) {
102 if (m.post.match(/,.*}/)) {
103 str = m.pre + '{' + m.body + escClose + m.post;
111 n = m.body.split(/\.\./);
113 n = parseCommaParts(m.body);
114 if (n.length === 1) {
115 // x{{a,b}}y ==> x{a}y x{b}y
116 n = expand(n[0], false).map(embrace);
117 if (n.length === 1) {
118 var post = m.post.length
119 ? expand(m.post, false)
121 return post.map(function(p) {
122 return m.pre + n[0] + p;
128 // at this point, n is the parts, and we know it's not a comma set
129 // with a single entry.
131 // no need to expand pre, since it is guaranteed to be free of brace-sets
133 var post = m.post.length
134 ? expand(m.post, false)
140 var x = numeric(n[0]);
141 var y = numeric(n[1]);
142 var width = Math.max(n[0].length, n[1].length)
143 var incr = n.length == 3
144 ? Math.abs(numeric(n[2]))
152 var pad = n.some(isPadded);
156 for (var i = x; test(i, y); i += incr) {
158 if (isAlphaSequence) {
159 c = String.fromCharCode(i);
165 var need = width - c.length;
167 var z = new Array(need + 1).join('0');
169 c = '-' + z + c.slice(1);
178 N = concatMap(n, function(el) { return expand(el, false) });
181 for (var j = 0; j < N.length; j++) {
182 for (var k = 0; k < post.length; k++) {
183 var expansion = pre + N[j] + post[k];
184 if (!isTop || isSequence || expansion)
185 expansions.push(expansion);