outmatch
Version:
An extremely fast and lightweight glob-matching library with advanced features
524 lines (517 loc) • 18.1 kB
JavaScript
function handleNoCommaBraces(span) {
if (span.length < 3) {
return "{" + span + "}";
}
var separatorI = -1;
for (var i = 2; i < span.length; i++) {
if (span[i] === '.' && span[i - 1] === '.' && (i < 2 || span[i - 2] !== '\\')) {
if (separatorI > -1) {
return "{" + span + "}";
}
separatorI = i - 1;
}
}
if (separatorI > -1) {
var rangeStart = span.substr(0, separatorI);
var rangeEnd = span.substr(separatorI + 2);
if (rangeStart.length > 0 && rangeEnd.length > 0) {
return "[" + span.substr(0, separatorI) + "-" + span.substr(separatorI + 2) + "]";
}
}
return "{" + span + "}";
}
function expand(pattern) {
if (typeof pattern !== 'string') {
throw new TypeError("A pattern must be a string, but " + typeof pattern + " given");
}
var scanning = false;
var openingBraces = 0;
var closingBraces = 0;
var handledUntil = -1;
var results = [''];
var alternatives = [];
var span;
for (var i = 0; i < pattern.length; i++) {
var char = pattern[i];
if (char === '\\') {
i++;
continue;
}
if (char === '{') {
if (scanning) {
openingBraces++;
}
else if (i > handledUntil && !openingBraces) {
span = pattern.substring(handledUntil + 1, i);
for (var j = 0; j < results.length; j++) {
results[j] += span;
}
alternatives = [];
handledUntil = i;
scanning = true;
openingBraces++;
}
else {
openingBraces--;
}
}
else if (char === '}') {
if (scanning) {
closingBraces++;
}
else if (closingBraces === 1) {
span = pattern.substring(handledUntil + 1, i);
if (alternatives.length > 0) {
var newResults = [];
alternatives.push(expand(span));
for (var j = 0; j < results.length; j++) {
for (var k = 0; k < alternatives.length; k++) {
for (var l = 0; l < alternatives[k].length; l++) {
newResults.push(results[j] + alternatives[k][l]);
}
}
}
results = newResults;
}
else {
span = handleNoCommaBraces(span);
for (var j = 0; j < results.length; j++) {
results[j] += span;
}
}
handledUntil = i;
closingBraces--;
}
else {
closingBraces--;
}
}
else if (!scanning && char === ',' && closingBraces - openingBraces === 1) {
span = pattern.substring(handledUntil + 1, i);
alternatives.push(expand(span));
handledUntil = i;
}
if (scanning && (closingBraces === openingBraces || i === pattern.length - 1)) {
scanning = false;
i = handledUntil - 1;
}
}
if (handledUntil === -1) {
return [pattern];
}
var unhandledFrom = pattern[handledUntil] === '{' ? handledUntil : handledUntil + 1;
if (unhandledFrom < pattern.length) {
span = pattern.substr(unhandledFrom);
for (var j = 0; j < results.length; j++) {
results[j] += span;
}
}
return results;
}
function negate(pattern, options) {
var supportNegation = options['!'] !== false;
var supportParens = options['()'] !== false;
var isNegated = false;
var i;
if (supportNegation) {
for (i = 0; i < pattern.length && pattern[i] === '!'; i++) {
if (supportParens && pattern[i + 1] === '(') {
i--;
break;
}
isNegated = !isNegated;
}
if (i > 0) {
pattern = pattern.substr(i);
}
}
return { pattern: pattern, isNegated: isNegated };
}
function escapeRegExpChar(char) { if (char === '-' ||
char === '^' ||
char === '$' ||
char === '+' ||
char === '.' ||
char === '(' ||
char === ')' ||
char === '|' ||
char === '[' ||
char === ']' ||
char === '{' ||
char === '}' ||
char === '*' ||
char === '?' ||
char === '\\') {
return "\\" + char;
}
else {
return char;
} }
function escapeRegExpString(str) {
var result = '';
for (var i = 0; i < str.length; i++) {
result += escapeRegExpChar(str[i]);
}
return result;
}
function Pattern(source, options, excludeDot) {
var separator = typeof options.separator === 'undefined' ? true : options.separator;
var separatorSplitter = '';
var separatorMatcher = '';
var wildcard = '.';
if (separator === true) {
separatorSplitter = '/';
separatorMatcher = '[/\\\\]';
wildcard = '[^/\\\\]';
}
else if (separator) {
separatorSplitter = separator;
separatorMatcher = escapeRegExpString(separatorSplitter);
if (separatorMatcher.length > 1) {
separatorMatcher = "(?:" + separatorMatcher + ")";
wildcard = "((?!" + separatorMatcher + ").)";
}
else {
wildcard = "[^" + separatorMatcher + "]";
}
}
else {
wildcard = '.';
}
var requiredSeparator = separator ? separatorMatcher + "+?" : '';
var optionalSeparator = separator ? separatorMatcher + "*?" : '';
var segments = separator ? source.split(separatorSplitter) : [source];
var support = {
qMark: options['?'] !== false,
star: options['*'] !== false,
globstar: separator && options['**'] !== false,
brackets: options['[]'] !== false,
extglobs: options['()'] !== false,
excludeDot: excludeDot && options.excludeDot !== false,
};
return {
source: source,
segments: segments,
options: options,
separator: separator,
separatorSplitter: separatorSplitter,
separatorMatcher: separatorMatcher,
optionalSeparator: optionalSeparator,
requiredSeparator: requiredSeparator,
wildcard: wildcard,
support: support,
};
}
function Segment(source, pattern, isFirst, isLast) { return {
source: source,
isFirst: isFirst,
isLast: isLast,
end: source.length - 1,
}; }
function Result() {
return {
match: '',
unmatch: '',
useUnmatch: false,
}; }
function State(pattern, segment, result) { return {
pattern: pattern,
segment: segment,
result: result,
openingBracket: segment.end + 1,
closingBracket: -1,
openingParens: 0,
closingParens: 0,
parensHandledUntil: -1,
extglobModifiers: [],
scanningForParens: false,
escapeChar: false,
addToMatch: true,
addToUnmatch: pattern.support.extglobs,
dotHandled: false,
i: -1,
char: '',
nextChar: '',
}; }
var EXCLUDE_DOT_PATTERN = '(?!\\.)';
function add(state, addition, excludeDot) {
if (state.addToUnmatch) {
state.result.unmatch += addition;
}
if (state.addToMatch) {
if (excludeDot && !state.dotHandled) {
addition = EXCLUDE_DOT_PATTERN + addition;
}
state.dotHandled = true;
state.result.match += addition;
}
return state.result;
}
function convertSegment(pattern, segment, result) {
var support = pattern.support;
var state = State(pattern, segment, result);
var separatorMatcher = segment.isLast
? pattern.optionalSeparator
: pattern.requiredSeparator;
if (!support.excludeDot) {
state.dotHandled = true;
}
if (segment.end === -1) {
return segment.isLast && !segment.isFirst ? result : add(state, separatorMatcher);
}
if (support.globstar && segment.source === '**') {
var prefix = !state.dotHandled ? EXCLUDE_DOT_PATTERN : '';
var globstarSegment = prefix + pattern.wildcard + "*?" + separatorMatcher;
return add(state, "(?:" + globstarSegment + ")*?");
}
while (++state.i <= segment.end) {
state.char = state.segment.source[state.i];
state.nextChar = state.i < segment.end ? segment.source[state.i + 1] : '';
if (state.char === '\\') {
if (state.i < state.segment.end) {
state.escapeChar = true;
continue;
}
else {
state.char = '';
}
}
var pattern = state.pattern, segment = state.segment, char = state.char, i = state.i;
if (pattern.support.brackets && !state.scanningForParens) {
if (i > state.openingBracket && i <= state.closingBracket) {
if (state.escapeChar) {
add(state, escapeRegExpChar(char));
}
else if (i === state.closingBracket) {
add(state, ']');
state.openingBracket = segment.source.length;
}
else if (char === '-' && i === state.closingBracket - 1) {
add(state, '\\-');
}
else if (char === '!' && i === state.openingBracket + 1) {
add(state, '^');
}
else if (char === ']') {
add(state, '\\]');
}
else {
add(state, char);
}
state.escapeChar = false;
continue;
}
if (i > state.openingBracket) {
if (char === ']' &&
!state.escapeChar &&
i > state.openingBracket + 1 &&
i > state.closingBracket) {
state.closingBracket = i;
state.i = state.openingBracket;
if (pattern.separator) {
add(state, "(?!" + pattern.separatorMatcher + ")[", true);
}
else {
add(state, '[', true);
}
}
else if (i === segment.end) {
add(state, '\\[');
state.i = state.openingBracket;
state.openingBracket = segment.source.length;
state.closingBracket = segment.source.length;
}
state.escapeChar = false;
continue;
}
if (char === '[' &&
!state.escapeChar &&
i > state.closingBracket &&
i < segment.end) {
state.openingBracket = i;
state.escapeChar = false;
continue;
}
}
if (state.pattern.support.extglobs) {
var extglobModifiers = state.extglobModifiers, char = state.char, nextChar = state.nextChar, i = state.i;
if (nextChar === '(' &&
!state.escapeChar &&
(char === '@' || char === '?' || char === '*' || char === '+' || char === '!')) {
if (state.scanningForParens) {
state.openingParens++;
}
else if (i > state.parensHandledUntil && !state.closingParens) {
state.parensHandledUntil = i;
state.scanningForParens = true;
state.openingParens++;
}
else if (state.closingParens >= state.openingParens) {
if (char === '!') {
state.addToMatch = true;
state.addToUnmatch = false;
add(state, state.pattern.wildcard + "*?", true);
state.addToMatch = false;
state.addToUnmatch = true;
state.result.useUnmatch = true;
}
extglobModifiers.push(char);
add(state, '(?:', true);
state.openingParens--;
state.i++;
continue;
}
else {
state.openingParens--;
}
}
else if (char === ')' && !state.escapeChar) {
if (state.scanningForParens) {
state.closingParens++;
}
else if (extglobModifiers.length) {
var modifier_1 = extglobModifiers.pop();
if (modifier_1 === '!' && extglobModifiers.indexOf('!') !== -1) {
throw new Error("Nested negated extglobs aren't supported");
}
modifier_1 = modifier_1 === '!' || modifier_1 === '@' ? '' : modifier_1;
add(state, ")" + modifier_1);
state.addToMatch = true;
state.addToUnmatch = true;
state.closingParens--;
continue;
}
}
else if (char === '|' && state.closingParens &&
!state.scanningForParens &&
!state.escapeChar) {
add(state, '|');
continue;
}
if (state.scanningForParens) {
if (state.closingParens === state.openingParens || i === state.segment.end) {
state.scanningForParens = false;
state.i = state.parensHandledUntil - 1;
}
state.escapeChar = false;
continue;
}
}
var pattern = state.pattern;
var support = pattern.support;
if (!state.escapeChar && support.star && state.char === '*') {
if (state.i === state.segment.end || state.nextChar !== '*') {
add(state, pattern.wildcard + "*?", true);
}
}
else if (!state.escapeChar && support.qMark && state.char === '?') {
add(state, pattern.wildcard, true);
}
else {
add(state, escapeRegExpChar(state.char));
}
state.escapeChar = false;
}
return add(state, separatorMatcher);
}
function convert(source, options, excludeDot) {
var pattern = Pattern(source, options, excludeDot);
var result = Result();
var segments = pattern.segments;
for (var i = 0; i < segments.length; i++) {
var segment = Segment(segments[i], pattern, i === 0, i === segments.length - 1);
convertSegment(pattern, segment, result);
}
if (result.useUnmatch) {
return "(?!^" + result.unmatch + "$)" + result.match;
}
else {
return result.match;
}
}
function flatMap(array, predicate) {
var results = [];
for (var i = 0; i < array.length; i++) {
var mappedValue = predicate(array[i]);
for (var j = 0; j < mappedValue.length; j++) {
results.push(mappedValue[j]);
}
}
return results;
}
function compile(patterns, options) {
patterns = Array.isArray(patterns) ? patterns : [patterns];
if (options['{}'] !== false) {
patterns = flatMap(patterns, expand);
}
var positiveResults = [];
var negativeResults = [];
var result = '';
for (var i = 0; i < patterns.length; i++) {
var negatedPattern = negate(patterns[i], options);
var convertedPattern = convert(negatedPattern.pattern, options, !negatedPattern.isNegated);
if (negatedPattern.isNegated) {
negativeResults.push(convertedPattern);
}
else {
positiveResults.push(convertedPattern);
}
}
if (negativeResults.length) {
result = "(?!(?:" + negativeResults.join('|') + ")$)";
}
if (positiveResults.length > 1) {
result += "(?:" + positiveResults.join('|') + ")";
}
else if (positiveResults.length === 1) {
result += positiveResults[0];
}
else if (result.length) {
result += convert('**', options, true);
}
return "^" + result + "$";
}
function isMatch(regexp, sample) { if (typeof sample !== 'string') {
throw new TypeError("Sample must be a string, but " + typeof sample + " given");
} return regexp.test(sample); }
/**
* Compiles one or more glob patterns into a RegExp and returns an isMatch function.
* The isMatch function takes a sample string as its only argument and returns true
* if the string matches the pattern(s).
*
* ```js
* outmatch('src/*.js')('src/index.js') //=> true
* ```
*
* ```js
* const isMatch = outmatch('*.example.com', '.')
* isMatch('foo.example.com') //=> true
* isMatch('foo.bar.com') //=> false
* ```
*/
function outmatch(pattern, options) {
if (typeof pattern !== 'string' && !Array.isArray(pattern)) {
throw new TypeError("The first argument must be a single pattern string or an array of patterns, but " + typeof pattern + " given");
}
if (typeof options === 'string' || typeof options === 'boolean') {
options = { separator: options };
}
if (arguments.length === 2 &&
!(typeof options === 'undefined' ||
(typeof options === 'object' && options !== null && !Array.isArray(options)))) {
throw new TypeError("The second argument must be an options object or a string/boolean separator, but " + typeof options + " given");
}
options = options || {};
if (options.separator === '\\') {
throw new Error('\\ is not a valid separator');
}
var regexpPattern = compile(pattern, options);
var regexp = new RegExp(regexpPattern, options.flags);
var fn = isMatch.bind(null, regexp);
fn.options = options;
fn.pattern = pattern;
fn.regexp = regexp;
return fn;
}
export { outmatch as default };
//# sourceMappingURL=index.es.mjs.map