123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- 'use strict';
- var regexNot = require('regex-not');
- var toRegex = require('to-regex');
- var isOdd = require('is-odd');
- /**
- * 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('bos', function() {
- if (this.parsed) return;
- var pos = this.position();
- 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 = isOdd(val.length);
- 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});
- if (this.input.slice(0, 4) === '/**/') {
- this.input = this.input.slice(3);
- }
- if (type === 'globstar') {
- this.state.globstar = true;
- node.val = '**';
- } else {
- this.state.star = true;
- node.val = '*';
- }
- this.state.metachar = true;
- 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;
|