diff options
Diffstat (limited to 'fs')
-rw-r--r-- | fs/glob.ts | 67 | ||||
-rw-r--r-- | fs/glob_test.ts | 88 | ||||
-rw-r--r-- | fs/globrex.ts | 46 | ||||
-rw-r--r-- | fs/path/constants.ts | 1 | ||||
-rw-r--r-- | fs/testdata/glob/abc | 0 | ||||
-rw-r--r-- | fs/testdata/glob/abcdef | 0 | ||||
-rw-r--r-- | fs/testdata/glob/abcdefghi | 0 | ||||
-rw-r--r-- | fs/testdata/glob/subdir/abc | 0 |
8 files changed, 173 insertions, 29 deletions
diff --git a/fs/glob.ts b/fs/glob.ts index 506d4012c..b974f2b29 100644 --- a/fs/glob.ts +++ b/fs/glob.ts @@ -1,4 +1,7 @@ import { globrex } from "./globrex.ts"; +import { isAbsolute, join } from "./path/mod.ts"; +import { WalkInfo, walk, walkSync } from "./walk.ts"; +const { cwd } = Deno; export interface GlobOptions { // Allow ExtGlob features @@ -41,7 +44,11 @@ export interface GlobOptions { * @returns A RegExp for the glob pattern */ export function glob(glob: string, options: GlobOptions = {}): RegExp { - return globrex(glob, options).regex; + const result = globrex(glob, options); + if (options.filepath) { + return result.path!.regex; + } + return result.regex; } /** Test whether the given string is a glob */ @@ -76,3 +83,61 @@ export function isGlob(str: string): boolean { return false; } + +export interface ExpandGlobOptions extends GlobOptions { + root?: string; + includeDirs?: boolean; +} + +/** + * Expand the glob string from the specified `root` directory and yield each + * result as a `WalkInfo` object. + */ +// TODO: Use a proper glob expansion algorithm. +// This is a very incomplete solution. The whole directory tree from `root` is +// walked and parent paths are not supported. +export async function* expandGlob( + globString: string, + { + root = cwd(), + includeDirs = true, + extended = false, + globstar = false, + strict = false, + filepath = true, + flags = "" + }: ExpandGlobOptions = {} +): AsyncIterableIterator<WalkInfo> { + const absoluteGlob = isAbsolute(globString) + ? globString + : join(root, globString); + const globOptions = { extended, globstar, strict, filepath, flags }; + yield* walk(root, { + match: [glob(absoluteGlob, globOptions)], + includeDirs + }); +} + +/** Synchronous version of `expandGlob()`. */ +// TODO: As `expandGlob()`. +export function* expandGlobSync( + globString: string, + { + root = cwd(), + includeDirs = true, + extended = false, + globstar = false, + strict = false, + filepath = true, + flags = "" + }: ExpandGlobOptions = {} +): IterableIterator<WalkInfo> { + const absoluteGlob = isAbsolute(globString) + ? globString + : join(root, globString); + const globOptions = { extended, globstar, strict, filepath, flags }; + yield* walkSync(root, { + match: [glob(absoluteGlob, globOptions)], + includeDirs + }); +} diff --git a/fs/glob_test.ts b/fs/glob_test.ts index 9151b9e9e..0d180eafd 100644 --- a/fs/glob_test.ts +++ b/fs/glob_test.ts @@ -1,9 +1,16 @@ -const { mkdir } = Deno; -type FileInfo = Deno.FileInfo; +const { cwd, mkdir } = Deno; import { test, runIfMain } from "../testing/mod.ts"; import { assert, assertEquals } from "../testing/asserts.ts"; -import { glob, isGlob } from "./glob.ts"; -import { join } from "./path.ts"; +import { isWindows } from "./path/constants.ts"; +import { + ExpandGlobOptions, + expandGlob, + glob, + isGlob, + expandGlobSync +} from "./glob.ts"; +import { join, normalize, relative } from "./path.ts"; +import { WalkInfo } from "./walk.ts"; import { testWalk } from "./walk_test.ts"; import { touch, walkArray } from "./walk_test.ts"; @@ -131,7 +138,6 @@ testWalk( const arr = await walkArray(".", { match: [glob("x.*", { flags: "g", globstar: true })] }); - console.log(arr); assertEquals(arr.length, 2); assertEquals(arr[0], "x.js"); assertEquals(arr[1], "x.ts"); @@ -253,4 +259,76 @@ test({ } }); +async function expandGlobArray( + globString: string, + options: ExpandGlobOptions +): Promise<string[]> { + const infos: WalkInfo[] = []; + for await (const info of expandGlob(globString, options)) { + infos.push(info); + } + infos.sort(); + const infosSync = [...expandGlobSync(globString, options)]; + infosSync.sort(); + assertEquals(infos, infosSync); + const root = normalize(options.root || cwd()); + const paths = infos.map(({ filename }): string => filename); + for (const path of paths) { + assert(path.startsWith(root)); + } + const relativePaths = paths.map((path: string): string => + relative(root, path) + ); + relativePaths.sort(); + return relativePaths; +} + +function urlToFilePath(url: URL): string { + // Since `new URL('file:///C:/a').pathname` is `/C:/a`, remove leading slash. + return url.pathname.slice(url.protocol == "file:" && isWindows ? 1 : 0); +} + +const EG_OPTIONS = { + root: urlToFilePath(new URL(join("testdata", "glob"), import.meta.url)), + includeDirs: true, + extended: false, + globstar: false, + strict: false, + filepath: false, + flags: "" +}; + +test(async function expandGlobExt(): Promise<void> { + const options = { ...EG_OPTIONS, extended: true }; + assertEquals(await expandGlobArray("abc?(def|ghi)", options), [ + "abc", + "abcdef" + ]); + assertEquals(await expandGlobArray("abc*(def|ghi)", options), [ + "abc", + "abcdef", + "abcdefghi" + ]); + assertEquals(await expandGlobArray("abc+(def|ghi)", options), [ + "abcdef", + "abcdefghi" + ]); + assertEquals(await expandGlobArray("abc@(def|ghi)", options), ["abcdef"]); + assertEquals(await expandGlobArray("abc{def,ghi}", options), ["abcdef"]); + assertEquals(await expandGlobArray("abc!(def|ghi)", options), ["abc"]); +}); + +test(async function expandGlobGlobstar(): Promise<void> { + const options = { ...EG_OPTIONS, globstar: true }; + assertEquals(await expandGlobArray(join("**", "abc"), options), [ + "abc", + join("subdir", "abc") + ]); +}); + +test(async function expandGlobIncludeDirs(): Promise<void> { + const options = { ...EG_OPTIONS, includeDirs: false }; + assertEquals(await expandGlobArray("subdir", options), []); +}); + runIfMain(import.meta); diff --git a/fs/globrex.ts b/fs/globrex.ts index 32a129a67..e382dc82e 100644 --- a/fs/globrex.ts +++ b/fs/globrex.ts @@ -5,17 +5,18 @@ import { GlobOptions } from "./glob.ts"; const isWin = Deno.build.os === "win"; -const SEP = isWin ? `\\\\+` : `\\/`; +const SEP = isWin ? `(\\\\+|\\/)` : `\\/`; const SEP_ESC = isWin ? `\\\\` : `/`; -const GLOBSTAR = `((?:[^/]*(?:/|$))*)`; -const WILDCARD = `([^/]*)`; -const GLOBSTAR_SEGMENT = `((?:[^${SEP_ESC}]*(?:${SEP_ESC}|$))*)`; -const WILDCARD_SEGMENT = `([^${SEP_ESC}]*)`; +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 GlobrexResult { regex: RegExp; path?: { - regex: string | RegExp; + regex: RegExp; segments: RegExp[]; globstar?: RegExp; }; @@ -44,11 +45,8 @@ export function globrex( ): GlobrexResult { let regex = ""; let segment = ""; - let path: { - regex: string | RegExp; - segments: RegExp[]; - globstar?: RegExp; - } = { regex: "", segments: [] }; + 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. @@ -72,13 +70,13 @@ export function globrex( const { split, last, only } = options; if (only !== "path") regex += str; if (filepath && only !== "regex") { - path.regex += str === "\\/" ? SEP : str; + pathRegexStr += str.match(new RegExp(`^${SEP}$`)) ? SEP : str; if (split) { if (last) segment += str; if (segment !== "") { // change it 'includes' if (!flags.includes("g")) segment = `^${segment}$`; - path.segments.push(new RegExp(segment, flags)); + pathSegments.push(new RegExp(segment, flags)); } segment = ""; } else { @@ -267,9 +265,9 @@ export function globrex( let isGlobstar = starCount > 1 && // multiple "*"'s // from the start of the segment - (prevChar === "/" || prevChar === undefined) && + [SEP_RAW, "/", undefined].includes(prevChar) && // to the end of the segment - (nextChar === "/" || nextChar === undefined); + [SEP_RAW, "/", undefined].includes(nextChar); if (isGlobstar) { // it's a globstar, so match zero or more path segments add(GLOBSTAR, { only: "regex" }); @@ -292,20 +290,22 @@ export function globrex( if (!flags.includes("g")) { regex = `^${regex}$`; segment = `^${segment}$`; - if (filepath) path.regex = `^${path.regex}$`; + if (filepath) pathRegexStr = `^${pathRegexStr}$`; } const result: GlobrexResult = { regex: new RegExp(regex, flags) }; // Push the last segment if (filepath) { - path.segments.push(new RegExp(segment, flags)); - path.regex = new RegExp(path.regex.toString(), flags); - path.globstar = new RegExp( - !flags.includes("g") ? `^${GLOBSTAR_SEGMENT}$` : GLOBSTAR_SEGMENT, - flags - ); - result.path = path; + 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/fs/path/constants.ts b/fs/path/constants.ts index d70bb8073..55851f8cc 100644 --- a/fs/path/constants.ts +++ b/fs/path/constants.ts @@ -50,3 +50,4 @@ export const CHAR_9 = 57; /* 9 */ export const isWindows = build.os === "win"; export const EOL = isWindows ? "\r\n" : "\n"; +export const SEP = isWindows ? "\\" : "/"; diff --git a/fs/testdata/glob/abc b/fs/testdata/glob/abc new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/fs/testdata/glob/abc diff --git a/fs/testdata/glob/abcdef b/fs/testdata/glob/abcdef new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/fs/testdata/glob/abcdef diff --git a/fs/testdata/glob/abcdefghi b/fs/testdata/glob/abcdefghi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/fs/testdata/glob/abcdefghi diff --git a/fs/testdata/glob/subdir/abc b/fs/testdata/glob/subdir/abc new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/fs/testdata/glob/subdir/abc |