diff --git a/README.md b/README.md index 0872498..18328ba 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,15 @@ will cover most scenarios, this library focuses on the file system operations. Example: ``` -import { exists } from "@cross/fs/stat"; +import { exists, find } from "@cross/fs/stat"; +// Check if my/file exists console.log(await exists("my/file")); -// -> true / false +// false + +// Search for package.json recursively, starting from parent folder +console.log(await find("../", (path) => path.endsWith("package.json"))); +// ["/home/.../package.json","/home/.../.../package.json"] ``` Methods: @@ -34,12 +39,13 @@ Methods: | --------- | ---- | ---- | --- | ------------------- | | stat | X | X | X | runtime native | | lstat | X | X | X | node:fs/promises | -| exists | X | X | X | custom native | -| isDir | X | X | X | custom native | -| isFile | X | X | X | custom native | -| isSymlink | X | X | X | custom native | -| size | X | X | X | custom native | -| diskusage | X | X | X | custom native | +| exists | X | X | X | custom | +| isDir | X | X | X | custom | +| isFile | X | X | X | custom | +| isSymlink | X | X | X | custom | +| size | X | X | X | custom | +| find | X | X | X | custom | +| diskusage | X | X | X | custom | ### Io diff --git a/deno.json b/deno.json index c7ee363..d64c459 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@cross/fs", - "version": "0.0.5", + "version": "0.0.6", "exports": { ".": "./mod.ts", "./stat": "./src/stat/mod.ts", diff --git a/src/stat/find.ts b/src/stat/find.ts new file mode 100644 index 0000000..e4f578f --- /dev/null +++ b/src/stat/find.ts @@ -0,0 +1,67 @@ +import { readdir } from "node:fs/promises"; +import { stat, StatResult } from "./mod.ts"; +import { join, resolve } from "@std/path"; + +/** + * Recursively finds files and directories within a specified path, optionally applying advanced filtering. + * + * @param inPath - The starting directory path for the search. + * @param fileFilter - A filter function that takes the absolute file/directory path and its + * stat result as arguments. The function should return `true` if the item should be included + * in the results and `false` otherwise. + * @param recursive - Whether to search subdirectories recursively (default: `true`). + * @returns A `Promise` resolving to an array of absolute paths matching the filter criteria. + * + * **Examples:** + * + * **Simple Usage (Filename filtering):** + * ```typescript + * console.log(await find("../", (path) => path.endsWith("package.json"))); + * ``` + * + * **Advanced Usage (Filtering based on file type, size, etc.):** + * ```typescript + * console.log(await find("./documents", (path, stat) => + * stat.isFile() && stat.size > 1024 * 1024 // Find files larger than 1MB + * )); + * ``` + */ +export async function find( + inPath: string, + fileFilter: (path: string, stat: StatResult) => boolean, + recursive: boolean = true, +): Promise { + const statSelf = await stat(inPath); + const resolvedPath = resolve(inPath); + if (statSelf.isFile || statSelf.isSymlink) { + if (!fileFilter || fileFilter(resolvedPath, statSelf)) { + return [resolvedPath]; // Return the file path directly + } else { + return []; // File doesn't match the filter + } + } + if (statSelf.isDirectory) { + // Include the directory itself if it passes the filter + let results: string[] = []; + if (fileFilter(resolvedPath, statSelf)) { + results.push(resolvedPath); + } + const files = await readdir(resolvedPath, { withFileTypes: true }); + const paths: Promise[] = files.map((file) => { + const path = join(resolvedPath, file.name); + return find(path, fileFilter, recursive); + }); + const promiseResults = await Promise.allSettled(paths); + results = results.concat( + promiseResults + .filter((result): result is PromiseFulfilledResult => + result.status === "fulfilled" + ) + .map((result) => result.value) + .flat(), + ); + return results; + } + + return []; // Not a directory or file +} diff --git a/src/stat/mod.ts b/src/stat/mod.ts index aabac39..8221cde 100644 --- a/src/stat/mod.ts +++ b/src/stat/mod.ts @@ -175,3 +175,4 @@ export { lstat } from "node:fs/promises"; export * from "./is.ts"; export * from "./exists.ts"; export * from "./size.ts"; +export * from "./find.ts"; diff --git a/src/stat/size.ts b/src/stat/size.ts index 7bfeaa6..97d106c 100644 --- a/src/stat/size.ts +++ b/src/stat/size.ts @@ -1,21 +1,29 @@ -import { readdir, stat } from "node:fs/promises"; +import { readdir } from "node:fs/promises"; +import { stat } from "./mod.ts"; import { join } from "@std/path"; +/** + * Calculates the actual disk usage of a file or directory (considering file system block sizes). + * + * @param inPath - The path to the file or directory. + * @param recursive - If `true`, calculates disk usage recursively for directories. Defaults to `false`. + * @returns The total disk usage in bytes. + */ export async function diskusage( inPath: string, recursive?: boolean, ): Promise { const statSelf = await stat(inPath); const blockSize = statSelf.blksize; - const actualSize = Math.max(blockSize, statSelf.size); - if (statSelf.isFile()) { + const actualSize = Math.max(blockSize!, statSelf.size); + if (statSelf.isFile) { return actualSize; } - if (statSelf.isDirectory()) { + if (statSelf.isDirectory) { const files = await readdir(inPath, { withFileTypes: true }); const paths: Promise[] = files.map((file) => { const path = join(inPath, file.name); - return size(path, recursive); + return diskusage(path, recursive); }); const results = await Promise.allSettled(paths); const sizes = results @@ -27,15 +35,22 @@ export async function diskusage( return actualSize; } +/** + * Calculates the total size of a file or directory. + * + * @param inPath - The path to the file or directory. + * @param recursive - If `true`, calculates size recursively for directories. Defaults to `false`. + * @returns The total size in bytes. + */ export async function size( inPath: string, recursive?: boolean, ): Promise { const statSelf = await stat(inPath); - if (statSelf.isFile()) { + if (statSelf.isFile) { return statSelf.size; } - if (statSelf.isDirectory()) { + if (statSelf.isDirectory) { const files = await readdir(inPath, { withFileTypes: true }); const paths: Promise[] = files.map((file) => { const path = join(inPath, file.name);