/* | |
MIT License http://www.opensource.org/licenses/mit-license.php | |
Author Tobias Koppers @sokra | |
*/ | |
"use strict"; | |
function globToRegExp(glob) { | |
// * [^\\\/]* | |
// /**/ /.+/ | |
// ^* \./.+ (concord special) | |
// ? [^\\\/] | |
// [!...] [^...] | |
// [^...] [^...] | |
// / [\\\/] | |
// {...,...} (...|...) | |
// ?(...|...) (...|...)? | |
// +(...|...) (...|...)+ | |
// *(...|...) (...|...)* | |
// @(...|...) (...|...) | |
if(/^\(.+\)$/.test(glob)) { | |
// allow to pass an RegExp in brackets | |
return new RegExp(glob.substr(1, glob.length - 2)); | |
} | |
const tokens = tokenize(glob); | |
const process = createRoot(); | |
const regExpStr = tokens.map(process).join(""); | |
return new RegExp("^" + regExpStr + "$"); | |
} | |
const SIMPLE_TOKENS = { | |
"@(": "one", | |
"?(": "zero-one", | |
"+(": "one-many", | |
"*(": "zero-many", | |
"|": "segment-sep", | |
"/**/": "any-path-segments", | |
"**": "any-path", | |
"*": "any-path-segment", | |
"?": "any-char", | |
"{": "or", | |
"/": "path-sep", | |
",": "comma", | |
")": "closing-segment", | |
"}": "closing-or" | |
}; | |
function tokenize(glob) { | |
return glob.split(/([@?+*]\(|\/\*\*\/|\*\*|[?*]|\[[\!\^]?(?:[^\]\\]|\\.)+\]|\{|,|\/|[|)}])/g).map(item => { | |
if(!item) | |
return null; | |
const t = SIMPLE_TOKENS[item]; | |
if(t) { | |
return { | |
type: t | |
}; | |
} | |
if(item[0] === "[") { | |
if(item[1] === "^" || item[1] === "!") { | |
return { | |
type: "inverted-char-set", | |
value: item.substr(2, item.length - 3) | |
}; | |
} else { | |
return { | |
type: "char-set", | |
value: item.substr(1, item.length - 2) | |
}; | |
} | |
} | |
return { | |
type: "string", | |
value: item | |
}; | |
}).filter(Boolean).concat({ | |
type: "end" | |
}); | |
} | |
function createRoot() { | |
const inOr = []; | |
const process = createSeqment(); | |
let initial = true; | |
return function(token) { | |
switch(token.type) { | |
case "or": | |
inOr.push(initial); | |
return "("; | |
case "comma": | |
if(inOr.length) { | |
initial = inOr[inOr.length - 1]; | |
return "|"; | |
} else { | |
return process({ | |
type: "string", | |
value: "," | |
}, initial); | |
} | |
case "closing-or": | |
if(inOr.length === 0) | |
throw new Error("Unmatched '}'"); | |
inOr.pop(); | |
return ")"; | |
case "end": | |
if(inOr.length) | |
throw new Error("Unmatched '{'"); | |
return process(token, initial); | |
default: | |
{ | |
const result = process(token, initial); | |
initial = false; | |
return result; | |
} | |
} | |
}; | |
} | |
function createSeqment() { | |
const inSeqment = []; | |
const process = createSimple(); | |
return function(token, initial) { | |
switch(token.type) { | |
case "one": | |
case "one-many": | |
case "zero-many": | |
case "zero-one": | |
inSeqment.push(token.type); | |
return "("; | |
case "segment-sep": | |
if(inSeqment.length) { | |
return "|"; | |
} else { | |
return process({ | |
type: "string", | |
value: "|" | |
}, initial); | |
} | |
case "closing-segment": | |
{ | |
const segment = inSeqment.pop(); | |
switch(segment) { | |
case "one": | |
return ")"; | |
case "one-many": | |
return ")+"; | |
case "zero-many": | |
return ")*"; | |
case "zero-one": | |
return ")?"; | |
} | |
throw new Error("Unexcepted segment " + segment); | |
} | |
case "end": | |
if(inSeqment.length > 0) { | |
throw new Error("Unmatched segment, missing ')'"); | |
} | |
return process(token, initial); | |
default: | |
return process(token, initial); | |
} | |
}; | |
} | |
function createSimple() { | |
return function(token, initial) { | |
switch(token.type) { | |
case "path-sep": | |
return "[\\\\/]+"; | |
case "any-path-segments": | |
return "[\\\\/]+(?:(.+)[\\\\/]+)?"; | |
case "any-path": | |
return "(.*)"; | |
case "any-path-segment": | |
if(initial) { | |
return "\\.[\\\\/]+(?:.*[\\\\/]+)?([^\\\\/]+)"; | |
} else { | |
return "([^\\\\/]*)"; | |
} | |
case "any-char": | |
return "[^\\\\/]"; | |
case "inverted-char-set": | |
return "[^" + token.value + "]"; | |
case "char-set": | |
return "[" + token.value + "]"; | |
case "string": | |
return token.value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); | |
case "end": | |
return ""; | |
default: | |
throw new Error("Unsupported token '" + token.type + "'"); | |
} | |
}; | |
} | |
exports.globToRegExp = globToRegExp; |