3 var Node = require('snapdragon-node');
4 var utils = require('./utils');
10 module.exports = function(braces, options) {
12 .set('bos', function() {
14 this.ast = this.nodes[0] = new Node(this.ast);
22 .set('escape', function() {
23 var pos = this.position();
24 var m = this.match(/^(?:\\(.)|\$\{)/);
27 var prev = this.prev();
28 var last = utils.last(prev.nodes);
30 var node = pos(new Node({
36 if (node.val === '\\\\') {
40 if (node.val === '${') {
45 while ((ch = str[++idx])) {
49 node.val += str[++idx];
58 if (this.options.unescape !== false) {
59 node.val = node.val.replace(/\\([{}])/g, '$1');
62 if (last.val === '"' && this.input.charAt(0) === '"') {
68 return concatNodes.call(this, pos, node, prev, options);
72 * Brackets: "[...]" (basic, this is overridden by
73 * other parsers in more advanced implementations)
76 .set('bracket', function() {
77 var isInside = this.isInside('brace');
78 var pos = this.position();
79 var m = this.match(/^(?:\[([!^]?)([^\]]{2,}|\]-)(\]|[^*+?]+)|\[)/);
82 var prev = this.prev();
84 var negated = m[1] ? '^' : '';
85 var inner = m[2] || '';
86 var close = m[3] || '';
88 if (isInside && prev.type === 'brace') {
89 prev.text = prev.text || '';
93 var esc = this.input.slice(0, 2);
94 if (inner === '' && esc === '\\]') {
102 while ((ch = str[++idx])) {
112 return pos(new Node({
115 escaped: close !== ']',
123 * Empty braces (we capture these early to
124 * speed up processing in the compiler)
127 .set('multiplier', function() {
128 var isInside = this.isInside('brace');
129 var pos = this.position();
130 var m = this.match(/^\{((?:,|\{,+\})+)\}/);
133 this.multiplier = true;
134 var prev = this.prev();
137 if (isInside && prev.type === 'brace') {
138 prev.text = prev.text || '';
142 var node = pos(new Node({
149 return concatNodes.call(this, pos, node, prev, options);
156 .set('brace.open', function() {
157 var pos = this.position();
158 var m = this.match(/^\{(?!(?:[^\\}]?|,+)\})/);
161 var prev = this.prev();
162 var last = utils.last(prev.nodes);
164 // if the last parsed character was an extglob character
165 // we need to _not optimize_ the brace pattern because
166 // it might be mistaken for an extglob by a downstream parser
167 if (last && last.val && isExtglobChar(last.val.slice(-1))) {
168 last.optimize = false;
171 var open = pos(new Node({
176 var node = pos(new Node({
183 this.push('brace', node);
190 .set('brace.close', function() {
191 var pos = this.position();
192 var m = this.match(/^\}/);
193 if (!m || !m[0]) return;
195 var brace = this.pop('brace');
196 var node = pos(new Node({
201 if (!this.isType(brace, 'brace')) {
202 if (this.options.strict) {
203 throw new Error('missing opening "{"');
211 var prev = this.prev();
212 var last = utils.last(prev.nodes);
214 var lastNode = utils.last(last.nodes);
215 if (lastNode.val === ')' && /[!@*?+]\(/.test(last.text)) {
216 var open = last.nodes[0];
217 var text = last.nodes[1];
218 if (open.type === 'brace.open' && text && text.type === 'text') {
219 text.optimize = false;
224 if (brace.nodes.length > 2) {
225 var first = brace.nodes[1];
226 if (first.type === 'text' && first.val === ',') {
227 brace.nodes.splice(1, 1);
228 brace.nodes.push(first);
236 * Capture boundary characters
239 .set('boundary', function() {
240 var pos = this.position();
241 var m = this.match(/^[$^](?!\{)/);
243 return pos(new Node({
250 * One or zero, non-comma characters wrapped in braces
253 .set('nobrace', function() {
254 var isInside = this.isInside('brace');
255 var pos = this.position();
256 var m = this.match(/^\{[^,]?\}/);
259 var prev = this.prev();
262 if (isInside && prev.type === 'brace') {
263 prev.text = prev.text || '';
267 return pos(new Node({
278 .set('text', function() {
279 var isInside = this.isInside('brace');
280 var pos = this.position();
281 var m = this.match(/^((?!\\)[^${}[\]])+/);
284 var prev = this.prev();
287 if (isInside && prev.type === 'brace') {
288 prev.text = prev.text || '';
292 var node = pos(new Node({
298 return concatNodes.call(this, pos, node, prev, options);
303 * Returns true if the character is an extglob character.
306 function isExtglobChar(ch) {
307 return ch === '!' || ch === '@' || ch === '*' || ch === '?' || ch === '+';
311 * Combine text nodes, and calculate empty sets (`{,,}`)
312 * @param {Function} `pos` Function to calculate node position
313 * @param {Object} `node` AST node
317 function concatNodes(pos, node, parent, options) {
318 node.orig = node.val;
319 var prev = this.prev();
320 var last = utils.last(prev.nodes);
321 var isEscaped = false;
323 if (node.val.length > 1) {
324 var a = node.val.charAt(0);
325 var b = node.val.slice(-1);
327 isEscaped = (a === '"' && b === '"')
328 || (a === "'" && b === "'")
329 || (a === '`' && b === '`');
332 if (isEscaped && options.unescape !== false) {
333 node.val = node.val.slice(1, node.val.length - 1);
338 var match = node.match[1];
339 if (!match || match.indexOf('}') === -1) {
340 match = node.match[0];
343 // replace each set with a single ","
344 var val = match.replace(/\{/g, ',').replace(/\}/g, '');
345 node.multiplier *= val.length;
349 var simpleText = last.type === 'text'
350 && last.multiplier === 1
351 && node.multiplier === 1
355 last.val += node.val;