| '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); | 
| } |