import { NATIVE_OS } from "./_constants.ts";import { join, normalize } from "./mod.ts";import { SEP, SEP_PATTERN } from "./separator.ts";
export interface GlobOptions { extended?: boolean; globstar?: boolean; os?: typeof Deno.build.os;}
export type GlobToRegExpOptions = GlobOptions;
export function globToRegExp( glob: string, { extended = true, globstar: globstarOption = true, os = NATIVE_OS }: GlobToRegExpOptions = {},): RegExp { const sep = os == "windows" ? `(?:\\\\|\\/)+` : `\\/+`; const sepMaybe = os == "windows" ? `(?:\\\\|\\/)*` : `\\/*`; const seps = os == "windows" ? ["\\", "/"] : ["/"]; const sepRaw = os == "windows" ? `\\` : `/`; const globstar = os == "windows" ? `(?:[^\\\\/]*(?:\\\\|\\/|$)+)*` : `(?:[^/]*(?:\\/|$)+)*`; const wildcard = os == "windows" ? `[^\\\\/]*` : `[^/]*`;
const extStack = [];
let inGroup = false; let inRange = false;
let regExpString = "";
let newLength = glob.length; for (; newLength > 0 && seps.includes(glob[newLength - 1]); newLength--); glob = glob.slice(0, newLength);
let c, n; for (let i = 0; i < glob.length; i++) { c = glob[i]; n = glob[i + 1];
if (seps.includes(c)) { regExpString += sep; while (seps.includes(glob[i + 1])) i++; continue; }
if (c == "[") { if (inRange && n == ":") { i++; let value = ""; while (glob[++i] !== ":") value += glob[i]; if (value == "alnum") regExpString += "\\w\\d"; else if (value == "space") regExpString += "\\s"; else if (value == "digit") regExpString += "\\d"; i++; continue; } inRange = true; regExpString += c; continue; }
if (c == "]") { inRange = false; regExpString += c; continue; }
if (c == "!") { if (inRange) { if (glob[i - 1] == "[") { regExpString += "^"; continue; } } else if (extended) { if (n == "(") { extStack.push(c); regExpString += "(?!"; i++; continue; } regExpString += `\\${c}`; continue; } else { regExpString += `\\${c}`; continue; } }
if (inRange) { if (c == "\\" || c == "^" && glob[i - 1] == "[") regExpString += `\\${c}`; else regExpString += c; continue; }
if (["\\", "$", "^", ".", "="].includes(c)) { regExpString += `\\${c}`; continue; }
if (c == "(") { if (extStack.length) { regExpString += `${c}?:`; continue; } regExpString += `\\${c}`; continue; }
if (c == ")") { if (extStack.length) { regExpString += c; const type = extStack.pop()!; if (type == "@") { regExpString += "{1}"; } else if (type == "!") { regExpString += wildcard; } else { regExpString += type; } continue; } regExpString += `\\${c}`; continue; }
if (c == "|") { if (extStack.length) { regExpString += c; continue; } regExpString += `\\${c}`; continue; }
if (c == "+") { if (n == "(" && extended) { extStack.push(c); continue; } regExpString += `\\${c}`; continue; }
if (c == "@" && extended) { if (n == "(") { extStack.push(c); continue; } }
if (c == "?") { if (extended) { if (n == "(") { extStack.push(c); } continue; } else { regExpString += "."; continue; } }
if (c == "{") { inGroup = true; regExpString += "(?:"; continue; }
if (c == "}") { inGroup = false; regExpString += ")"; continue; }
if (c == ",") { if (inGroup) { regExpString += "|"; continue; } regExpString += `\\${c}`; continue; }
if (c == "*") { if (n == "(" && extended) { extStack.push(c); continue; } const prevChar = glob[i - 1]; let starCount = 1; while (glob[i + 1] == "*") { starCount++; i++; } const nextChar = glob[i + 1]; const isGlobstar = globstarOption && starCount > 1 && [sepRaw, "/", undefined].includes(prevChar) && [sepRaw, "/", undefined].includes(nextChar); if (isGlobstar) { regExpString += globstar; while (seps.includes(glob[i + 1])) i++; } else { regExpString += wildcard; } continue; }
regExpString += c; }
regExpString = `^${regExpString}${regExpString != "" ? sepMaybe : ""}$`; return new RegExp(regExpString);}
export function isGlob(str: string): boolean { const chars: Record<string, string> = { "{": "}", "(": ")", "[": "]" }; const regex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/;
if (str === "") { return false; }
let match: RegExpExecArray | null;
while ((match = regex.exec(str))) { if (match[2]) return true; let idx = match.index + match[0].length;
const open = match[1]; const close = open ? chars[open] : null; if (open && close) { const n = str.indexOf(close, idx); if (n !== -1) { idx = n + 1; } }
str = str.slice(idx); }
return false;}
export function normalizeGlob( glob: string, { globstar = false }: GlobOptions = {},): string { if (glob.match(/\0/g)) { throw new Error(`Glob contains invalid characters: "${glob}"`); } if (!globstar) { return normalize(glob); } const s = SEP_PATTERN.source; const badParentPattern = new RegExp( `(?<=(${s}|^)\\*\\*${s})\\.\\.(?=${s}|$)`, "g", ); return normalize(glob.replace(badParentPattern, "\0")).replace(/\0/g, "..");}
export function joinGlobs( globs: string[], { extended = false, globstar = false }: GlobOptions = {},): string { if (!globstar || globs.length == 0) { return join(...globs); } if (globs.length === 0) return "."; let joined: string | undefined; for (const glob of globs) { const path = glob; if (path.length > 0) { if (!joined) joined = path; else joined += `${SEP}${path}`; } } if (!joined) return "."; return normalizeGlob(joined, { extended, globstar });}