Spaces:
Runtime error
Runtime error
const isWin = process.platform === 'win32'; | |
const SEP = isWin ? `\\\\+` : `\\/`; | |
const SEP_ESC = isWin ? `\\\\` : `/`; | |
const GLOBSTAR = `((?:[^/]*(?:/|$))*)`; | |
const WILDCARD = `([^/]*)`; | |
const GLOBSTAR_SEGMENT = `((?:[^${SEP_ESC}]*(?:${SEP_ESC}|$))*)`; | |
const WILDCARD_SEGMENT = `([^${SEP_ESC}]*)`; | |
/** | |
* Convert any glob pattern to a JavaScript Regexp object | |
* @param {String} glob Glob pattern to convert | |
* @param {Object} opts Configuration object | |
* @param {Boolean} [opts.extended=false] Support advanced ext globbing | |
* @param {Boolean} [opts.globstar=false] Support globstar | |
* @param {Boolean} [opts.strict=true] be laissez faire about mutiple slashes | |
* @param {Boolean} [opts.filepath=''] Parse as filepath for extra path related features | |
* @param {String} [opts.flags=''] RegExp globs | |
* @returns {Object} converted object with string, segments and RegExp object | |
*/ | |
function globrex(glob, {extended = false, globstar = false, strict = false, filepath = false, flags = ''} = {}) { | |
let regex = ''; | |
let segment = ''; | |
let path = { regex: '', segments: [] }; | |
// If we are doing extended matching, this boolean is true when we are inside | |
// a group (eg {*.html,*.js}), and false otherwise. | |
let inGroup = false; | |
let inRange = false; | |
// extglob stack. Keep track of scope | |
const ext = []; | |
// Helper function to build string and segments | |
function add(str, {split, last, only}={}) { | |
if (only !== 'path') regex += str; | |
if (filepath && only !== 'regex') { | |
path.regex += (str === '\\/' ? SEP : str); | |
if (split) { | |
if (last) segment += str; | |
if (segment !== '') { | |
if (!flags.includes('g')) segment = `^${segment}$`; // change it 'includes' | |
path.segments.push(new RegExp(segment, flags)); | |
} | |
segment = ''; | |
} else { | |
segment += str; | |
} | |
} | |
} | |
let c, n; | |
for (let i = 0; i < glob.length; i++) { | |
c = glob[i]; | |
n = glob[i + 1]; | |
if (['\\', '$', '^', '.', '='].includes(c)) { | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === '/') { | |
add(`\\${c}`, {split: true}); | |
if (n === '/' && !strict) regex += '?'; | |
continue; | |
} | |
if (c === '(') { | |
if (ext.length) { | |
add(c); | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === ')') { | |
if (ext.length) { | |
add(c); | |
let type = ext.pop(); | |
if (type === '@') { | |
add('{1}'); | |
} else if (type === '!') { | |
add('([^\/]*)'); | |
} else { | |
add(type); | |
} | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === '|') { | |
if (ext.length) { | |
add(c); | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === '+') { | |
if (n === '(' && extended) { | |
ext.push(c); | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === '@' && extended) { | |
if (n === '(') { | |
ext.push(c); | |
continue; | |
} | |
} | |
if (c === '!') { | |
if (extended) { | |
if (inRange) { | |
add('^'); | |
continue | |
} | |
if (n === '(') { | |
ext.push(c); | |
add('(?!'); | |
i++; | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === '?') { | |
if (extended) { | |
if (n === '(') { | |
ext.push(c); | |
} else { | |
add('.'); | |
} | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === '[') { | |
if (inRange && n === ':') { | |
i++; // skip [ | |
let value = ''; | |
while(glob[++i] !== ':') value += glob[i]; | |
if (value === 'alnum') add('(\\w|\\d)'); | |
else if (value === 'space') add('\\s'); | |
else if (value === 'digit') add('\\d'); | |
i++; // skip last ] | |
continue; | |
} | |
if (extended) { | |
inRange = true; | |
add(c); | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === ']') { | |
if (extended) { | |
inRange = false; | |
add(c); | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === '{') { | |
if (extended) { | |
inGroup = true; | |
add('('); | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === '}') { | |
if (extended) { | |
inGroup = false; | |
add(')'); | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === ',') { | |
if (inGroup) { | |
add('|'); | |
continue; | |
} | |
add(`\\${c}`); | |
continue; | |
} | |
if (c === '*') { | |
if (n === '(' && extended) { | |
ext.push(c); | |
continue; | |
} | |
// Move over all consecutive "*"'s. | |
// Also store the previous and next characters | |
let prevChar = glob[i - 1]; | |
let starCount = 1; | |
while (glob[i + 1] === '*') { | |
starCount++; | |
i++; | |
} | |
let nextChar = glob[i + 1]; | |
if (!globstar) { | |
// globstar is disabled, so treat any number of "*" as one | |
add('.*'); | |
} else { | |
// globstar is enabled, so determine if this is a globstar segment | |
let isGlobstar = | |
starCount > 1 && // multiple "*"'s | |
(prevChar === '/' || prevChar === undefined) && // from the start of the segment | |
(nextChar === '/' || nextChar === undefined); // to the end of the segment | |
if (isGlobstar) { | |
// it's a globstar, so match zero or more path segments | |
add(GLOBSTAR, {only:'regex'}); | |
add(GLOBSTAR_SEGMENT, {only:'path', last:true, split:true}); | |
i++; // move over the "/" | |
} else { | |
// it's not a globstar, so only match one path segment | |
add(WILDCARD, {only:'regex'}); | |
add(WILDCARD_SEGMENT, {only:'path'}); | |
} | |
} | |
continue; | |
} | |
add(c); | |
} | |
// When regexp 'g' flag is specified don't | |
// constrain the regular expression with ^ & $ | |
if (!flags.includes('g')) { | |
regex = `^${regex}$`; | |
segment = `^${segment}$`; | |
if (filepath) path.regex = `^${path.regex}$`; | |
} | |
const result = {regex: new RegExp(regex, flags)}; | |
// Push the last segment | |
if (filepath) { | |
path.segments.push(new RegExp(segment, flags)); | |
path.regex = new RegExp(path.regex, flags); | |
path.globstar = new RegExp(!flags.includes('g') ? `^${GLOBSTAR_SEGMENT}$` : GLOBSTAR_SEGMENT, flags); | |
result.path = path; | |
} | |
return result; | |
} | |
module.exports = globrex; | |