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.

387 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;