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