0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-10-29 08:58:01 -04:00
denoland-deno/std/fs/walk.ts

161 lines
3.9 KiB
TypeScript

// Documentation and interface for walk were adapted from Go
// https://golang.org/pkg/path/filepath/#Walk
// Copyright 2009 The Go Authors. All rights reserved. BSD license.
import { unimplemented, assert } from "../testing/asserts.ts";
import { join } from "../path/mod.ts";
const { readdir, readdirSync, stat, statSync } = Deno;
export interface WalkOptions {
maxDepth?: number;
includeFiles?: boolean;
includeDirs?: boolean;
followSymlinks?: boolean;
exts?: string[];
match?: RegExp[];
skip?: RegExp[];
}
function include(
filename: string,
exts?: string[],
match?: RegExp[],
skip?: RegExp[]
): boolean {
if (exts && !exts.some((ext): boolean => filename.endsWith(ext))) {
return false;
}
if (match && !match.some((pattern): boolean => !!filename.match(pattern))) {
return false;
}
if (skip && skip.some((pattern): boolean => !!filename.match(pattern))) {
return false;
}
return true;
}
export interface WalkEntry {
filename: string;
info: Deno.FileInfo;
}
/** Walks the file tree rooted at root, yielding each file or directory in the
* tree filtered according to the given options. The files are walked in lexical
* order, which makes the output deterministic but means that for very large
* directories walk() can be inefficient.
*
* Options:
* - maxDepth?: number = Infinity;
* - includeFiles?: boolean = true;
* - includeDirs?: boolean = true;
* - followSymlinks?: boolean = false;
* - exts?: string[];
* - match?: RegExp[];
* - skip?: RegExp[];
*
* for await (const { filename, info } of walk(".")) {
* console.log(filename);
* assert(info.isFile);
* };
*/
export async function* walk(
root: string,
{
maxDepth = Infinity,
includeFiles = true,
includeDirs = true,
followSymlinks = false,
exts = undefined,
match = undefined,
skip = undefined,
}: WalkOptions = {}
): AsyncIterableIterator<WalkEntry> {
if (maxDepth < 0) {
return;
}
if (includeDirs && include(root, exts, match, skip)) {
yield { filename: root, info: await stat(root) };
}
if (maxDepth < 1 || !include(root, undefined, undefined, skip)) {
return;
}
for await (const dirEntry of readdir(root)) {
if (dirEntry.isSymlink) {
if (followSymlinks) {
// TODO(ry) Re-enable followSymlinks.
unimplemented();
} else {
continue;
}
}
const filename = join(root, dirEntry.name);
if (dirEntry.isFile) {
if (includeFiles && include(filename, exts, match, skip)) {
yield { filename, info: dirEntry };
}
} else {
yield* walk(filename, {
maxDepth: maxDepth - 1,
includeFiles,
includeDirs,
followSymlinks,
exts,
match,
skip,
});
}
}
}
/** Same as walk() but uses synchronous ops */
export function* walkSync(
root: string,
{
maxDepth = Infinity,
includeFiles = true,
includeDirs = true,
followSymlinks = false,
exts = undefined,
match = undefined,
skip = undefined,
}: WalkOptions = {}
): IterableIterator<WalkEntry> {
if (maxDepth < 0) {
return;
}
if (includeDirs && include(root, exts, match, skip)) {
yield { filename: root, info: statSync(root) };
}
if (maxDepth < 1 || !include(root, undefined, undefined, skip)) {
return;
}
for (const dirEntry of readdirSync(root)) {
if (dirEntry.isSymlink) {
if (followSymlinks) {
unimplemented();
} else {
continue;
}
}
assert(dirEntry.name != null);
const filename = join(root, dirEntry.name);
if (dirEntry.isFile) {
if (includeFiles && include(filename, exts, match, skip)) {
yield { filename, info: dirEntry };
}
} else {
yield* walkSync(filename, {
maxDepth: maxDepth - 1,
includeFiles,
includeDirs,
followSymlinks,
exts,
match,
skip,
});
}
}
}