Skip to content

Instantly share code, notes, and snippets.

@HunterKohler
Created January 31, 2022 20:47
Show Gist options
  • Save HunterKohler/182ba73929152bdf4d89ceda467d51b1 to your computer and use it in GitHub Desktop.
Save HunterKohler/182ba73929152bdf4d89ceda467d51b1 to your computer and use it in GitHub Desktop.
Alright file system walker
/*
* Copyright (C) 2021 Hunter Kohler <jhunterkohler@gmail.com>
*/
import fs from "fs";
import path from "path";
interface WalkdirOptions {
withFileTypes?: boolean;
followlinks?: boolean;
files?: boolean;
encoding?: BufferEncoding;
blockdev?: boolean;
chardev?: boolean;
dirs?: boolean;
fifos?: boolean;
sockets?: boolean;
symlinks?: boolean;
maxdepth?: number;
}
/**
* Walk a directory tree.
*
* @param root Start of walk.
* @param options Walk options.
*/
export async function* walkdirs(
root: fs.PathLike,
options?: WalkdirOptions
): AsyncIterator<string> {
const {
followlinks = false,
files = true,
encoding = "utf8",
blockdev = true,
chardev = true,
dirs = true,
fifos = true,
sockets = true,
symlinks = true,
maxdepth = Infinity,
} = options ?? {};
const dirstack = [{ dirpath: String(root), depth: 0 }];
const diropts = { withFileTypes: true, encoding } as const;
const linkopts = { encoding };
const seen = new Set<string>();
while (dirstack.length) {
const { dirpath, depth } = dirstack.pop()!;
if (depth >= maxdepth) {
continue;
}
const dir: (string | fs.Dirent)[] = await fs.promises.readdir(
dirpath,
diropts
);
for (const dirent of dir) {
let filepath: string;
let stats: fs.Dirent | fs.Stats;
if (typeof dirent == "string") {
filepath = dirent;
stats = await fs.promises.stat(filepath);
} else {
filepath = path.join(dirpath, dirent.name);
stats = dirent;
}
/*
* Stop cycles if symlinks are allowed. (Though I suppose UNIX
* systems could implement a circular file system haha).
*/
if (seen.has(filepath)) {
continue;
} else {
seen.add(filepath);
}
if (stats.isFile()) {
if (files) {
yield filepath;
}
} else if (stats.isDirectory()) {
if (dirs) {
yield filepath;
}
dirstack.push({ dirpath: filepath, depth: depth + 1 });
} else if (stats.isSymbolicLink()) {
if (symlinks) {
yield filepath;
}
if (followlinks) {
dir.push(await fs.promises.readlink(filepath, linkopts));
}
} else if (stats.isBlockDevice()) {
if (blockdev) {
yield filepath;
}
} else if (stats.isCharacterDevice()) {
if (chardev) {
yield filepath;
}
} else if (stats.isFIFO()) {
if (fifos) {
yield filepath;
}
} else if (stats.isSocket()) {
if (sockets) {
yield filepath;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment