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