123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- 'use strict';
- var Node = require('snapdragon-node');
- var utils = require('./utils');
- /**
- * Braces parsers
- */
- module.exports = function(braces, options) {
- function update(parent, val) {
- if (parent && parent.type === 'brace') {
- parent.text = parent.text || '';
- parent.text += val;
- }
- }
- 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 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] || '';
- 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;
- }
- }
- var node = pos(new Node({
- type: 'bracket',
- val: val,
- escaped: close !== ']',
- negated: negated,
- inner: inner,
- close: close
- }));
- update(prev, m[0]);
- return node;
- })
- /**
- * Empty braces (we capture these early to
- * speed up processing in the compiler)
- */
- .set('multiplier', function() {
- var pos = this.position();
- var m = this.match(/^\{(,+(?:(\{,+\})*),*|,*(?:(\{,+\})*),+)\}/);
- if (!m) return;
- this.multiplier = true;
- var prev = this.prev();
- var val = m[0];
- update(prev, val);
- var node = pos(new Node({
- type: 'text',
- multiplier: 1,
- match: m,
- val: val
- }));
- return concatNodes.call(this, pos, node, prev, options);
- })
- /**
- * Paren open: "("
- */
- .set('paren.open', function() {
- var pos = this.position();
- var m = this.match(/^\(/);
- if (!m) return;
- var prev = this.prev();
- var node = pos(new Node({
- type: 'paren',
- nodes: []
- }));
- var open = pos(new Node({
- type: 'paren.open',
- val: m[0]
- }));
- node.push(open);
- prev.push(node);
- this.push('paren', node);
- })
- /**
- * Paren close: ")"
- */
- .set('paren.close', function() {
- var pos = this.position();
- var m = this.match(/^\)/);
- if (!m) return;
- var paren = this.pop('paren');
- var node = pos(new Node({
- type: 'paren.close',
- val: m[0]
- }));
- if (!this.isType(paren, 'paren')) {
- if (this.options.strict) {
- throw new Error('missing opening "("');
- }
- node.type = 'text';
- node.escaped = true;
- return node;
- }
- var prev = this.prev();
- paren.push(node);
- // paren.type = 'text';
- // paren.val = '';
- function addNode(node) {
- // node.type = 'text';
- // var last = prev.nodes[prev.nodes.length - 1];
- // if (last && last.type === 'text') {
- // last.val += node.val;
- // } else {
- // prev.nodes.push(node);
- // }
- prev.nodes.push(node);
- }
- visit(paren, function(n) {
- // if (n.type === 'paren.open' || n.type === 'paren.close') {
- // n.type = 'text';
- // }
- if (n.val && prev.type === 'brace') {
- prev.text += n.val;
- }
- // if (n.val) {
- // addNode(n);
- // }
- // if (n.type === 'bracket') {
- // paren.val += n.val;
- // if (n.inner === '\\]') {
- // paren.val += n.inner;
- // }
- // paren.val += n.close;
- // } else if (n.type === 'paren.open' || n.type === 'paren.close') {
- // paren.val += n.val;
- // paren.unescape = false;
- // } else if (n.val) {
- // paren.val += n.val;
- // }
- });
- var arr = prev.nodes.slice();
- var close = arr[arr.length - 1];
- var nodes = [arr[0]];
- delete paren.nodes;
- var split = require('split-string');
- var segs = split(prev.text, {separator: ','});
- for (var i = 0; i < segs.length; i++) {
- var seg = segs[i];
- if (i !== segs.length && i !== segs.length - 1) seg += ',';
- var n = new Node({type: 'text', val: seg});
- n.optimize = false;
- n.parent = prev;
- nodes.push(n);
- }
- prev.nodes = nodes;
- })
- /**
- * Brace 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);
- var open = pos(new Node({
- type: 'brace.open',
- val: m[0]
- }));
- var brace = pos(new Node({
- type: 'brace',
- nodes: []
- }));
- // if the last parsed character was an extglob character
- // we need to _not optimize or escape_ 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.val = last.val.replace(/([@!*+?])/, '\\$1');
- brace.hasExtglob = true;
- // last.optimize = false;
- }
- brace.push(open);
- prev.push(brace);
- this.push('brace', brace);
- })
- /**
- * Brace close: "}"
- */
- .set('brace.close', function() {
- var pos = this.position();
- var m = this.match(/^\}/);
- if (!m || !m[0]) return;
- var brace = this.pop('brace');
- update(his.prev(), m[0]);
- 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 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);
- if (utils.hasExtglob(brace.text)) {
- brace.optimize = false;
- prev.optimize = false;
- // brace.nodes.forEach(function(node) {
- // node.optimize = false;
- // });
- visit(prev, function(node) {
- node.optimize = false;
- return node;
- });
- }
- delete brace.text;
- console.log(brace)
- })
- /**
- * Capture boundary characters
- */
- .set('boundary', function() {
- var pos = this.position();
- var m = this.match(/^[$^](?!\{)/);
- if (!m) return;
- update(this.prev(), m[0]);
- return pos(new Node({
- type: 'text',
- val: m[0]
- }));
- })
- /**
- * One or zero, non-comma characters wrapped in braces
- */
- .set('nobrace', function() {
- var pos = this.position();
- var m = this.match(/^\{[^,]?\}/);
- if (!m) return;
- var prev = this.prev();
- var val = m[0];
- update(prev, val);
- return pos(new Node({
- type: 'text',
- multiplier: 0,
- val: val
- }));
- })
- /**
- * Text
- */
- .set('text', function() {
- var pos = this.position();
- var m = this.match(/^((?!\\)[^${}()\[\]])+/);
- if (!m) return;
- var prev = this.prev();
- var val = m[0];
- update(prev, val);
- var node = pos(new Node({
- type: 'text',
- multiplier: 1,
- val: val
- }));
- 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);
- }
- function visit(node, fn) {
- fn(node);
- return node.nodes ? mapVisit(node, fn) : node;
- }
- function mapVisit(node, fn) {
- for (let i = 0; i < node.nodes.length; i++) {
- visit(node.nodes[i], fn);
- }
- return node;
- }
|