diff options
Diffstat (limited to 'std/path/_globrex.ts')
-rw-r--r-- | std/path/_globrex.ts | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/std/path/_globrex.ts b/std/path/_globrex.ts new file mode 100644 index 000000000..3fc69dd6c --- /dev/null +++ b/std/path/_globrex.ts @@ -0,0 +1,327 @@ +// This file is ported from globrex@0.1.2 +// MIT License +// Copyright (c) 2018 Terkel Gjervig Nielsen + +const isWin = Deno.build.os === "windows"; +const SEP = isWin ? `(?:\\\\|\\/)` : `\\/`; +const SEP_ESC = isWin ? `\\\\` : `/`; +const SEP_RAW = isWin ? `\\` : `/`; +const GLOBSTAR = `(?:(?:[^${SEP_ESC}/]*(?:${SEP_ESC}|\/|$))*)`; +const WILDCARD = `(?:[^${SEP_ESC}/]*)`; +const GLOBSTAR_SEGMENT = `((?:[^${SEP_ESC}/]*(?:${SEP_ESC}|\/|$))*)`; +const WILDCARD_SEGMENT = `(?:[^${SEP_ESC}/]*)`; + +export interface GlobrexOptions { + /** Allow ExtGlob features. + * @default false */ + extended?: boolean; + /** Support globstar. + * @remarks When globstar is `true`, '/foo/**' is equivelant + * to '/foo/*' when globstar is `false`. + * Having globstar set to `true` is the same usage as + * using wildcards in bash. + * @default false */ + globstar?: boolean; + /** Be laissez-faire about mutiple slashes. + * @default true */ + strict?: boolean; + /** Parse as filepath for extra path related features. + * @default false */ + filepath?: boolean; + /** Flag to use in the generated RegExp. */ + flags?: string; +} + +export interface GlobrexResult { + regex: RegExp; + path?: { + regex: RegExp; + segments: RegExp[]; + globstar?: RegExp; + }; +} + +/** + * Convert any glob pattern to a JavaScript Regexp object + * @param glob Glob pattern to convert + * @param opts Configuration object + * @returns Converted object with string, segments and RegExp object + */ +export function globrex( + glob: string, + { + extended = false, + globstar = false, + strict = false, + filepath = false, + flags = "", + }: GlobrexOptions = {} +): GlobrexResult { + const sepPattern = new RegExp(`^${SEP}${strict ? "" : "+"}$`); + let regex = ""; + let segment = ""; + let pathRegexStr = ""; + const pathSegments = []; + + // 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 = []; + + interface AddOptions { + split?: boolean; + last?: boolean; + only?: string; + } + + // Helper function to build string and segments + function add( + str: string, + options: AddOptions = { split: false, last: false, only: "" } + ): void { + const { split, last, only } = options; + if (only !== "path") regex += str; + if (filepath && only !== "regex") { + pathRegexStr += str.match(sepPattern) ? SEP : str; + if (split) { + if (last) segment += str; + if (segment !== "") { + // change it 'includes' + if (!flags.includes("g")) segment = `^${segment}$`; + pathSegments.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.match(sepPattern)) { + add(SEP, { split: true }); + if (n != null && n.match(sepPattern) && !strict) regex += "?"; + continue; + } + + if (c === "(") { + if (ext.length) { + add(`${c}?:`); + continue; + } + add(`\\${c}`); + continue; + } + + if (c === ")") { + if (ext.length) { + add(c); + const type: string | undefined = ext.pop(); + if (type === "@") { + add("{1}"); + } else if (type === "!") { + add(WILDCARD); + } else { + add(type as string); + } + 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 + const prevChar = glob[i - 1]; + let starCount = 1; + while (glob[i + 1] === "*") { + starCount++; + i++; + } + const 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 + const isGlobstar = + starCount > 1 && // multiple "*"'s + // from the start of the segment + [SEP_RAW, "/", undefined].includes(prevChar) && + // to the end of the segment + [SEP_RAW, "/", undefined].includes(nextChar); + 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) pathRegexStr = `^${pathRegexStr}$`; + } + + const result: GlobrexResult = { regex: new RegExp(regex, flags) }; + + // Push the last segment + if (filepath) { + pathSegments.push(new RegExp(segment, flags)); + result.path = { + regex: new RegExp(pathRegexStr, flags), + segments: pathSegments, + globstar: new RegExp( + !flags.includes("g") ? `^${GLOBSTAR_SEGMENT}$` : GLOBSTAR_SEGMENT, + flags + ), + }; + } + + return result; +} |