1 | 'use strict';
|
2 |
|
3 | var utils = require('./utils');
|
4 | var regexNot = require('regex-not');
|
5 | var toRegex = require('to-regex');
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | var cached;
|
13 | var regex = textRegex();
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | module.exports = function(brace) {
|
20 | brace.parser.sets.brace = brace.parser.sets.brace || [];
|
21 | brace.parser
|
22 |
|
23 | |
24 |
|
25 |
|
26 |
|
27 | .capture('bracket', function() {
|
28 | var pos = this.position();
|
29 | var m = this.match(/^(?:\[([!^]?)([^\]]{2,}|\]\-)(\]|[^*+?]+)|\[)/);
|
30 | if (!m) return;
|
31 |
|
32 | var val = m[0];
|
33 | var negated = m[1] ? '^' : '';
|
34 | var inner = m[2] || '';
|
35 | var close = m[3] || '';
|
36 |
|
37 | var esc = this.input.slice(0, 2);
|
38 | if (inner === '' && esc === '\\]') {
|
39 | inner += esc;
|
40 | this.consume(2);
|
41 |
|
42 | var str = this.input;
|
43 | var idx = -1;
|
44 | var ch;
|
45 |
|
46 | while ((ch = str[++idx])) {
|
47 | this.consume(1);
|
48 | if (ch === ']') {
|
49 | close = ch;
|
50 | break;
|
51 | }
|
52 | inner += ch;
|
53 | }
|
54 | }
|
55 |
|
56 | return pos({
|
57 | type: 'bracket',
|
58 | val: val,
|
59 | escaped: close !== ']',
|
60 | negated: negated,
|
61 | inner: inner,
|
62 | close: close
|
63 | });
|
64 | })
|
65 |
|
66 | |
67 |
|
68 |
|
69 |
|
70 | .capture('escape', function() {
|
71 | var pos = this.position();
|
72 | var m = this.match(/^(?:\\(.)|\$\{)/);
|
73 | if (!m) return;
|
74 |
|
75 | var node = pos({
|
76 | type: 'text',
|
77 | multiplier: 1,
|
78 | val: m[0]
|
79 | });
|
80 |
|
81 | if (node.val === '\\\\') {
|
82 | return node;
|
83 | }
|
84 |
|
85 | var chars = {'${': '}', '`': '`', '"': '"'};
|
86 | var val = node.val;
|
87 | if (chars[val]) {
|
88 | var str = this.input;
|
89 | var idx = -1;
|
90 | var ch;
|
91 |
|
92 | while ((ch = str[++idx])) {
|
93 | this.consume(1);
|
94 | node.val += ch;
|
95 | if (ch === '\\') {
|
96 | ch += str[++idx];
|
97 | node.val += ch;
|
98 | }
|
99 | if (ch === chars[val]) {
|
100 | break;
|
101 | }
|
102 | }
|
103 | }
|
104 |
|
105 | if (this.options.unescape !== false) {
|
106 | node.val = node.val.replace(/\\([{}])/g, '$1');
|
107 | }
|
108 |
|
109 | return concatNodes.call(this, pos, node);
|
110 | })
|
111 |
|
112 | |
113 |
|
114 |
|
115 |
|
116 | .capture('brace.open', function() {
|
117 | var pos = this.position();
|
118 | var m = this.match(/^\{(?!(?:[^\\}]?|,+)\})/);
|
119 | if (!m) return;
|
120 |
|
121 | var prev = this.prev();
|
122 | var val = m[0];
|
123 |
|
124 | var open = pos({
|
125 | type: 'brace.open',
|
126 | val: val
|
127 | });
|
128 |
|
129 | var node = pos({
|
130 | type: 'brace',
|
131 | nodes: [open]
|
132 | });
|
133 |
|
134 | utils.define(node, 'parent', prev);
|
135 | utils.define(open, 'parent', node);
|
136 | this.push('brace', node);
|
137 | prev.nodes.push(node);
|
138 | })
|
139 |
|
140 | |
141 |
|
142 |
|
143 |
|
144 | .capture('brace.close', function() {
|
145 | var pos = this.position();
|
146 | var m = this.match(/^\}/);
|
147 | if (!m || !m[0]) return;
|
148 |
|
149 | var brace = this.pop('brace');
|
150 | var node = pos({
|
151 | type: 'brace.close',
|
152 | val: m[0]
|
153 | });
|
154 |
|
155 | if (!this.isType(brace, 'brace')) {
|
156 | if (this.options.strict) {
|
157 | throw new Error('missing opening "{"');
|
158 | }
|
159 | node.type = 'text';
|
160 | node.multiplier = 1;
|
161 | node.escaped = true;
|
162 | return node;
|
163 | }
|
164 |
|
165 | brace.nodes.push(node);
|
166 | utils.define(node, 'parent', brace);
|
167 | })
|
168 |
|
169 | |
170 |
|
171 |
|
172 |
|
173 | .capture('text', function() {
|
174 | var pos = this.position();
|
175 | var m = this.match(regex);
|
176 | if (!m) return;
|
177 |
|
178 | var node = pos({
|
179 | type: 'text',
|
180 | multiplier: 1,
|
181 | val: m[0]
|
182 | });
|
183 |
|
184 | return concatNodes.call(this, pos, node);
|
185 | });
|
186 |
|
187 | };
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 | function concatNodes(pos, node) {
|
197 | var prev = this.prev();
|
198 | var last = utils.last(prev.nodes);
|
199 | var re = /^\{(,+)\}/;
|
200 | var multi;
|
201 |
|
202 | var a = node.val.charAt(0);
|
203 | var b = node.val.slice(-1);
|
204 |
|
205 | if ((a === '"' && b === '"') || (a === '`' && b === '`') || (a === "'" && b === "'")) {
|
206 | node.val = node.val.slice(1, node.val.length - 1);
|
207 | node.escaped = true;
|
208 | }
|
209 |
|
210 | if (re.test(node.val)) {
|
211 | this.input = node.val + this.input;
|
212 | node.val = '';
|
213 | }
|
214 |
|
215 | while ((multi = re.exec(this.input))) {
|
216 | this.consume(multi[0].length);
|
217 | node.multiplier *= multi[1].length + 1;
|
218 | }
|
219 |
|
220 | if (last.type === 'text' && node.val && node.multiplier === 1 && last.multiplier === 1) {
|
221 | last.val += node.val;
|
222 | return;
|
223 | }
|
224 |
|
225 | return node;
|
226 | }
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | function textRegex(pattern) {
|
233 | if (cached) return cached;
|
234 | var opts = {contains: true, strictClose: false};
|
235 | var not = regexNot.create('(\\$?\\{|\\\\.|\\}|[\\[\\]])', opts);
|
236 | var re = toRegex('^(?:\\{(,+|.?)\\}|' + not + ')', opts);
|
237 | return (cached = re);
|
238 | }
|