283 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			283 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								'use strict';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var utils = require('./utils');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = function(braces, options) {
							 | 
						||
| 
								 | 
							
								  braces.compiler
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * bos
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .set('bos', function() {
							 | 
						||
| 
								 | 
							
								      if (this.output) return;
							 | 
						||
| 
								 | 
							
								      this.ast.queue = isEscaped(this.ast) ? [this.ast.val] : [];
							 | 
						||
| 
								 | 
							
								      this.ast.count = 1;
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Square brackets
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .set('bracket', function(node) {
							 | 
						||
| 
								 | 
							
								      var close = node.close;
							 | 
						||
| 
								 | 
							
								      var open = !node.escaped ? '[' : '\\[';
							 | 
						||
| 
								 | 
							
								      var negated = node.negated;
							 | 
						||
| 
								 | 
							
								      var inner = node.inner;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      inner = inner.replace(/\\(?=[\\\w]|$)/g, '\\\\');
							 | 
						||
| 
								 | 
							
								      if (inner === ']-') {
							 | 
						||
| 
								 | 
							
								        inner = '\\]\\-';
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (negated && inner.indexOf('.') === -1) {
							 | 
						||
| 
								 | 
							
								        inner += '.';
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (negated && inner.indexOf('/') === -1) {
							 | 
						||
| 
								 | 
							
								        inner += '/';
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      var val = open + negated + inner + close;
							 | 
						||
| 
								 | 
							
								      var queue = node.parent.queue;
							 | 
						||
| 
								 | 
							
								      var last = utils.arrayify(queue.pop());
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      queue.push(utils.join(last, val));
							 | 
						||
| 
								 | 
							
								      queue.push.apply(queue, []);
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Brace
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .set('brace', function(node) {
							 | 
						||
| 
								 | 
							
								      node.queue = isEscaped(node) ? [node.val] : [];
							 | 
						||
| 
								 | 
							
								      node.count = 1;
							 | 
						||
| 
								 | 
							
								      return this.mapVisit(node.nodes);
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Open
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .set('brace.open', function(node) {
							 | 
						||
| 
								 | 
							
								      node.parent.open = node.val;
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Inner
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .set('text', function(node) {
							 | 
						||
| 
								 | 
							
								      var queue = node.parent.queue;
							 | 
						||
| 
								 | 
							
								      var escaped = node.escaped;
							 | 
						||
| 
								 | 
							
								      var segs = [node.val];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (node.optimize === false) {
							 | 
						||
| 
								 | 
							
								        options = utils.extend({}, options, {optimize: false});
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (node.multiplier > 1) {
							 | 
						||
| 
								 | 
							
								        node.parent.count *= node.multiplier;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (options.quantifiers === true && utils.isQuantifier(node.val)) {
							 | 
						||
| 
								 | 
							
								        escaped = true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      } else if (node.val.length > 1) {
							 | 
						||
| 
								 | 
							
								        if (isType(node.parent, 'brace') && !isEscaped(node)) {
							 | 
						||
| 
								 | 
							
								          var expanded = utils.expand(node.val, options);
							 | 
						||
| 
								 | 
							
								          segs = expanded.segs;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          if (expanded.isOptimized) {
							 | 
						||
| 
								 | 
							
								            node.parent.isOptimized = true;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          // if nothing was expanded, we probably have a literal brace
							 | 
						||
| 
								 | 
							
								          if (!segs.length) {
							 | 
						||
| 
								 | 
							
								            var val = (expanded.val || node.val);
							 | 
						||
| 
								 | 
							
								            if (options.unescape !== false) {
							 | 
						||
| 
								 | 
							
								              // unescape unexpanded brace sequence/set separators
							 | 
						||
| 
								 | 
							
								              val = val.replace(/\\([,.])/g, '$1');
							 | 
						||
| 
								 | 
							
								              // strip quotes
							 | 
						||
| 
								 | 
							
								              val = val.replace(/["'`]/g, '');
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            segs = [val];
							 | 
						||
| 
								 | 
							
								            escaped = true;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      } else if (node.val === ',') {
							 | 
						||
| 
								 | 
							
								        if (options.expand) {
							 | 
						||
| 
								 | 
							
								          node.parent.queue.push(['']);
							 | 
						||
| 
								 | 
							
								          segs = [''];
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          segs = ['|'];
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        escaped = true;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (escaped && isType(node.parent, 'brace')) {
							 | 
						||
| 
								 | 
							
								        if (node.parent.nodes.length <= 4 && node.parent.count === 1) {
							 | 
						||
| 
								 | 
							
								          node.parent.escaped = true;
							 | 
						||
| 
								 | 
							
								        } else if (node.parent.length <= 3) {
							 | 
						||
| 
								 | 
							
								          node.parent.escaped = true;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!hasQueue(node.parent)) {
							 | 
						||
| 
								 | 
							
								        node.parent.queue = segs;
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      var last = utils.arrayify(queue.pop());
							 | 
						||
| 
								 | 
							
								      if (node.parent.count > 1 && options.expand) {
							 | 
						||
| 
								 | 
							
								        last = multiply(last, node.parent.count);
							 | 
						||
| 
								 | 
							
								        node.parent.count = 1;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      queue.push(utils.join(utils.flatten(last), segs.shift()));
							 | 
						||
| 
								 | 
							
								      queue.push.apply(queue, segs);
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * Close
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .set('brace.close', function(node) {
							 | 
						||
| 
								 | 
							
								      var queue = node.parent.queue;
							 | 
						||
| 
								 | 
							
								      var prev = node.parent.parent;
							 | 
						||
| 
								 | 
							
								      var last = prev.queue.pop();
							 | 
						||
| 
								 | 
							
								      var open = node.parent.open;
							 | 
						||
| 
								 | 
							
								      var close = node.val;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (open && close && isOptimized(node, options)) {
							 | 
						||
| 
								 | 
							
								        open = '(';
							 | 
						||
| 
								 | 
							
								        close = ')';
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // if a close brace exists, and the previous segment is one character
							 | 
						||
| 
								 | 
							
								      // don't wrap the result in braces or parens
							 | 
						||
| 
								 | 
							
								      var ele = utils.last(queue);
							 | 
						||
| 
								 | 
							
								      if (node.parent.count > 1 && options.expand) {
							 | 
						||
| 
								 | 
							
								        ele = multiply(queue.pop(), node.parent.count);
							 | 
						||
| 
								 | 
							
								        node.parent.count = 1;
							 | 
						||
| 
								 | 
							
								        queue.push(ele);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (close && typeof ele === 'string' && ele.length === 1) {
							 | 
						||
| 
								 | 
							
								        open = '';
							 | 
						||
| 
								 | 
							
								        close = '';
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if ((isLiteralBrace(node, options) || noInner(node)) && !node.parent.hasEmpty) {
							 | 
						||
| 
								 | 
							
								        queue.push(utils.join(open, queue.pop() || ''));
							 | 
						||
| 
								 | 
							
								        queue = utils.flatten(utils.join(queue, close));
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (typeof last === 'undefined') {
							 | 
						||
| 
								 | 
							
								        prev.queue = [queue];
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        prev.queue.push(utils.flatten(utils.join(last, queue)));
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * eos
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .set('eos', function(node) {
							 | 
						||
| 
								 | 
							
								      if (this.input) return;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (options.optimize !== false) {
							 | 
						||
| 
								 | 
							
								        this.output = utils.last(utils.flatten(this.ast.queue));
							 | 
						||
| 
								 | 
							
								      } else if (Array.isArray(utils.last(this.ast.queue))) {
							 | 
						||
| 
								 | 
							
								        this.output = utils.flatten(this.ast.queue.pop());
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        this.output = utils.flatten(this.ast.queue);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (node.parent.count > 1 && options.expand) {
							 | 
						||
| 
								 | 
							
								        this.output = multiply(this.output, node.parent.count);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      this.output = utils.arrayify(this.output);
							 | 
						||
| 
								 | 
							
								      this.ast.queue = [];
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Multiply the segments in the current brace level
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function multiply(queue, n, options) {
							 | 
						||
| 
								 | 
							
								  return utils.flatten(utils.repeat(utils.arrayify(queue), n));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Return true if `node` is escaped
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isEscaped(node) {
							 | 
						||
| 
								 | 
							
								  return node.escaped === true;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Returns true if regex parens should be used for sets. If the parent `type`
							 | 
						||
| 
								 | 
							
								 * is not `brace`, then we're on a root node, which means we should never
							 | 
						||
| 
								 | 
							
								 * expand segments and open/close braces should be `{}` (since this indicates
							 | 
						||
| 
								 | 
							
								 * a brace is missing from the set)
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isOptimized(node, options) {
							 | 
						||
| 
								 | 
							
								  if (node.parent.isOptimized) return true;
							 | 
						||
| 
								 | 
							
								  return isType(node.parent, 'brace')
							 | 
						||
| 
								 | 
							
								    && !isEscaped(node.parent)
							 | 
						||
| 
								 | 
							
								    && options.expand !== true;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Returns true if the value in `node` should be wrapped in a literal brace.
							 | 
						||
| 
								 | 
							
								 * @return {Boolean}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isLiteralBrace(node, options) {
							 | 
						||
| 
								 | 
							
								  return isEscaped(node.parent) || options.optimize !== false;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Returns true if the given `node` does not have an inner value.
							 | 
						||
| 
								 | 
							
								 * @return {Boolean}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function noInner(node, type) {
							 | 
						||
| 
								 | 
							
								  if (node.parent.queue.length === 1) {
							 | 
						||
| 
								 | 
							
								    return true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  var nodes = node.parent.nodes;
							 | 
						||
| 
								 | 
							
								  return nodes.length === 3
							 | 
						||
| 
								 | 
							
								    && isType(nodes[0], 'brace.open')
							 | 
						||
| 
								 | 
							
								    && !isType(nodes[1], 'text')
							 | 
						||
| 
								 | 
							
								    && isType(nodes[2], 'brace.close');
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Returns true if the given `node` is the given `type`
							 | 
						||
| 
								 | 
							
								 * @return {Boolean}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isType(node, type) {
							 | 
						||
| 
								 | 
							
								  return typeof node !== 'undefined' && node.type === type;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Returns true if the given `node` has a non-empty queue.
							 | 
						||
| 
								 | 
							
								 * @return {Boolean}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function hasQueue(node) {
							 | 
						||
| 
								 | 
							
								  return Array.isArray(node.queue) && node.queue.length;
							 | 
						||
| 
								 | 
							
								}
							 |