1
|
'use strict';
|
2
|
|
3
|
var regexNot = require('regex-not');
|
4
|
var toRegex = require('to-regex');
|
5
|
|
6
|
/**
|
7
|
* Characters to use in negation regex (we want to "not" match
|
8
|
* characters that are matched by other parsers)
|
9
|
*/
|
10
|
|
11
|
var cached;
|
12
|
var NOT_REGEX = '[\\[!*+?$^"\'.\\\\/]+';
|
13
|
var not = createTextRegex(NOT_REGEX);
|
14
|
|
15
|
/**
|
16
|
* Nanomatch parsers
|
17
|
*/
|
18
|
|
19
|
module.exports = function(nanomatch, options) {
|
20
|
var parser = nanomatch.parser;
|
21
|
var opts = parser.options;
|
22
|
|
23
|
parser.state = {
|
24
|
slashes: 0,
|
25
|
paths: []
|
26
|
};
|
27
|
|
28
|
parser.ast.state = parser.state;
|
29
|
parser
|
30
|
|
31
|
/**
|
32
|
* Beginning-of-string
|
33
|
*/
|
34
|
|
35
|
.capture('prefix', function() {
|
36
|
if (this.parsed) return;
|
37
|
var m = this.match(/^\.[\\/]/);
|
38
|
if (!m) return;
|
39
|
this.state.strictOpen = !!this.options.strictOpen;
|
40
|
this.state.addPrefix = true;
|
41
|
})
|
42
|
|
43
|
/**
|
44
|
* Escape: "\\."
|
45
|
*/
|
46
|
|
47
|
.capture('escape', function() {
|
48
|
if (this.isInside('bracket')) return;
|
49
|
var pos = this.position();
|
50
|
var m = this.match(/^(?:\\(.)|([$^]))/);
|
51
|
if (!m) return;
|
52
|
|
53
|
return pos({
|
54
|
type: 'escape',
|
55
|
val: m[2] || m[1]
|
56
|
});
|
57
|
})
|
58
|
|
59
|
/**
|
60
|
* Quoted strings
|
61
|
*/
|
62
|
|
63
|
.capture('quoted', function() {
|
64
|
var pos = this.position();
|
65
|
var m = this.match(/^["']/);
|
66
|
if (!m) return;
|
67
|
|
68
|
var quote = m[0];
|
69
|
if (this.input.indexOf(quote) === -1) {
|
70
|
return pos({
|
71
|
type: 'escape',
|
72
|
val: quote
|
73
|
});
|
74
|
}
|
75
|
|
76
|
var tok = advanceTo(this.input, quote);
|
77
|
this.consume(tok.len);
|
78
|
|
79
|
return pos({
|
80
|
type: 'quoted',
|
81
|
val: tok.esc
|
82
|
});
|
83
|
})
|
84
|
|
85
|
/**
|
86
|
* Negations: "!"
|
87
|
*/
|
88
|
|
89
|
.capture('not', function() {
|
90
|
var parsed = this.parsed;
|
91
|
var pos = this.position();
|
92
|
var m = this.match(this.notRegex || /^!+/);
|
93
|
if (!m) return;
|
94
|
var val = m[0];
|
95
|
|
96
|
var isNegated = (val.length % 2) === 1;
|
97
|
if (parsed === '' && !isNegated) {
|
98
|
val = '';
|
99
|
}
|
100
|
|
101
|
// if nothing has been parsed, we know `!` is at the start,
|
102
|
// so we need to wrap the result in a negation regex
|
103
|
if (parsed === '' && isNegated && this.options.nonegate !== true) {
|
104
|
this.bos.val = '(?!^(?:';
|
105
|
this.append = ')$).*';
|
106
|
val = '';
|
107
|
}
|
108
|
return pos({
|
109
|
type: 'not',
|
110
|
val: val
|
111
|
});
|
112
|
})
|
113
|
|
114
|
/**
|
115
|
* Dot: "."
|
116
|
*/
|
117
|
|
118
|
.capture('dot', function() {
|
119
|
var parsed = this.parsed;
|
120
|
var pos = this.position();
|
121
|
var m = this.match(/^\.+/);
|
122
|
if (!m) return;
|
123
|
|
124
|
var val = m[0];
|
125
|
this.state.dot = val === '.' && (parsed === '' || parsed.slice(-1) === '/');
|
126
|
|
127
|
return pos({
|
128
|
type: 'dot',
|
129
|
dotfiles: this.state.dot,
|
130
|
val: val
|
131
|
});
|
132
|
})
|
133
|
|
134
|
/**
|
135
|
* Plus: "+"
|
136
|
*/
|
137
|
|
138
|
.capture('plus', /^\+(?!\()/)
|
139
|
|
140
|
/**
|
141
|
* Question mark: "?"
|
142
|
*/
|
143
|
|
144
|
.capture('qmark', function() {
|
145
|
var parsed = this.parsed;
|
146
|
var pos = this.position();
|
147
|
var m = this.match(/^\?+(?!\()/);
|
148
|
if (!m) return;
|
149
|
|
150
|
this.state.metachar = true;
|
151
|
this.state.qmark = true;
|
152
|
|
153
|
return pos({
|
154
|
type: 'qmark',
|
155
|
parsed: parsed,
|
156
|
val: m[0]
|
157
|
});
|
158
|
})
|
159
|
|
160
|
/**
|
161
|
* Globstar: "**"
|
162
|
*/
|
163
|
|
164
|
.capture('globstar', function() {
|
165
|
var parsed = this.parsed;
|
166
|
var pos = this.position();
|
167
|
var m = this.match(/^\*{2}(?![*(])(?=[,)/]|$)/);
|
168
|
if (!m) return;
|
169
|
|
170
|
var type = opts.noglobstar !== true ? 'globstar' : 'star';
|
171
|
var node = pos({type: type, parsed: parsed});
|
172
|
this.state.metachar = true;
|
173
|
|
174
|
while (this.input.slice(0, 4) === '/**/') {
|
175
|
this.input = this.input.slice(3);
|
176
|
}
|
177
|
|
178
|
node.isInside = {
|
179
|
brace: this.isInside('brace'),
|
180
|
paren: this.isInside('paren')
|
181
|
};
|
182
|
|
183
|
if (type === 'globstar') {
|
184
|
this.state.globstar = true;
|
185
|
node.val = '**';
|
186
|
|
187
|
} else {
|
188
|
this.state.star = true;
|
189
|
node.val = '*';
|
190
|
}
|
191
|
|
192
|
return node;
|
193
|
})
|
194
|
|
195
|
/**
|
196
|
* Star: "*"
|
197
|
*/
|
198
|
|
199
|
.capture('star', function() {
|
200
|
var pos = this.position();
|
201
|
var starRe = /^(?:\*(?![*(])|[*]{3,}(?!\()|[*]{2}(?![(/]|$)|\*(?=\*\())/;
|
202
|
var m = this.match(starRe);
|
203
|
if (!m) return;
|
204
|
|
205
|
this.state.metachar = true;
|
206
|
this.state.star = true;
|
207
|
return pos({
|
208
|
type: 'star',
|
209
|
val: m[0]
|
210
|
});
|
211
|
})
|
212
|
|
213
|
/**
|
214
|
* Slash: "/"
|
215
|
*/
|
216
|
|
217
|
.capture('slash', function() {
|
218
|
var pos = this.position();
|
219
|
var m = this.match(/^\//);
|
220
|
if (!m) return;
|
221
|
|
222
|
this.state.slashes++;
|
223
|
return pos({
|
224
|
type: 'slash',
|
225
|
val: m[0]
|
226
|
});
|
227
|
})
|
228
|
|
229
|
/**
|
230
|
* Backslash: "\\"
|
231
|
*/
|
232
|
|
233
|
.capture('backslash', function() {
|
234
|
var pos = this.position();
|
235
|
var m = this.match(/^\\(?![*+?(){}[\]'"])/);
|
236
|
if (!m) return;
|
237
|
|
238
|
var val = m[0];
|
239
|
|
240
|
if (this.isInside('bracket')) {
|
241
|
val = '\\';
|
242
|
} else if (val.length > 1) {
|
243
|
val = '\\\\';
|
244
|
}
|
245
|
|
246
|
return pos({
|
247
|
type: 'backslash',
|
248
|
val: val
|
249
|
});
|
250
|
})
|
251
|
|
252
|
/**
|
253
|
* Square: "[.]"
|
254
|
*/
|
255
|
|
256
|
.capture('square', function() {
|
257
|
if (this.isInside('bracket')) return;
|
258
|
var pos = this.position();
|
259
|
var m = this.match(/^\[([^!^\\])\]/);
|
260
|
if (!m) return;
|
261
|
|
262
|
return pos({
|
263
|
type: 'square',
|
264
|
val: m[1]
|
265
|
});
|
266
|
})
|
267
|
|
268
|
/**
|
269
|
* Brackets: "[...]" (basic, this can be overridden by other parsers)
|
270
|
*/
|
271
|
|
272
|
.capture('bracket', function() {
|
273
|
var pos = this.position();
|
274
|
var m = this.match(/^(?:\[([!^]?)([^\]]+|\]-)(\]|[^*+?]+)|\[)/);
|
275
|
if (!m) return;
|
276
|
|
277
|
var val = m[0];
|
278
|
var negated = m[1] ? '^' : '';
|
279
|
var inner = (m[2] || '').replace(/\\\\+/, '\\\\');
|
280
|
var close = m[3] || '';
|
281
|
|
282
|
if (m[2] && inner.length < m[2].length) {
|
283
|
val = val.replace(/\\\\+/, '\\\\');
|
284
|
}
|
285
|
|
286
|
var esc = this.input.slice(0, 2);
|
287
|
if (inner === '' && esc === '\\]') {
|
288
|
inner += esc;
|
289
|
this.consume(2);
|
290
|
|
291
|
var str = this.input;
|
292
|
var idx = -1;
|
293
|
var ch;
|
294
|
|
295
|
while ((ch = str[++idx])) {
|
296
|
this.consume(1);
|
297
|
if (ch === ']') {
|
298
|
close = ch;
|
299
|
break;
|
300
|
}
|
301
|
inner += ch;
|
302
|
}
|
303
|
}
|
304
|
|
305
|
return pos({
|
306
|
type: 'bracket',
|
307
|
val: val,
|
308
|
escaped: close !== ']',
|
309
|
negated: negated,
|
310
|
inner: inner,
|
311
|
close: close
|
312
|
});
|
313
|
})
|
314
|
|
315
|
/**
|
316
|
* Text
|
317
|
*/
|
318
|
|
319
|
.capture('text', function() {
|
320
|
if (this.isInside('bracket')) return;
|
321
|
var pos = this.position();
|
322
|
var m = this.match(not);
|
323
|
if (!m || !m[0]) return;
|
324
|
|
325
|
return pos({
|
326
|
type: 'text',
|
327
|
val: m[0]
|
328
|
});
|
329
|
});
|
330
|
|
331
|
/**
|
332
|
* Allow custom parsers to be passed on options
|
333
|
*/
|
334
|
|
335
|
if (options && typeof options.parsers === 'function') {
|
336
|
options.parsers(nanomatch.parser);
|
337
|
}
|
338
|
};
|
339
|
|
340
|
/**
|
341
|
* Advance to the next non-escaped character
|
342
|
*/
|
343
|
|
344
|
function advanceTo(input, endChar) {
|
345
|
var ch = input.charAt(0);
|
346
|
var tok = { len: 1, val: '', esc: '' };
|
347
|
var idx = 0;
|
348
|
|
349
|
function advance() {
|
350
|
if (ch !== '\\') {
|
351
|
tok.esc += '\\' + ch;
|
352
|
tok.val += ch;
|
353
|
}
|
354
|
|
355
|
ch = input.charAt(++idx);
|
356
|
tok.len++;
|
357
|
|
358
|
if (ch === '\\') {
|
359
|
advance();
|
360
|
advance();
|
361
|
}
|
362
|
}
|
363
|
|
364
|
while (ch && ch !== endChar) {
|
365
|
advance();
|
366
|
}
|
367
|
return tok;
|
368
|
}
|
369
|
|
370
|
/**
|
371
|
* Create text regex
|
372
|
*/
|
373
|
|
374
|
function createTextRegex(pattern) {
|
375
|
if (cached) return cached;
|
376
|
var opts = {contains: true, strictClose: false};
|
377
|
var not = regexNot.create(pattern, opts);
|
378
|
var re = toRegex('^(?:[*]\\((?=.)|' + not + ')', opts);
|
379
|
return (cached = re);
|
380
|
}
|
381
|
|
382
|
/**
|
383
|
* Expose negation string
|
384
|
*/
|
385
|
|
386
|
module.exports.not = NOT_REGEX;
|