diff options
Diffstat (limited to 'std/path')
-rw-r--r-- | std/path/_constants.ts | 14 | ||||
-rw-r--r-- | std/path/_globrex.ts | 328 | ||||
-rw-r--r-- | std/path/_globrex_test.ts | 825 | ||||
-rw-r--r-- | std/path/glob.ts | 274 | ||||
-rw-r--r-- | std/path/glob_test.ts | 582 |
5 files changed, 698 insertions, 1325 deletions
diff --git a/std/path/_constants.ts b/std/path/_constants.ts index 186c32ab5..920979432 100644 --- a/std/path/_constants.ts +++ b/std/path/_constants.ts @@ -47,14 +47,16 @@ export const CHAR_EQUAL = 61; /* = */ export const CHAR_0 = 48; /* 0 */ export const CHAR_9 = 57; /* 9 */ +let NATIVE_OS: typeof Deno.build.os = "linux"; // eslint-disable-next-line @typescript-eslint/no-explicit-any const navigator = (globalThis as any).navigator; - -let isWindows = false; if (globalThis.Deno != null) { - isWindows = Deno.build.os == "windows"; -} else if (navigator?.appVersion != null) { - isWindows = navigator.appVersion.includes("Win"); + NATIVE_OS = Deno.build.os; +} else if (navigator?.appVersion?.includes?.("Win") ?? false) { + NATIVE_OS = "windows"; } +// TODO(nayeemrmn): Improve OS detection in browsers beyond Windows. + +export const isWindows = NATIVE_OS == "windows"; -export { isWindows }; +export { NATIVE_OS }; diff --git a/std/path/_globrex.ts b/std/path/_globrex.ts deleted file mode 100644 index 0cc5399fa..000000000 --- a/std/path/_globrex.ts +++ /dev/null @@ -1,328 +0,0 @@ -// This file is ported from globrex@0.1.2 -// MIT License -// Copyright (c) 2018 Terkel Gjervig Nielsen -/** This module is browser compatible. */ - -import { isWindows as isWin } from "./_constants.ts"; - -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 equivalent - * 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; -} diff --git a/std/path/_globrex_test.ts b/std/path/_globrex_test.ts deleted file mode 100644 index 19eabc983..000000000 --- a/std/path/_globrex_test.ts +++ /dev/null @@ -1,825 +0,0 @@ -// This file is ported from globrex@0.1.2 -// MIT License -// Copyright (c) 2018 Terkel Gjervig Nielsen -import { assertEquals } from "../testing/asserts.ts"; -import { GlobrexOptions, globrex } from "./_globrex.ts"; - -const isWin = Deno.build.os === "windows"; -const t = { equal: assertEquals, is: assertEquals }; - -function match( - glob: string, - strUnix: string, - strWin?: string | object, - opts: GlobrexOptions = {}, -): boolean { - if (typeof strWin === "object") { - opts = strWin; - strWin = ""; - } - const { regex } = globrex(glob, opts); - const match = (isWin && strWin ? strWin : strUnix).match(regex); - if (match && !regex.flags.includes("g")) { - assertEquals(match.length, 1); - } - return !!match; -} - -Deno.test({ - name: "globrex: standard", - fn(): void { - const res = globrex("*.js"); - t.equal(typeof globrex, "function", "constructor is a typeof function"); - t.equal(res instanceof Object, true, "returns object"); - t.equal(res.regex.toString(), "/^.*\\.js$/", "returns regex object"); - }, -}); - -Deno.test({ - name: "globrex: Standard * matching", - fn(): void { - t.equal(match("*", "foo"), true, "match everything"); - t.equal(match("*", "foo", { flags: "g" }), true, "match everything"); - t.equal(match("f*", "foo"), true, "match the end"); - t.equal(match("f*", "foo", { flags: "g" }), true, "match the end"); - t.equal(match("*o", "foo"), true, "match the start"); - t.equal(match("*o", "foo", { flags: "g" }), true, "match the start"); - t.equal(match("u*orn", "unicorn"), true, "match the middle"); - t.equal( - match("u*orn", "unicorn", { flags: "g" }), - true, - "match the middle", - ); - t.equal(match("ico", "unicorn"), false, "do not match without g"); - t.equal( - match("ico", "unicorn", { flags: "g" }), - true, - 'match anywhere with RegExp "g"', - ); - t.equal(match("u*nicorn", "unicorn"), true, "match zero characters"); - t.equal( - match("u*nicorn", "unicorn", { flags: "g" }), - true, - "match zero characters", - ); - }, -}); - -Deno.test({ - name: "globrex: advance * matching", - fn(): void { - t.equal( - match("*.min.js", "http://example.com/jquery.min.js", { - globstar: false, - }), - true, - "complex match", - ); - t.equal( - match("*.min.*", "http://example.com/jquery.min.js", { globstar: false }), - true, - "complex match", - ); - t.equal( - match("*/js/*.js", "http://example.com/js/jquery.min.js", { - globstar: false, - }), - true, - "complex match", - ); - t.equal( - match("*.min.*", "http://example.com/jquery.min.js", { flags: "g" }), - true, - "complex match global", - ); - t.equal( - match("*.min.js", "http://example.com/jquery.min.js", { flags: "g" }), - true, - "complex match global", - ); - t.equal( - match("*/js/*.js", "http://example.com/js/jquery.min.js", { flags: "g" }), - true, - "complex match global", - ); - - const str = "\\/$^+?.()=!|{},[].*"; - t.equal(match(str, str), true, "battle test complex string - strict"); - t.equal( - match(str, str, { flags: "g" }), - true, - "battle test complex string - strict", - ); - - t.equal( - match(".min.", "http://example.com/jquery.min.js"), - false, - 'matches without/with using RegExp "g"', - ); - t.equal( - match("*.min.*", "http://example.com/jquery.min.js"), - true, - 'matches without/with using RegExp "g"', - ); - t.equal( - match(".min.", "http://example.com/jquery.min.js", { flags: "g" }), - true, - 'matches without/with using RegExp "g"', - ); - t.equal( - match("http:", "http://example.com/jquery.min.js"), - false, - 'matches without/with using RegExp "g"', - ); - t.equal( - match("http:*", "http://example.com/jquery.min.js"), - true, - 'matches without/with using RegExp "g"', - ); - t.equal( - match("http:", "http://example.com/jquery.min.js", { flags: "g" }), - true, - 'matches without/with using RegExp "g"', - ); - t.equal( - match("min.js", "http://example.com/jquery.min.js"), - false, - 'matches without/with using RegExp "g"', - ); - t.equal( - match("*.min.js", "http://example.com/jquery.min.js"), - true, - 'matches without/with using RegExp "g"', - ); - t.equal( - match("min.js", "http://example.com/jquery.min.js", { flags: "g" }), - true, - 'matches without/with using RegExp "g"', - ); - t.equal( - match("min", "http://example.com/jquery.min.js", { flags: "g" }), - true, - 'match anywhere (globally) using RegExp "g"', - ); - t.equal( - match("/js/", "http://example.com/js/jquery.min.js", { flags: "g" }), - true, - 'match anywhere (globally) using RegExp "g"', - ); - t.equal(match("/js*jq*.js", "http://example.com/js/jquery.min.js"), false); - t.equal( - match("/js*jq*.js", "http://example.com/js/jquery.min.js", { - flags: "g", - }), - true, - ); - }, -}); - -Deno.test({ - name: "globrex: ? match one character, no more and no less", - fn(): void { - t.equal(match("f?o", "foo", { extended: true }), true); - t.equal(match("f?o", "fooo", { extended: true }), false); - t.equal(match("f?oo", "foo", { extended: true }), false); - - const tester = (globstar: boolean): void => { - t.equal( - match("f?o", "foo", { extended: true, globstar, flags: "g" }), - true, - ); - t.equal( - match("f?o", "fooo", { extended: true, globstar, flags: "g" }), - true, - ); - t.equal( - match("f?o?", "fooo", { extended: true, globstar, flags: "g" }), - true, - ); - - t.equal( - match("?fo", "fooo", { extended: true, globstar, flags: "g" }), - false, - ); - t.equal( - match("f?oo", "foo", { extended: true, globstar, flags: "g" }), - false, - ); - t.equal( - match("foo?", "foo", { extended: true, globstar, flags: "g" }), - false, - ); - }; - - tester(true); - tester(false); - }, -}); - -Deno.test({ - name: "globrex: [] match a character range", - fn(): void { - t.equal(match("fo[oz]", "foo", { extended: true }), true); - t.equal(match("fo[oz]", "foz", { extended: true }), true); - t.equal(match("fo[oz]", "fog", { extended: true }), false); - t.equal(match("fo[a-z]", "fob", { extended: true }), true); - t.equal(match("fo[a-d]", "fot", { extended: true }), false); - t.equal(match("fo[!tz]", "fot", { extended: true }), false); - t.equal(match("fo[!tz]", "fob", { extended: true }), true); - - const tester = (globstar: boolean): void => { - t.equal( - match("fo[oz]", "foo", { extended: true, globstar, flags: "g" }), - true, - ); - t.equal( - match("fo[oz]", "foz", { extended: true, globstar, flags: "g" }), - true, - ); - t.equal( - match("fo[oz]", "fog", { extended: true, globstar, flags: "g" }), - false, - ); - }; - - tester(true); - tester(false); - }, -}); - -Deno.test({ - name: "globrex: [] extended character ranges", - fn(): void { - t.equal( - match("[[:alnum:]]/bar.txt", "a/bar.txt", { extended: true }), - true, - ); - t.equal( - match("@([[:alnum:]abc]|11)/bar.txt", "11/bar.txt", { extended: true }), - true, - ); - t.equal( - match("@([[:alnum:]abc]|11)/bar.txt", "a/bar.txt", { extended: true }), - true, - ); - t.equal( - match("@([[:alnum:]abc]|11)/bar.txt", "b/bar.txt", { extended: true }), - true, - ); - t.equal( - match("@([[:alnum:]abc]|11)/bar.txt", "c/bar.txt", { extended: true }), - true, - ); - t.equal( - match("@([[:alnum:]abc]|11)/bar.txt", "abc/bar.txt", { extended: true }), - false, - ); - t.equal( - match("@([[:alnum:]abc]|11)/bar.txt", "3/bar.txt", { extended: true }), - true, - ); - t.equal( - match("[[:digit:]]/bar.txt", "1/bar.txt", { extended: true }), - true, - ); - t.equal( - match("[[:digit:]b]/bar.txt", "b/bar.txt", { extended: true }), - true, - ); - t.equal( - match("[![:digit:]b]/bar.txt", "a/bar.txt", { extended: true }), - true, - ); - t.equal( - match("[[:alnum:]]/bar.txt", "!/bar.txt", { extended: true }), - false, - ); - t.equal( - match("[[:digit:]]/bar.txt", "a/bar.txt", { extended: true }), - false, - ); - t.equal( - match("[[:digit:]b]/bar.txt", "a/bar.txt", { extended: true }), - false, - ); - }, -}); - -Deno.test({ - name: "globrex: {} match a choice of different substrings", - fn(): void { - t.equal(match("foo{bar,baaz}", "foobaaz", { extended: true }), true); - t.equal(match("foo{bar,baaz}", "foobar", { extended: true }), true); - t.equal(match("foo{bar,baaz}", "foobuzz", { extended: true }), false); - t.equal(match("foo{bar,b*z}", "foobuzz", { extended: true }), true); - - const tester = (globstar: boolean): void => { - t.equal( - match("foo{bar,baaz}", "foobaaz", { - extended: true, - globstar, - flag: "g", - }), - true, - ); - t.equal( - match("foo{bar,baaz}", "foobar", { - extended: true, - globstar, - flag: "g", - }), - true, - ); - t.equal( - match("foo{bar,baaz}", "foobuzz", { - extended: true, - globstar, - flag: "g", - }), - false, - ); - t.equal( - match("foo{bar,b*z}", "foobuzz", { - extended: true, - globstar, - flag: "g", - }), - true, - ); - }; - - tester(true); - tester(false); - }, -}); - -Deno.test({ - name: "globrex: complex extended matches", - fn(): void { - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://foo.baaz.com/jquery.min.js", - { extended: true }, - ), - true, - ); - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://moz.buzz.com/index.html", - { extended: true }, - ), - true, - ); - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://moz.buzz.com/index.htm", - { extended: true }, - ), - false, - ); - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://moz.bar.com/index.html", - { extended: true }, - ), - false, - ); - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://flozz.buzz.com/index.html", - { extended: true }, - ), - false, - ); - - const tester = (globstar: boolean): void => { - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://foo.baaz.com/jquery.min.js", - { extended: true, globstar, flags: "g" }, - ), - true, - ); - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://moz.buzz.com/index.html", - { extended: true, globstar, flags: "g" }, - ), - true, - ); - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://moz.buzz.com/index.htm", - { extended: true, globstar, flags: "g" }, - ), - false, - ); - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://moz.bar.com/index.html", - { extended: true, globstar, flags: "g" }, - ), - false, - ); - t.equal( - match( - "http://?o[oz].b*z.com/{*.js,*.html}", - "http://flozz.buzz.com/index.html", - { extended: true, globstar, flags: "g" }, - ), - false, - ); - }; - - tester(true); - tester(false); - }, -}); - -Deno.test({ - name: "globrex: standard globstar", - fn(): void { - const tester = (globstar: boolean): void => { - t.equal( - match( - "http://foo.com/**/{*.js,*.html}", - "http://foo.com/bar/jquery.min.js", - { extended: true, globstar, flags: "g" }, - ), - true, - ); - t.equal( - match( - "http://foo.com/**/{*.js,*.html}", - "http://foo.com/bar/baz/jquery.min.js", - { extended: true, globstar, flags: "g" }, - ), - true, - ); - t.equal( - match("http://foo.com/**", "http://foo.com/bar/baz/jquery.min.js", { - extended: true, - globstar, - flags: "g", - }), - true, - ); - }; - - tester(true); - tester(false); - }, -}); - -Deno.test({ - name: "globrex: remaining chars should match themself", - fn(): void { - const tester = (globstar: boolean): void => { - const testExtStr = "\\/$^+.()=!|,.*"; - t.equal(match(testExtStr, testExtStr, { extended: true }), true); - t.equal( - match(testExtStr, testExtStr, { extended: true, globstar, flags: "g" }), - true, - ); - }; - - tester(true); - tester(false); - }, -}); - -Deno.test({ - name: "globrex: globstar advance testing", - fn(): void { - t.equal(match("/foo/*", "/foo/bar.txt", { globstar: true }), true); - t.equal(match("/foo/**", "/foo/bar.txt", { globstar: true }), true); - t.equal(match("/foo/**", "/foo/bar/baz.txt", { globstar: true }), true); - t.equal(match("/foo/**", "/foo/bar/baz.txt", { globstar: true }), true); - t.equal( - match("/foo/*/*.txt", "/foo/bar/baz.txt", { globstar: true }), - true, - ); - t.equal( - match("/foo/**/*.txt", "/foo/bar/baz.txt", { globstar: true }), - true, - ); - t.equal( - match("/foo/**/*.txt", "/foo/bar/baz/qux.txt", { globstar: true }), - true, - ); - t.equal(match("/foo/**/bar.txt", "/foo/bar.txt", { globstar: true }), true); - t.equal( - match("/foo/**/**/bar.txt", "/foo/bar.txt", { globstar: true }), - true, - ); - t.equal( - match("/foo/**/*/baz.txt", "/foo/bar/baz.txt", { globstar: true }), - true, - ); - t.equal(match("/foo/**/*.txt", "/foo/bar.txt", { globstar: true }), true); - t.equal( - match("/foo/**/**/*.txt", "/foo/bar.txt", { globstar: true }), - true, - ); - t.equal( - match("/foo/**/*/*.txt", "/foo/bar/baz.txt", { globstar: true }), - true, - ); - t.equal( - match("**/*.txt", "/foo/bar/baz/qux.txt", { globstar: true }), - true, - ); - t.equal(match("**/foo.txt", "foo.txt", { globstar: true }), true); - t.equal(match("**/*.txt", "foo.txt", { globstar: true }), true); - t.equal(match("/foo/*", "/foo/bar/baz.txt", { globstar: true }), false); - t.equal(match("/foo/*.txt", "/foo/bar/baz.txt", { globstar: true }), false); - t.equal( - match("/foo/*/*.txt", "/foo/bar/baz/qux.txt", { globstar: true }), - false, - ); - t.equal(match("/foo/*/bar.txt", "/foo/bar.txt", { globstar: true }), false); - t.equal( - match("/foo/*/*/baz.txt", "/foo/bar/baz.txt", { globstar: true }), - false, - ); - t.equal( - match("/foo/**.txt", "/foo/bar/baz/qux.txt", { globstar: true }), - false, - ); - t.equal( - match("/foo/bar**/*.txt", "/foo/bar/baz/qux.txt", { globstar: true }), - false, - ); - t.equal(match("/foo/bar**", "/foo/bar/baz.txt", { globstar: true }), false); - t.equal( - match("**/.txt", "/foo/bar/baz/qux.txt", { globstar: true }), - false, - ); - t.equal( - match("*/*.txt", "/foo/bar/baz/qux.txt", { globstar: true }), - false, - ); - t.equal(match("*/*.txt", "foo.txt", { globstar: true }), false); - t.equal( - match("http://foo.com/*", "http://foo.com/bar/baz/jquery.min.js", { - extended: true, - globstar: true, - }), - false, - ); - t.equal( - match("http://foo.com/*", "http://foo.com/bar/baz/jquery.min.js", { - globstar: true, - }), - false, - ); - t.equal( - match("http://foo.com/*", "http://foo.com/bar/baz/jquery.min.js", { - globstar: false, - }), - true, - ); - t.equal( - match("http://foo.com/**", "http://foo.com/bar/baz/jquery.min.js", { - globstar: true, - }), - true, - ); - t.equal( - match( - "http://foo.com/*/*/jquery.min.js", - "http://foo.com/bar/baz/jquery.min.js", - { globstar: true }, - ), - true, - ); - t.equal( - match( - "http://foo.com/**/jquery.min.js", - "http://foo.com/bar/baz/jquery.min.js", - { globstar: true }, - ), - true, - ); - t.equal( - match( - "http://foo.com/*/*/jquery.min.js", - "http://foo.com/bar/baz/jquery.min.js", - { globstar: false }, - ), - true, - ); - t.equal( - match( - "http://foo.com/*/jquery.min.js", - "http://foo.com/bar/baz/jquery.min.js", - { globstar: false }, - ), - true, - ); - t.equal( - match( - "http://foo.com/*/jquery.min.js", - "http://foo.com/bar/baz/jquery.min.js", - { globstar: true }, - ), - false, - ); - }, -}); - -Deno.test({ - name: "globrex: extended extglob ?", - fn(): void { - t.equal(match("(foo).txt", "(foo).txt", { extended: true }), true); - t.equal(match("?(foo).txt", "foo.txt", { extended: true }), true); - t.equal(match("?(foo).txt", ".txt", { extended: true }), true); - t.equal(match("?(foo|bar)baz.txt", "foobaz.txt", { extended: true }), true); - t.equal( - match("?(ba[zr]|qux)baz.txt", "bazbaz.txt", { extended: true }), - true, - ); - t.equal( - match("?(ba[zr]|qux)baz.txt", "barbaz.txt", { extended: true }), - true, - ); - t.equal( - match("?(ba[zr]|qux)baz.txt", "quxbaz.txt", { extended: true }), - true, - ); - t.equal( - match("?(ba[!zr]|qux)baz.txt", "batbaz.txt", { extended: true }), - true, - ); - t.equal(match("?(ba*|qux)baz.txt", "batbaz.txt", { extended: true }), true); - t.equal( - match("?(ba*|qux)baz.txt", "batttbaz.txt", { extended: true }), - true, - ); - t.equal(match("?(ba*|qux)baz.txt", "quxbaz.txt", { extended: true }), true); - t.equal( - match("?(ba?(z|r)|qux)baz.txt", "bazbaz.txt", { extended: true }), - true, - ); - t.equal( - match("?(ba?(z|?(r))|qux)baz.txt", "bazbaz.txt", { extended: true }), - true, - ); - t.equal(match("?(foo).txt", "foo.txt", { extended: false }), false); - t.equal( - match("?(foo|bar)baz.txt", "foobarbaz.txt", { extended: true }), - false, - ); - t.equal( - match("?(ba[zr]|qux)baz.txt", "bazquxbaz.txt", { extended: true }), - false, - ); - t.equal( - match("?(ba[!zr]|qux)baz.txt", "bazbaz.txt", { extended: true }), - false, - ); - }, -}); - -Deno.test({ - name: "globrex: extended extglob *", - fn(): void { - t.equal(match("*(foo).txt", "foo.txt", { extended: true }), true); - t.equal(match("*foo.txt", "bofoo.txt", { extended: true }), true); - t.equal(match("*(foo).txt", "foofoo.txt", { extended: true }), true); - t.equal(match("*(foo).txt", ".txt", { extended: true }), true); - t.equal(match("*(fooo).txt", ".txt", { extended: true }), true); - t.equal(match("*(fooo).txt", "foo.txt", { extended: true }), false); - t.equal(match("*(foo|bar).txt", "foobar.txt", { extended: true }), true); - t.equal(match("*(foo|bar).txt", "barbar.txt", { extended: true }), true); - t.equal(match("*(foo|bar).txt", "barfoobar.txt", { extended: true }), true); - t.equal(match("*(foo|bar).txt", ".txt", { extended: true }), true); - t.equal(match("*(foo|ba[rt]).txt", "bat.txt", { extended: true }), true); - t.equal(match("*(foo|b*[rt]).txt", "blat.txt", { extended: true }), true); - t.equal(match("*(foo|b*[rt]).txt", "tlat.txt", { extended: true }), false); - t.equal( - match("*(*).txt", "whatever.txt", { extended: true, globstar: true }), - true, - ); - t.equal( - match("*(foo|bar)/**/*.txt", "foo/hello/world/bar.txt", { - extended: true, - globstar: true, - }), - true, - ); - t.equal( - match("*(foo|bar)/**/*.txt", "foo/world/bar.txt", { - extended: true, - globstar: true, - }), - true, - ); - }, -}); - -Deno.test({ - name: "globrex: extended extglob +", - fn(): void { - t.equal(match("+(foo).txt", "foo.txt", { extended: true }), true); - t.equal(match("+foo.txt", "+foo.txt", { extended: true }), true); - t.equal(match("+(foo).txt", ".txt", { extended: true }), false); - t.equal(match("+(foo|bar).txt", "foobar.txt", { extended: true }), true); - }, -}); - -Deno.test({ - name: "globrex: extended extglob @", - fn(): void { - t.equal(match("@(foo).txt", "foo.txt", { extended: true }), true); - t.equal(match("@foo.txt", "@foo.txt", { extended: true }), true); - t.equal(match("@(foo|baz)bar.txt", "foobar.txt", { extended: true }), true); - t.equal( - match("@(foo|baz)bar.txt", "foobazbar.txt", { extended: true }), - false, - ); - t.equal( - match("@(foo|baz)bar.txt", "foofoobar.txt", { extended: true }), - false, - ); - t.equal( - match("@(foo|baz)bar.txt", "toofoobar.txt", { extended: true }), - false, - ); - }, -}); - -Deno.test({ - name: "globrex: extended extglob !", - fn(): void { - t.equal(match("!(boo).txt", "foo.txt", { extended: true }), true); - t.equal(match("!(foo|baz)bar.txt", "buzbar.txt", { extended: true }), true); - t.equal(match("!bar.txt", "!bar.txt", { extended: true }), true); - t.equal( - match("!({foo,bar})baz.txt", "notbaz.txt", { extended: true }), - true, - ); - t.equal( - match("!({foo,bar})baz.txt", "foobaz.txt", { extended: true }), - false, - ); - }, -}); - -Deno.test({ - name: "globrex: strict", - fn(): void { - t.equal(match("foo//bar.txt", "foo/bar.txt"), true); - t.equal(match("foo///bar.txt", "foo/bar.txt"), true); - t.equal(match("foo///bar.txt", "foo/bar.txt", { strict: true }), false); - }, -}); - -Deno.test({ - name: "globrex: stress testing", - fn(): void { - t.equal( - match("**/*/?yfile.{md,js,txt}", "foo/bar/baz/myfile.md", { - extended: true, - }), - true, - ); - t.equal( - match("**/*/?yfile.{md,js,txt}", "foo/baz/myfile.md", { extended: true }), - true, - ); - t.equal( - match("**/*/?yfile.{md,js,txt}", "foo/baz/tyfile.js", { extended: true }), - true, - ); - t.equal( - match("[[:digit:]_.]/file.js", "1/file.js", { extended: true }), - true, - ); - t.equal( - match("[[:digit:]_.]/file.js", "2/file.js", { extended: true }), - true, - ); - t.equal( - match("[[:digit:]_.]/file.js", "_/file.js", { extended: true }), - true, - ); - t.equal( - match("[[:digit:]_.]/file.js", "./file.js", { extended: true }), - true, - ); - t.equal( - match("[[:digit:]_.]/file.js", "z/file.js", { extended: true }), - false, - ); - }, -}); diff --git a/std/path/glob.ts b/std/path/glob.ts index 172587626..2fff76f34 100644 --- a/std/path/glob.ts +++ b/std/path/glob.ts @@ -1,56 +1,254 @@ +// globToRegExp() is originall ported from globrex@0.1.2. +// Copyright 2018 Terkel Gjervig Nielsen. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -/** This module is browser compatible. */ +// This module is browser compatible. -import { SEP, SEP_PATTERN } from "./separator.ts"; -import { globrex } from "./_globrex.ts"; +import { NATIVE_OS } from "./_constants.ts"; import { join, normalize } from "./mod.ts"; -import { assert } from "../_util/assert.ts"; +import { SEP, SEP_PATTERN } from "./separator.ts"; export interface GlobOptions { + /** Extended glob syntax. + * See https://www.linuxjournal.com/content/bash-extended-globbing. Defaults + * to true. */ extended?: boolean; + /** Globstar syntax. + * See https://www.linuxjournal.com/content/globstar-new-bash-globbing-option. + * If false, `**` is treated like `*`. Defaults to true. */ globstar?: boolean; + /** Operating system. Defaults to the native OS. */ + os?: typeof Deno.build.os; } -export interface GlobToRegExpOptions extends GlobOptions { - flags?: string; -} +export type GlobToRegExpOptions = GlobOptions; -/** - * Generate a regex based on glob pattern and options - * This was meant to be using the the `fs.walk` function - * but can be used anywhere else. - * Examples: - * - * Looking for all the `ts` files: - * walkSync(".", { - * match: [globToRegExp("*.ts")] - * }) +/** Convert a glob string to a regular expressions. * - * Looking for all the `.json` files in any subfolder: - * walkSync(".", { - * match: [globToRegExp(join("a", "**", "*.json"),{ - * flags: "g", - * extended: true, - * globstar: true - * })] - * }) + * // Looking for all the `ts` files: + * walkSync(".", { + * match: [globToRegExp("*.ts")] + * }); * - * @param glob - Glob pattern to be used - * @param options - Specific options for the glob pattern - * @returns A RegExp for the glob pattern - */ + * Looking for all the `.json` files in any subfolder: + * walkSync(".", { + * match: [globToRegExp(join("a", "**", "*.json"), { + * extended: true, + * globstar: true + * })] + * }); */ export function globToRegExp( glob: string, - { extended = false, globstar = true }: GlobToRegExpOptions = {}, + { extended = true, globstar: globstarOption = true, os = NATIVE_OS }: + GlobToRegExpOptions = {}, ): RegExp { - const result = globrex(glob, { - extended, - globstar, - strict: false, - filepath: true, - }); - assert(result.path != null); - return result.path.regex; + const sep = os == "windows" ? `(?:\\\\|\\/)+` : `\\/+`; + const sepMaybe = os == "windows" ? `(?:\\\\|\\/)*` : `\\/*`; + const seps = os == "windows" ? ["\\", "/"] : ["/"]; + const sepRaw = os == "windows" ? `\\` : `/`; + const globstar = os == "windows" + ? `(?:[^\\\\/]*(?:\\\\|\\/|$)+)*` + : `(?:[^/]*(?:\\/|$)+)*`; + const wildcard = os == "windows" ? `[^\\\\/]*` : `[^/]*`; + + // Keep track of scope for extended syntaxes. + const extStack = []; + + // 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; + + let regExpString = ""; + + // Remove trailing separators. + 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++; // skip [ + 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++; // skip last ] + 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; + } + // 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]; + const isGlobstar = globstarOption && starCount > 1 && + // from the start of the segment + [sepRaw, "/", undefined].includes(prevChar) && + // to the end of the segment + [sepRaw, "/", undefined].includes(nextChar); + if (isGlobstar) { + // it's a globstar, so match zero or more path segments + regExpString += globstar; + while (seps.includes(glob[i + 1])) i++; + } else { + // it's not a globstar, so only match one path segment + regExpString += wildcard; + } + continue; + } + + regExpString += c; + } + + regExpString = `^${regExpString}${regExpString != "" ? sepMaybe : ""}$`; + return new RegExp(regExpString); } /** Test whether the given string is a glob */ diff --git a/std/path/glob_test.ts b/std/path/glob_test.ts index dba79c302..bb3f8add0 100644 --- a/std/path/glob_test.ts +++ b/std/path/glob_test.ts @@ -1,130 +1,456 @@ import { assert, assertEquals } from "../testing/asserts.ts"; -import { testWalk, touch, walkArray } from "../fs/walk_test.ts"; -import { globToRegExp, isGlob, joinGlobs, normalizeGlob } from "./glob.ts"; -import { SEP, join } from "./mod.ts"; - -Deno.test({ - name: "glob: glob to regex", - fn(): void { - assertEquals(globToRegExp("unicorn.*") instanceof RegExp, true); - assertEquals(globToRegExp("unicorn.*").test("poney.ts"), false); - assertEquals(globToRegExp("unicorn.*").test("unicorn.py"), true); - assertEquals(globToRegExp("*.ts").test("poney.ts"), true); - assertEquals(globToRegExp("*.ts").test("unicorn.js"), false); - assertEquals( - globToRegExp(join("unicorn", "**", "cathedral.ts")).test( - join("unicorn", "in", "the", "cathedral.ts"), - ), - true, - ); - assertEquals( - globToRegExp(join("unicorn", "**", "cathedral.ts")).test( - join("unicorn", "in", "the", "kitchen.ts"), - ), - false, - ); - assertEquals( - globToRegExp(join("unicorn", "**", "bathroom.*")).test( - join("unicorn", "sleeping", "in", "bathroom.py"), - ), - true, - ); - assertEquals( - globToRegExp(join("unicorn", "!(sleeping)", "bathroom.ts"), { - extended: true, - }).test(join("unicorn", "flying", "bathroom.ts")), - true, - ); - assertEquals( - globToRegExp(join("unicorn", "(!sleeping)", "bathroom.ts"), { - extended: true, - }).test(join("unicorn", "sleeping", "bathroom.ts")), - false, - ); - }, -}); - -testWalk( - async (d: string): Promise<void> => { - await Deno.mkdir(d + "/a"); - await Deno.mkdir(d + "/b"); - await touch(d + "/a/x.ts"); - await touch(d + "/b/z.ts"); - await touch(d + "/b/z.js"); - }, - async function globInWalkWildcard(): Promise<void> { - const arr = await walkArray(".", { - match: [globToRegExp(join("*", "*.ts"))], - }); - assertEquals(arr.length, 2); - assertEquals(arr[0], "a/x.ts"); - assertEquals(arr[1], "b/z.ts"); - }, -); - -testWalk( - async (d: string): Promise<void> => { - await Deno.mkdir(d + "/a"); - await Deno.mkdir(d + "/a/yo"); - await touch(d + "/a/yo/x.ts"); - }, - async function globInWalkFolderWildcard(): Promise<void> { - const arr = await walkArray(".", { - match: [ - globToRegExp(join("a", "**", "*.ts"), { - flags: "g", - globstar: true, - }), - ], - }); - assertEquals(arr.length, 1); - assertEquals(arr[0], "a/yo/x.ts"); - }, -); - -testWalk( - async (d: string): Promise<void> => { - await Deno.mkdir(d + "/a"); - await Deno.mkdir(d + "/a/unicorn"); - await Deno.mkdir(d + "/a/deno"); - await Deno.mkdir(d + "/a/raptor"); - await touch(d + "/a/raptor/x.ts"); - await touch(d + "/a/deno/x.ts"); - await touch(d + "/a/unicorn/x.ts"); - }, - async function globInWalkFolderExtended(): Promise<void> { - const arr = await walkArray(".", { - match: [ - globToRegExp(join("a", "+(raptor|deno)", "*.ts"), { - flags: "g", - extended: true, - }), - ], - }); - assertEquals(arr.length, 2); - assertEquals(arr[0], "a/deno/x.ts"); - assertEquals(arr[1], "a/raptor/x.ts"); - }, -); - -testWalk( - async (d: string): Promise<void> => { - await touch(d + "/x.ts"); - await touch(d + "/x.js"); - await touch(d + "/b.js"); - }, - async function globInWalkWildcardExtension(): Promise<void> { - const arr = await walkArray(".", { - match: [globToRegExp("x.*", { flags: "g", globstar: true })], - }); - assertEquals(arr.length, 2); - assertEquals(arr[0], "x.js"); - assertEquals(arr[1], "x.ts"); - }, -); - -Deno.test({ - name: "isGlob: pattern to test", +import { + GlobToRegExpOptions, + globToRegExp, + isGlob, + joinGlobs, + normalizeGlob, +} from "./glob.ts"; +import { SEP } from "./mod.ts"; + +function match( + glob: string, + path: string, + opts: GlobToRegExpOptions = {}, +): boolean { + if (opts.os == null) { + const matchDarwin = path.match( + globToRegExp(glob, { ...opts, os: "darwin" }), + ); + if (matchDarwin) { + assertEquals(matchDarwin.length, 1); + } + const matchLinux = path.match(globToRegExp(glob, { ...opts, os: "linux" })); + if (matchLinux) { + assertEquals(matchLinux.length, 1); + } + const matchWindows = path.match( + globToRegExp(glob, { ...opts, os: "windows" }), + ); + if (matchWindows) { + assertEquals(matchWindows.length, 1); + } + return !!matchDarwin && !!matchLinux && !!matchWindows; + } else { + const match = path.match(globToRegExp(glob, opts)); + if (match) { + assertEquals(match.length, 1); + } + return !!match; + } +} + +Deno.test({ + name: "[path] globToRegExp() Basic RegExp", + fn(): void { + assertEquals(globToRegExp(""), /^$/); + assertEquals(globToRegExp("*.js", { os: "linux" }), /^[^/]*\.js\/*$/); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() * (wildcard)", + fn(): void { + assert(match("*", "foo", { extended: false, globstar: false })); + assert(match("*", "foo", { extended: false, globstar: false })); + assert(match("f*", "foo", { extended: false, globstar: false })); + assert(match("f*", "foo", { extended: false, globstar: false })); + assert(match("*o", "foo", { extended: false, globstar: false })); + assert(match("*o", "foo", { extended: false, globstar: false })); + assert(match("u*orn", "unicorn", { extended: false, globstar: false })); + assert(match("u*orn", "unicorn", { extended: false, globstar: false })); + assert(!match("ico", "unicorn", { extended: false, globstar: false })); + assert(match("u*nicorn", "unicorn", { extended: false, globstar: false })); + assert(match("u*nicorn", "unicorn", { extended: false, globstar: false })); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() ? (match one character)", + fn(): void { + assert(match("f?o", "foo", { extended: false, globstar: false })); + assert(match("f?o?", "fooo", { extended: false, globstar: false })); + assert(!match("f?oo", "foo", { extended: false, globstar: false })); + assert(!match("?fo", "fooo", { extended: false, globstar: false })); + assert(!match("f?oo", "foo", { extended: false, globstar: false })); + assert(!match("foo?", "foo", { extended: false, globstar: false })); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() [seq] (character range)", + fn(): void { + assert(match("fo[oz]", "foo", { extended: false, globstar: false })); + assert(match("fo[oz]", "foz", { extended: false, globstar: false })); + assert(!match("fo[oz]", "fog", { extended: false, globstar: false })); + assert(match("fo[a-z]", "fob", { extended: false, globstar: false })); + assert(!match("fo[a-d]", "fot", { extended: false, globstar: false })); + assert(!match("fo[!tz]", "fot", { extended: false, globstar: false })); + assert(match("fo[!tz]", "fob", { extended: false, globstar: false })); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() [[:alnum:]] (character class in range)", + fn(): void { + assert( + match( + "[[:alnum:]]/bar.txt", + "a/bar.txt", + { extended: false, globstar: false }, + ), + ); + assert( + match( + "[[:alnum:]abc]/bar.txt", + "1/bar.txt", + { extended: false, globstar: false }, + ), + ); + assert( + match( + "[[:digit:]]/bar.txt", + "1/bar.txt", + { extended: false, globstar: false }, + ), + ); + assert( + match( + "[[:digit:]b]/bar.txt", + "b/bar.txt", + { extended: false, globstar: false }, + ), + ); + assert( + match( + "[![:digit:]b]/bar.txt", + "a/bar.txt", + { extended: false, globstar: false }, + ), + ); + assert( + !match( + "[[:alnum:]]/bar.txt", + "!/bar.txt", + { extended: false, globstar: false }, + ), + ); + assert( + !match( + "[[:digit:]]/bar.txt", + "a/bar.txt", + { extended: false, globstar: false }, + ), + ); + assert( + !match( + "[[:digit:]b]/bar.txt", + "a/bar.txt", + { extended: false, globstar: false }, + ), + ); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() {} (brace expansion)", + fn(): void { + assert( + match("foo{bar,baaz}", "foobaaz", { extended: false, globstar: false }), + ); + assert( + match("foo{bar,baaz}", "foobar", { extended: false, globstar: false }), + ); + assert( + !match("foo{bar,baaz}", "foobuzz", { extended: false, globstar: false }), + ); + assert( + match("foo{bar,b*z}", "foobuzz", { extended: false, globstar: false }), + ); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() Complex matches", + fn(): void { + assert( + match( + "http://?o[oz].b*z.com/{*.js,*.html}", + "http://foo.baaz.com/jquery.min.js", + { extended: false, globstar: false }, + ), + ); + assert( + match( + "http://?o[oz].b*z.com/{*.js,*.html}", + "http://moz.buzz.com/index.html", + { extended: false, globstar: false }, + ), + ); + assert( + !match( + "http://?o[oz].b*z.com/{*.js,*.html}", + "http://moz.buzz.com/index.htm", + { extended: false, globstar: false }, + ), + ); + assert( + !match( + "http://?o[oz].b*z.com/{*.js,*.html}", + "http://moz.bar.com/index.html", + { extended: false, globstar: false }, + ), + ); + assert( + !match( + "http://?o[oz].b*z.com/{*.js,*.html}", + "http://flozz.buzz.com/index.html", + { extended: false, globstar: false }, + ), + ); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() ** (globstar)", + fn(): void { + assert(match("/foo/**", "/foo/bar.txt")); + assert(match("/foo/**", "/foo/bar/baz.txt")); + assert(!match("/foo/**", "/foo/bar/baz.txt", { globstar: false })); + assert(match("/foo/**", "/foo/bar", { globstar: false })); + assert(match("/foo/**/*.txt", "/foo/bar/baz.txt")); + assert(match("/foo/**/*.txt", "/foo/bar/baz/qux.txt")); + assert(match("/foo/**/bar.txt", "/foo/bar.txt")); + assert(match("/foo/**/**/bar.txt", "/foo/bar.txt")); + assert(match("/foo/**/*/baz.txt", "/foo/bar/baz.txt")); + assert(match("/foo/**/*.txt", "/foo/bar.txt")); + assert(match("/foo/**/**/*.txt", "/foo/bar.txt")); + assert(match("/foo/**/*/*.txt", "/foo/bar/baz.txt")); + assert(match("**/*.txt", "/foo/bar/baz/qux.txt")); + assert(match("**/foo.txt", "foo.txt")); + assert(match("**/*.txt", "foo.txt")); + assert(!match("/foo/**.txt", "/foo/bar/baz/qux.txt")); + assert( + !match("/foo/bar**/*.txt", "/foo/bar/baz/qux.txt"), + ); + assert(!match("/foo/bar**", "/foo/bar/baz.txt")); + assert(!match("**/.txt", "/foo/bar/baz/qux.txt")); + assert( + !match( + "http://foo.com/*", + "http://foo.com/bar/baz/jquery.min.js", + ), + ); + assert( + !match("http://foo.com/*", "http://foo.com/bar/baz/jquery.min.js"), + ); + assert( + match("http://foo.com/**", "http://foo.com/bar/baz/jquery.min.js"), + ); + assert( + match( + "http://foo.com/**/jquery.min.js", + "http://foo.com/bar/baz/jquery.min.js", + ), + ); + assert( + !match( + "http://foo.com/*/jquery.min.js", + "http://foo.com/bar/baz/jquery.min.js", + ), + ); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() ?(pattern-list) (extended: match zero or one)", + fn(): void { + assert(match("?(foo).txt", "foo.txt")); + assert(!match("?(foo).txt", "foo.txt", { extended: false })); + assert(match("?(foo).txt", "a(foo).txt", { extended: false })); + assert(match("?(foo).txt", ".txt")); + assert(match("?(foo|bar)baz.txt", "foobaz.txt")); + assert(match("?(ba[zr]|qux)baz.txt", "bazbaz.txt")); + assert(match("?(ba[zr]|qux)baz.txt", "barbaz.txt")); + assert(match("?(ba[zr]|qux)baz.txt", "quxbaz.txt")); + assert(match("?(ba[!zr]|qux)baz.txt", "batbaz.txt")); + assert(match("?(ba*|qux)baz.txt", "batbaz.txt")); + assert(match("?(ba*|qux)baz.txt", "batttbaz.txt")); + assert(match("?(ba*|qux)baz.txt", "quxbaz.txt")); + assert(match("?(ba?(z|r)|qux)baz.txt", "bazbaz.txt")); + assert(match("?(ba?(z|?(r))|qux)baz.txt", "bazbaz.txt")); + assert(!match("?(foo|bar)baz.txt", "foobarbaz.txt")); + assert(!match("?(ba[zr]|qux)baz.txt", "bazquxbaz.txt")); + assert(!match("?(ba[!zr]|qux)baz.txt", "bazbaz.txt")); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() *(pattern-list) (extended: match zero or more)", + fn(): void { + assert(match("*(foo).txt", "foo.txt")); + assert(!match("*(foo).txt", "foo.txt", { extended: false })); + assert(match("*(foo).txt", "bar(foo).txt", { extended: false })); + assert(match("*(foo).txt", "foofoo.txt")); + assert(match("*(foo).txt", ".txt")); + assert(match("*(fooo).txt", ".txt")); + assert(!match("*(fooo).txt", "foo.txt")); + assert(match("*(foo|bar).txt", "foobar.txt")); + assert(match("*(foo|bar).txt", "barbar.txt")); + assert(match("*(foo|bar).txt", "barfoobar.txt")); + assert(match("*(foo|bar).txt", ".txt")); + assert(match("*(foo|ba[rt]).txt", "bat.txt")); + assert(match("*(foo|b*[rt]).txt", "blat.txt")); + assert(!match("*(foo|b*[rt]).txt", "tlat.txt")); + assert(match("*(*).txt", "whatever.txt")); + assert(match("*(foo|bar)/**/*.txt", "foo/hello/world/bar.txt")); + assert(match("*(foo|bar)/**/*.txt", "foo/world/bar.txt")); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() +(pattern-list) (extended: match 1 or more)", + fn(): void { + assert(match("+(foo).txt", "foo.txt")); + assert(!match("+(foo).txt", "foo.txt", { extended: false })); + assert(match("+(foo).txt", "+(foo).txt", { extended: false })); + assert(!match("+(foo).txt", ".txt")); + assert(match("+(foo|bar).txt", "foobar.txt")); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() @(pattern-list) (extended: match one)", + fn(): void { + assert(match("@(foo).txt", "foo.txt")); + assert(!match("@(foo).txt", "foo.txt", { extended: false })); + assert(match("@(foo).txt", "@(foo).txt", { extended: false })); + assert(match("@(foo|baz)bar.txt", "foobar.txt")); + assert(!match("@(foo|baz)bar.txt", "foobazbar.txt")); + assert(!match("@(foo|baz)bar.txt", "foofoobar.txt")); + assert(!match("@(foo|baz)bar.txt", "toofoobar.txt")); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() !(pattern-list) (extended: match any except)", + fn(): void { + assert(match("!(boo).txt", "foo.txt")); + assert(!match("!(boo).txt", "foo.txt", { extended: false })); + assert(match("!(boo).txt", "!(boo).txt", { extended: false })); + assert(match("!(foo|baz)bar.txt", "buzbar.txt")); + assert(match("!({foo,bar})baz.txt", "notbaz.txt")); + assert(!match("!({foo,bar})baz.txt", "foobaz.txt")); + }, +}); + +Deno.test({ + name: + "[path] globToRegExp() Special extended characters should match themselves", + fn(): void { + const glob = "\\/$^+.()=!|,.*"; + assert(match(glob, glob)); + assert(match(glob, glob, { extended: false })); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() Special extended characters in range", + fn(): void { + assertEquals(globToRegExp("[?*+@!|]", { os: "linux" }), /^[?*+@!|]\/*$/); + assertEquals(globToRegExp("[!?*+@!|]", { os: "linux" }), /^[^?*+@!|]\/*$/); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() Special RegExp characters in range", + fn(): void { + // Excluding characters checked in the previous test. + assertEquals(globToRegExp("[\\$^.=]", { os: "linux" }), /^[\\$^.=]\/*$/); + assertEquals(globToRegExp("[!\\$^.=]", { os: "linux" }), /^[^\\$^.=]\/*$/); + assertEquals(globToRegExp("[^^]", { os: "linux" }), /^[\^^]\/*$/); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() Repeating separators", + fn() { + assert(match("foo/bar", "foo//bar")); + assert(match("foo//bar", "foo/bar")); + assert(match("foo//bar", "foo//bar")); + assert(match("**/bar", "foo//bar")); + assert(match("**//bar", "foo/bar")); + assert(match("**//bar", "foo//bar")); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() Trailing separators", + fn() { + assert(match("foo", "foo/")); + assert(match("foo/", "foo")); + assert(match("foo/", "foo/")); + assert(match("**", "foo/")); + assert(match("**/", "foo")); + assert(match("**/", "foo/")); + }, +}); + +Deno.test({ + name: "[path] globToRegExp() Backslashes on Windows", + fn() { + assert(match("foo/bar", "foo\\bar", { os: "windows" })); + assert(match("foo\\bar", "foo/bar", { os: "windows" })); + assert(match("foo\\bar", "foo\\bar", { os: "windows" })); + assert(match("**/bar", "foo\\bar", { os: "windows" })); + assert(match("**\\bar", "foo/bar", { os: "windows" })); + assert(match("**\\bar", "foo\\bar", { os: "windows" })); + }, +}); + +Deno.test({ + name: "[path] GlobToRegExpOptions::extended", + fn() { + const pattern1 = globToRegExp("?(foo|bar)"); + assertEquals("foo".match(pattern1)?.[0], "foo"); + assertEquals("bar".match(pattern1)?.[0], "bar"); + + const pattern2 = globToRegExp("?(foo|bar)", { extended: false }); + assertEquals("foo".match(pattern2)?.[0], undefined); + assertEquals("bar".match(pattern2)?.[0], undefined); + assertEquals("?(foo|bar)".match(pattern2)?.[0], "?(foo|bar)"); + }, +}); + +Deno.test({ + name: "[path] GlobToRegExpOptions::globstar", + fn() { + const pattern1 = globToRegExp("**/foo"); + assertEquals("foo".match(pattern1)?.[0], "foo"); + assertEquals("path/to/foo".match(pattern1)?.[0], "path/to/foo"); + + const pattern2 = globToRegExp("**/foo", { globstar: false }); + assertEquals("foo".match(pattern2)?.[0], undefined); + assertEquals("path/to/foo".match(pattern2)?.[0], undefined); + assertEquals("path-to/foo".match(pattern2)?.[0], "path-to/foo"); + }, +}); + +Deno.test({ + name: "[path] GlobToRegExpOptions::os", + fn() { + const pattern1 = globToRegExp("foo/bar", { os: "linux" }); + assertEquals("foo/bar".match(pattern1)?.[0], "foo/bar"); + assertEquals("foo\\bar".match(pattern1)?.[0], undefined); + + const pattern2 = globToRegExp("foo/bar", { os: "windows" }); + assertEquals("foo/bar".match(pattern2)?.[0], "foo/bar"); + assertEquals("foo\\bar".match(pattern2)?.[0], "foo\\bar"); + }, +}); + +Deno.test({ + name: "[path] isGlob()", fn(): void { // should be true if valid glob pattern assert(isGlob("!foo.js")); @@ -238,10 +564,10 @@ Deno.test({ }, }); -Deno.test("normalizeGlobGlobstar", function (): void { +Deno.test("[path] normalizeGlob() Globstar", function (): void { assertEquals(normalizeGlob(`**${SEP}..`, { globstar: true }), `**${SEP}..`); }); -Deno.test("joinGlobsGlobstar", function (): void { +Deno.test("[path] joinGlobs() Globstar", function (): void { assertEquals(joinGlobs(["**", ".."], { globstar: true }), `**${SEP}..`); }); |