UNPKG

outmatch

Version:

An extremely fast and lightweight glob-matching library with advanced features

524 lines (517 loc) 18.1 kB
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