You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							360 lines
						
					
					
						
							7.8 KiB
						
					
					
				
			
		
		
	
	
							360 lines
						
					
					
						
							7.8 KiB
						
					
					
				'use strict'; | 
						|
 | 
						|
var Node = require('snapdragon-node'); | 
						|
var utils = require('./utils'); | 
						|
 | 
						|
/** | 
						|
 * Braces parsers | 
						|
 */ | 
						|
 | 
						|
module.exports = function(braces, options) { | 
						|
  braces.parser | 
						|
    .set('bos', function() { | 
						|
      if (!this.parsed) { | 
						|
        this.ast = this.nodes[0] = new Node(this.ast); | 
						|
      } | 
						|
    }) | 
						|
 | 
						|
    /** | 
						|
     * Character parsers | 
						|
     */ | 
						|
 | 
						|
    .set('escape', function() { | 
						|
      var pos = this.position(); | 
						|
      var m = this.match(/^(?:\\(.)|\$\{)/); | 
						|
      if (!m) return; | 
						|
 | 
						|
      var prev = this.prev(); | 
						|
      var last = utils.last(prev.nodes); | 
						|
 | 
						|
      var node = pos(new Node({ | 
						|
        type: 'text', | 
						|
        multiplier: 1, | 
						|
        val: m[0] | 
						|
      })); | 
						|
 | 
						|
      if (node.val === '\\\\') { | 
						|
        return node; | 
						|
      } | 
						|
 | 
						|
      if (node.val === '${') { | 
						|
        var str = this.input; | 
						|
        var idx = -1; | 
						|
        var ch; | 
						|
 | 
						|
        while ((ch = str[++idx])) { | 
						|
          this.consume(1); | 
						|
          node.val += ch; | 
						|
          if (ch === '\\') { | 
						|
            node.val += str[++idx]; | 
						|
            continue; | 
						|
          } | 
						|
          if (ch === '}') { | 
						|
            break; | 
						|
          } | 
						|
        } | 
						|
      } | 
						|
 | 
						|
      if (this.options.unescape !== false) { | 
						|
        node.val = node.val.replace(/\\([{}])/g, '$1'); | 
						|
      } | 
						|
 | 
						|
      if (last.val === '"' && this.input.charAt(0) === '"') { | 
						|
        last.val = node.val; | 
						|
        this.consume(1); | 
						|
        return; | 
						|
      } | 
						|
 | 
						|
      return concatNodes.call(this, pos, node, prev, options); | 
						|
    }) | 
						|
 | 
						|
    /** | 
						|
     * Brackets: "[...]" (basic, this is overridden by | 
						|
     * other parsers in more advanced implementations) | 
						|
     */ | 
						|
 | 
						|
    .set('bracket', function() { | 
						|
      var isInside = this.isInside('brace'); | 
						|
      var pos = this.position(); | 
						|
      var m = this.match(/^(?:\[([!^]?)([^\]]{2,}|\]-)(\]|[^*+?]+)|\[)/); | 
						|
      if (!m) return; | 
						|
 | 
						|
      var prev = this.prev(); | 
						|
      var val = m[0]; | 
						|
      var negated = m[1] ? '^' : ''; | 
						|
      var inner = m[2] || ''; | 
						|
      var close = m[3] || ''; | 
						|
 | 
						|
      if (isInside && prev.type === 'brace') { | 
						|
        prev.text = prev.text || ''; | 
						|
        prev.text += val; | 
						|
      } | 
						|
 | 
						|
      var esc = this.input.slice(0, 2); | 
						|
      if (inner === '' && esc === '\\]') { | 
						|
        inner += esc; | 
						|
        this.consume(2); | 
						|
 | 
						|
        var str = this.input; | 
						|
        var idx = -1; | 
						|
        var ch; | 
						|
 | 
						|
        while ((ch = str[++idx])) { | 
						|
          this.consume(1); | 
						|
          if (ch === ']') { | 
						|
            close = ch; | 
						|
            break; | 
						|
          } | 
						|
          inner += ch; | 
						|
        } | 
						|
      } | 
						|
 | 
						|
      return pos(new Node({ | 
						|
        type: 'bracket', | 
						|
        val: val, | 
						|
        escaped: close !== ']', | 
						|
        negated: negated, | 
						|
        inner: inner, | 
						|
        close: close | 
						|
      })); | 
						|
    }) | 
						|
 | 
						|
    /** | 
						|
     * Empty braces (we capture these early to | 
						|
     * speed up processing in the compiler) | 
						|
     */ | 
						|
 | 
						|
    .set('multiplier', function() { | 
						|
      var isInside = this.isInside('brace'); | 
						|
      var pos = this.position(); | 
						|
      var m = this.match(/^\{((?:,|\{,+\})+)\}/); | 
						|
      if (!m) return; | 
						|
 | 
						|
      this.multiplier = true; | 
						|
      var prev = this.prev(); | 
						|
      var val = m[0]; | 
						|
 | 
						|
      if (isInside && prev.type === 'brace') { | 
						|
        prev.text = prev.text || ''; | 
						|
        prev.text += val; | 
						|
      } | 
						|
 | 
						|
      var node = pos(new Node({ | 
						|
        type: 'text', | 
						|
        multiplier: 1, | 
						|
        match: m, | 
						|
        val: val | 
						|
      })); | 
						|
 | 
						|
      return concatNodes.call(this, pos, node, prev, options); | 
						|
    }) | 
						|
 | 
						|
    /** | 
						|
     * Open | 
						|
     */ | 
						|
 | 
						|
    .set('brace.open', function() { | 
						|
      var pos = this.position(); | 
						|
      var m = this.match(/^\{(?!(?:[^\\}]?|,+)\})/); | 
						|
      if (!m) return; | 
						|
 | 
						|
      var prev = this.prev(); | 
						|
      var last = utils.last(prev.nodes); | 
						|
 | 
						|
      // if the last parsed character was an extglob character | 
						|
      // we need to _not optimize_ the brace pattern because | 
						|
      // it might be mistaken for an extglob by a downstream parser | 
						|
      if (last && last.val && isExtglobChar(last.val.slice(-1))) { | 
						|
        last.optimize = false; | 
						|
      } | 
						|
 | 
						|
      var open = pos(new Node({ | 
						|
        type: 'brace.open', | 
						|
        val: m[0] | 
						|
      })); | 
						|
 | 
						|
      var node = pos(new Node({ | 
						|
        type: 'brace', | 
						|
        nodes: [] | 
						|
      })); | 
						|
 | 
						|
      node.push(open); | 
						|
      prev.push(node); | 
						|
      this.push('brace', node); | 
						|
    }) | 
						|
 | 
						|
    /** | 
						|
     * Close | 
						|
     */ | 
						|
 | 
						|
    .set('brace.close', function() { | 
						|
      var pos = this.position(); | 
						|
      var m = this.match(/^\}/); | 
						|
      if (!m || !m[0]) return; | 
						|
 | 
						|
      var brace = this.pop('brace'); | 
						|
      var node = pos(new Node({ | 
						|
        type: 'brace.close', | 
						|
        val: m[0] | 
						|
      })); | 
						|
 | 
						|
      if (!this.isType(brace, 'brace')) { | 
						|
        if (this.options.strict) { | 
						|
          throw new Error('missing opening "{"'); | 
						|
        } | 
						|
        node.type = 'text'; | 
						|
        node.multiplier = 0; | 
						|
        node.escaped = true; | 
						|
        return node; | 
						|
      } | 
						|
 | 
						|
      var prev = this.prev(); | 
						|
      var last = utils.last(prev.nodes); | 
						|
      if (last.text) { | 
						|
        var lastNode = utils.last(last.nodes); | 
						|
        if (lastNode.val === ')' && /[!@*?+]\(/.test(last.text)) { | 
						|
          var open = last.nodes[0]; | 
						|
          var text = last.nodes[1]; | 
						|
          if (open.type === 'brace.open' && text && text.type === 'text') { | 
						|
            text.optimize = false; | 
						|
          } | 
						|
        } | 
						|
      } | 
						|
 | 
						|
      if (brace.nodes.length > 2) { | 
						|
        var first = brace.nodes[1]; | 
						|
        if (first.type === 'text' && first.val === ',') { | 
						|
          brace.nodes.splice(1, 1); | 
						|
          brace.nodes.push(first); | 
						|
        } | 
						|
      } | 
						|
 | 
						|
      brace.push(node); | 
						|
    }) | 
						|
 | 
						|
    /** | 
						|
     * Capture boundary characters | 
						|
     */ | 
						|
 | 
						|
    .set('boundary', function() { | 
						|
      var pos = this.position(); | 
						|
      var m = this.match(/^[$^](?!\{)/); | 
						|
      if (!m) return; | 
						|
      return pos(new Node({ | 
						|
        type: 'text', | 
						|
        val: m[0] | 
						|
      })); | 
						|
    }) | 
						|
 | 
						|
    /** | 
						|
     * One or zero, non-comma characters wrapped in braces | 
						|
     */ | 
						|
 | 
						|
    .set('nobrace', function() { | 
						|
      var isInside = this.isInside('brace'); | 
						|
      var pos = this.position(); | 
						|
      var m = this.match(/^\{[^,]?\}/); | 
						|
      if (!m) return; | 
						|
 | 
						|
      var prev = this.prev(); | 
						|
      var val = m[0]; | 
						|
 | 
						|
      if (isInside && prev.type === 'brace') { | 
						|
        prev.text = prev.text || ''; | 
						|
        prev.text += val; | 
						|
      } | 
						|
 | 
						|
      return pos(new Node({ | 
						|
        type: 'text', | 
						|
        multiplier: 0, | 
						|
        val: val | 
						|
      })); | 
						|
    }) | 
						|
 | 
						|
    /** | 
						|
     * Text | 
						|
     */ | 
						|
 | 
						|
    .set('text', function() { | 
						|
      var isInside = this.isInside('brace'); | 
						|
      var pos = this.position(); | 
						|
      var m = this.match(/^((?!\\)[^${}[\]])+/); | 
						|
      if (!m) return; | 
						|
 | 
						|
      var prev = this.prev(); | 
						|
      var val = m[0]; | 
						|
 | 
						|
      if (isInside && prev.type === 'brace') { | 
						|
        prev.text = prev.text || ''; | 
						|
        prev.text += val; | 
						|
      } | 
						|
 | 
						|
      var node = pos(new Node({ | 
						|
        type: 'text', | 
						|
        multiplier: 1, | 
						|
        val: val | 
						|
      })); | 
						|
 | 
						|
      return concatNodes.call(this, pos, node, prev, options); | 
						|
    }); | 
						|
}; | 
						|
 | 
						|
/** | 
						|
 * Returns true if the character is an extglob character. | 
						|
 */ | 
						|
 | 
						|
function isExtglobChar(ch) { | 
						|
  return ch === '!' || ch === '@' || ch === '*' || ch === '?' || ch === '+'; | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Combine text nodes, and calculate empty sets (`{,,}`) | 
						|
 * @param {Function} `pos` Function to calculate node position | 
						|
 * @param {Object} `node` AST node | 
						|
 * @return {Object} | 
						|
 */ | 
						|
 | 
						|
function concatNodes(pos, node, parent, options) { | 
						|
  node.orig = node.val; | 
						|
  var prev = this.prev(); | 
						|
  var last = utils.last(prev.nodes); | 
						|
  var isEscaped = false; | 
						|
 | 
						|
  if (node.val.length > 1) { | 
						|
    var a = node.val.charAt(0); | 
						|
    var b = node.val.slice(-1); | 
						|
 | 
						|
    isEscaped = (a === '"' && b === '"') | 
						|
      || (a === "'" && b === "'") | 
						|
      || (a === '`' && b === '`'); | 
						|
  } | 
						|
 | 
						|
  if (isEscaped && options.unescape !== false) { | 
						|
    node.val = node.val.slice(1, node.val.length - 1); | 
						|
    node.escaped = true; | 
						|
  } | 
						|
 | 
						|
  if (node.match) { | 
						|
    var match = node.match[1]; | 
						|
    if (!match || match.indexOf('}') === -1) { | 
						|
      match = node.match[0]; | 
						|
    } | 
						|
 | 
						|
    // replace each set with a single "," | 
						|
    var val = match.replace(/\{/g, ',').replace(/\}/g, ''); | 
						|
    node.multiplier *= val.length; | 
						|
    node.val = ''; | 
						|
  } | 
						|
 | 
						|
  var simpleText = last.type === 'text' | 
						|
    && last.multiplier === 1 | 
						|
    && node.multiplier === 1 | 
						|
    && node.val; | 
						|
 | 
						|
  if (simpleText) { | 
						|
    last.val += node.val; | 
						|
    return; | 
						|
  } | 
						|
 | 
						|
  prev.push(node); | 
						|
}
 | 
						|
 |