You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
386 lines
7.4 KiB
386 lines
7.4 KiB
'use strict';
|
|
|
|
var regexNot = require('regex-not');
|
|
var toRegex = require('to-regex');
|
|
|
|
/**
|
|
* Characters to use in negation regex (we want to "not" match
|
|
* characters that are matched by other parsers)
|
|
*/
|
|
|
|
var cached;
|
|
var NOT_REGEX = '[\\[!*+?$^"\'.\\\\/]+';
|
|
var not = createTextRegex(NOT_REGEX);
|
|
|
|
/**
|
|
* Nanomatch parsers
|
|
*/
|
|
|
|
module.exports = function(nanomatch, options) {
|
|
var parser = nanomatch.parser;
|
|
var opts = parser.options;
|
|
|
|
parser.state = {
|
|
slashes: 0,
|
|
paths: []
|
|
};
|
|
|
|
parser.ast.state = parser.state;
|
|
parser
|
|
|
|
/**
|
|
* Beginning-of-string
|
|
*/
|
|
|
|
.capture('prefix', function() {
|
|
if (this.parsed) return;
|
|
var m = this.match(/^\.[\\/]/);
|
|
if (!m) return;
|
|
this.state.strictOpen = !!this.options.strictOpen;
|
|
this.state.addPrefix = true;
|
|
})
|
|
|
|
/**
|
|
* Escape: "\\."
|
|
*/
|
|
|
|
.capture('escape', function() {
|
|
if (this.isInside('bracket')) return;
|
|
var pos = this.position();
|
|
var m = this.match(/^(?:\\(.)|([$^]))/);
|
|
if (!m) return;
|
|
|
|
return pos({
|
|
type: 'escape',
|
|
val: m[2] || m[1]
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Quoted strings
|
|
*/
|
|
|
|
.capture('quoted', function() {
|
|
var pos = this.position();
|
|
var m = this.match(/^["']/);
|
|
if (!m) return;
|
|
|
|
var quote = m[0];
|
|
if (this.input.indexOf(quote) === -1) {
|
|
return pos({
|
|
type: 'escape',
|
|
val: quote
|
|
});
|
|
}
|
|
|
|
var tok = advanceTo(this.input, quote);
|
|
this.consume(tok.len);
|
|
|
|
return pos({
|
|
type: 'quoted',
|
|
val: tok.esc
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Negations: "!"
|
|
*/
|
|
|
|
.capture('not', function() {
|
|
var parsed = this.parsed;
|
|
var pos = this.position();
|
|
var m = this.match(this.notRegex || /^!+/);
|
|
if (!m) return;
|
|
var val = m[0];
|
|
|
|
var isNegated = (val.length % 2) === 1;
|
|
if (parsed === '' && !isNegated) {
|
|
val = '';
|
|
}
|
|
|
|
// if nothing has been parsed, we know `!` is at the start,
|
|
// so we need to wrap the result in a negation regex
|
|
if (parsed === '' && isNegated && this.options.nonegate !== true) {
|
|
this.bos.val = '(?!^(?:';
|
|
this.append = ')$).*';
|
|
val = '';
|
|
}
|
|
return pos({
|
|
type: 'not',
|
|
val: val
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Dot: "."
|
|
*/
|
|
|
|
.capture('dot', function() {
|
|
var parsed = this.parsed;
|
|
var pos = this.position();
|
|
var m = this.match(/^\.+/);
|
|
if (!m) return;
|
|
|
|
var val = m[0];
|
|
this.state.dot = val === '.' && (parsed === '' || parsed.slice(-1) === '/');
|
|
|
|
return pos({
|
|
type: 'dot',
|
|
dotfiles: this.state.dot,
|
|
val: val
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Plus: "+"
|
|
*/
|
|
|
|
.capture('plus', /^\+(?!\()/)
|
|
|
|
/**
|
|
* Question mark: "?"
|
|
*/
|
|
|
|
.capture('qmark', function() {
|
|
var parsed = this.parsed;
|
|
var pos = this.position();
|
|
var m = this.match(/^\?+(?!\()/);
|
|
if (!m) return;
|
|
|
|
this.state.metachar = true;
|
|
this.state.qmark = true;
|
|
|
|
return pos({
|
|
type: 'qmark',
|
|
parsed: parsed,
|
|
val: m[0]
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Globstar: "**"
|
|
*/
|
|
|
|
.capture('globstar', function() {
|
|
var parsed = this.parsed;
|
|
var pos = this.position();
|
|
var m = this.match(/^\*{2}(?![*(])(?=[,)/]|$)/);
|
|
if (!m) return;
|
|
|
|
var type = opts.noglobstar !== true ? 'globstar' : 'star';
|
|
var node = pos({type: type, parsed: parsed});
|
|
this.state.metachar = true;
|
|
|
|
while (this.input.slice(0, 4) === '/**/') {
|
|
this.input = this.input.slice(3);
|
|
}
|
|
|
|
node.isInside = {
|
|
brace: this.isInside('brace'),
|
|
paren: this.isInside('paren')
|
|
};
|
|
|
|
if (type === 'globstar') {
|
|
this.state.globstar = true;
|
|
node.val = '**';
|
|
|
|
} else {
|
|
this.state.star = true;
|
|
node.val = '*';
|
|
}
|
|
|
|
return node;
|
|
})
|
|
|
|
/**
|
|
* Star: "*"
|
|
*/
|
|
|
|
.capture('star', function() {
|
|
var pos = this.position();
|
|
var starRe = /^(?:\*(?![*(])|[*]{3,}(?!\()|[*]{2}(?![(/]|$)|\*(?=\*\())/;
|
|
var m = this.match(starRe);
|
|
if (!m) return;
|
|
|
|
this.state.metachar = true;
|
|
this.state.star = true;
|
|
return pos({
|
|
type: 'star',
|
|
val: m[0]
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Slash: "/"
|
|
*/
|
|
|
|
.capture('slash', function() {
|
|
var pos = this.position();
|
|
var m = this.match(/^\//);
|
|
if (!m) return;
|
|
|
|
this.state.slashes++;
|
|
return pos({
|
|
type: 'slash',
|
|
val: m[0]
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Backslash: "\\"
|
|
*/
|
|
|
|
.capture('backslash', function() {
|
|
var pos = this.position();
|
|
var m = this.match(/^\\(?![*+?(){}[\]'"])/);
|
|
if (!m) return;
|
|
|
|
var val = m[0];
|
|
|
|
if (this.isInside('bracket')) {
|
|
val = '\\';
|
|
} else if (val.length > 1) {
|
|
val = '\\\\';
|
|
}
|
|
|
|
return pos({
|
|
type: 'backslash',
|
|
val: val
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Square: "[.]"
|
|
*/
|
|
|
|
.capture('square', function() {
|
|
if (this.isInside('bracket')) return;
|
|
var pos = this.position();
|
|
var m = this.match(/^\[([^!^\\])\]/);
|
|
if (!m) return;
|
|
|
|
return pos({
|
|
type: 'square',
|
|
val: m[1]
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Brackets: "[...]" (basic, this can be overridden by other parsers)
|
|
*/
|
|
|
|
.capture('bracket', function() {
|
|
var pos = this.position();
|
|
var m = this.match(/^(?:\[([!^]?)([^\]]+|\]-)(\]|[^*+?]+)|\[)/);
|
|
if (!m) return;
|
|
|
|
var val = m[0];
|
|
var negated = m[1] ? '^' : '';
|
|
var inner = (m[2] || '').replace(/\\\\+/, '\\\\');
|
|
var close = m[3] || '';
|
|
|
|
if (m[2] && inner.length < m[2].length) {
|
|
val = val.replace(/\\\\+/, '\\\\');
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return pos({
|
|
type: 'bracket',
|
|
val: val,
|
|
escaped: close !== ']',
|
|
negated: negated,
|
|
inner: inner,
|
|
close: close
|
|
});
|
|
})
|
|
|
|
/**
|
|
* Text
|
|
*/
|
|
|
|
.capture('text', function() {
|
|
if (this.isInside('bracket')) return;
|
|
var pos = this.position();
|
|
var m = this.match(not);
|
|
if (!m || !m[0]) return;
|
|
|
|
return pos({
|
|
type: 'text',
|
|
val: m[0]
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Allow custom parsers to be passed on options
|
|
*/
|
|
|
|
if (options && typeof options.parsers === 'function') {
|
|
options.parsers(nanomatch.parser);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Advance to the next non-escaped character
|
|
*/
|
|
|
|
function advanceTo(input, endChar) {
|
|
var ch = input.charAt(0);
|
|
var tok = { len: 1, val: '', esc: '' };
|
|
var idx = 0;
|
|
|
|
function advance() {
|
|
if (ch !== '\\') {
|
|
tok.esc += '\\' + ch;
|
|
tok.val += ch;
|
|
}
|
|
|
|
ch = input.charAt(++idx);
|
|
tok.len++;
|
|
|
|
if (ch === '\\') {
|
|
advance();
|
|
advance();
|
|
}
|
|
}
|
|
|
|
while (ch && ch !== endChar) {
|
|
advance();
|
|
}
|
|
return tok;
|
|
}
|
|
|
|
/**
|
|
* Create text regex
|
|
*/
|
|
|
|
function createTextRegex(pattern) {
|
|
if (cached) return cached;
|
|
var opts = {contains: true, strictClose: false};
|
|
var not = regexNot.create(pattern, opts);
|
|
var re = toRegex('^(?:[*]\\((?=.)|' + not + ')', opts);
|
|
return (cached = re);
|
|
}
|
|
|
|
/**
|
|
* Expose negation string
|
|
*/
|
|
|
|
module.exports.not = NOT_REGEX;
|
|
|