summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/glob.ts67
-rw-r--r--fs/glob_test.ts88
-rw-r--r--fs/globrex.ts46
-rw-r--r--fs/path/constants.ts1
-rw-r--r--fs/testdata/glob/abc0
-rw-r--r--fs/testdata/glob/abcdef0
-rw-r--r--fs/testdata/glob/abcdefghi0
-rw-r--r--fs/testdata/glob/subdir/abc0
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