202 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			202 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | var concatMap = require('concat-map'); | ||
|  | var balanced = require('balanced-match'); | ||
|  | 
 | ||
|  | module.exports = expandTop; | ||
|  | 
 | ||
|  | var escSlash = '\0SLASH'+Math.random()+'\0'; | ||
|  | var escOpen = '\0OPEN'+Math.random()+'\0'; | ||
|  | var escClose = '\0CLOSE'+Math.random()+'\0'; | ||
|  | var escComma = '\0COMMA'+Math.random()+'\0'; | ||
|  | var escPeriod = '\0PERIOD'+Math.random()+'\0'; | ||
|  | 
 | ||
|  | function numeric(str) { | ||
|  |   return parseInt(str, 10) == str | ||
|  |     ? parseInt(str, 10) | ||
|  |     : str.charCodeAt(0); | ||
|  | } | ||
|  | 
 | ||
|  | function escapeBraces(str) { | ||
|  |   return str.split('\\\\').join(escSlash) | ||
|  |             .split('\\{').join(escOpen) | ||
|  |             .split('\\}').join(escClose) | ||
|  |             .split('\\,').join(escComma) | ||
|  |             .split('\\.').join(escPeriod); | ||
|  | } | ||
|  | 
 | ||
|  | function unescapeBraces(str) { | ||
|  |   return str.split(escSlash).join('\\') | ||
|  |             .split(escOpen).join('{') | ||
|  |             .split(escClose).join('}') | ||
|  |             .split(escComma).join(',') | ||
|  |             .split(escPeriod).join('.'); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // Basically just str.split(","), but handling cases
 | ||
|  | // where we have nested braced sections, which should be
 | ||
|  | // treated as individual members, like {a,{b,c},d}
 | ||
|  | function parseCommaParts(str) { | ||
|  |   if (!str) | ||
|  |     return ['']; | ||
|  | 
 | ||
|  |   var parts = []; | ||
|  |   var m = balanced('{', '}', str); | ||
|  | 
 | ||
|  |   if (!m) | ||
|  |     return str.split(','); | ||
|  | 
 | ||
|  |   var pre = m.pre; | ||
|  |   var body = m.body; | ||
|  |   var post = m.post; | ||
|  |   var p = pre.split(','); | ||
|  | 
 | ||
|  |   p[p.length-1] += '{' + body + '}'; | ||
|  |   var postParts = parseCommaParts(post); | ||
|  |   if (post.length) { | ||
|  |     p[p.length-1] += postParts.shift(); | ||
|  |     p.push.apply(p, postParts); | ||
|  |   } | ||
|  | 
 | ||
|  |   parts.push.apply(parts, p); | ||
|  | 
 | ||
|  |   return parts; | ||
|  | } | ||
|  | 
 | ||
|  | function expandTop(str) { | ||
|  |   if (!str) | ||
|  |     return []; | ||
|  | 
 | ||
|  |   // I don't know why Bash 4.3 does this, but it does.
 | ||
|  |   // Anything starting with {} will have the first two bytes preserved
 | ||
|  |   // but *only* at the top level, so {},a}b will not expand to anything,
 | ||
|  |   // but a{},b}c will be expanded to [a}c,abc].
 | ||
|  |   // One could argue that this is a bug in Bash, but since the goal of
 | ||
|  |   // this module is to match Bash's rules, we escape a leading {}
 | ||
|  |   if (str.substr(0, 2) === '{}') { | ||
|  |     str = '\\{\\}' + str.substr(2); | ||
|  |   } | ||
|  | 
 | ||
|  |   return expand(escapeBraces(str), true).map(unescapeBraces); | ||
|  | } | ||
|  | 
 | ||
|  | function identity(e) { | ||
|  |   return e; | ||
|  | } | ||
|  | 
 | ||
|  | function embrace(str) { | ||
|  |   return '{' + str + '}'; | ||
|  | } | ||
|  | function isPadded(el) { | ||
|  |   return /^-?0\d/.test(el); | ||
|  | } | ||
|  | 
 | ||
|  | function lte(i, y) { | ||
|  |   return i <= y; | ||
|  | } | ||
|  | function gte(i, y) { | ||
|  |   return i >= y; | ||
|  | } | ||
|  | 
 | ||
|  | function expand(str, isTop) { | ||
|  |   var expansions = []; | ||
|  | 
 | ||
|  |   var m = balanced('{', '}', str); | ||
|  |   if (!m || /\$$/.test(m.pre)) return [str]; | ||
|  | 
 | ||
|  |   var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); | ||
|  |   var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); | ||
|  |   var isSequence = isNumericSequence || isAlphaSequence; | ||
|  |   var isOptions = m.body.indexOf(',') >= 0; | ||
|  |   if (!isSequence && !isOptions) { | ||
|  |     // {a},b}
 | ||
|  |     if (m.post.match(/,.*\}/)) { | ||
|  |       str = m.pre + '{' + m.body + escClose + m.post; | ||
|  |       return expand(str); | ||
|  |     } | ||
|  |     return [str]; | ||
|  |   } | ||
|  | 
 | ||
|  |   var n; | ||
|  |   if (isSequence) { | ||
|  |     n = m.body.split(/\.\./); | ||
|  |   } else { | ||
|  |     n = parseCommaParts(m.body); | ||
|  |     if (n.length === 1) { | ||
|  |       // x{{a,b}}y ==> x{a}y x{b}y
 | ||
|  |       n = expand(n[0], false).map(embrace); | ||
|  |       if (n.length === 1) { | ||
|  |         var post = m.post.length | ||
|  |           ? expand(m.post, false) | ||
|  |           : ['']; | ||
|  |         return post.map(function(p) { | ||
|  |           return m.pre + n[0] + p; | ||
|  |         }); | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   // at this point, n is the parts, and we know it's not a comma set
 | ||
|  |   // with a single entry.
 | ||
|  | 
 | ||
|  |   // no need to expand pre, since it is guaranteed to be free of brace-sets
 | ||
|  |   var pre = m.pre; | ||
|  |   var post = m.post.length | ||
|  |     ? expand(m.post, false) | ||
|  |     : ['']; | ||
|  | 
 | ||
|  |   var N; | ||
|  | 
 | ||
|  |   if (isSequence) { | ||
|  |     var x = numeric(n[0]); | ||
|  |     var y = numeric(n[1]); | ||
|  |     var width = Math.max(n[0].length, n[1].length) | ||
|  |     var incr = n.length == 3 | ||
|  |       ? Math.abs(numeric(n[2])) | ||
|  |       : 1; | ||
|  |     var test = lte; | ||
|  |     var reverse = y < x; | ||
|  |     if (reverse) { | ||
|  |       incr *= -1; | ||
|  |       test = gte; | ||
|  |     } | ||
|  |     var pad = n.some(isPadded); | ||
|  | 
 | ||
|  |     N = []; | ||
|  | 
 | ||
|  |     for (var i = x; test(i, y); i += incr) { | ||
|  |       var c; | ||
|  |       if (isAlphaSequence) { | ||
|  |         c = String.fromCharCode(i); | ||
|  |         if (c === '\\') | ||
|  |           c = ''; | ||
|  |       } else { | ||
|  |         c = String(i); | ||
|  |         if (pad) { | ||
|  |           var need = width - c.length; | ||
|  |           if (need > 0) { | ||
|  |             var z = new Array(need + 1).join('0'); | ||
|  |             if (i < 0) | ||
|  |               c = '-' + z + c.slice(1); | ||
|  |             else | ||
|  |               c = z + c; | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |       N.push(c); | ||
|  |     } | ||
|  |   } else { | ||
|  |     N = concatMap(n, function(el) { return expand(el, false) }); | ||
|  |   } | ||
|  | 
 | ||
|  |   for (var j = 0; j < N.length; j++) { | ||
|  |     for (var k = 0; k < post.length; k++) { | ||
|  |       var expansion = pre + N[j] + post[k]; | ||
|  |       if (!isTop || isSequence || expansion) | ||
|  |         expansions.push(expansion); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return expansions; | ||
|  | } | ||
|  | 
 |