diff --git a/__tests__/fdir.test.ts b/__tests__/fdir.test.ts index f080981..043c93d 100644 --- a/__tests__/fdir.test.ts +++ b/__tests__/fdir.test.ts @@ -2,7 +2,7 @@ import { fdir } from "../index"; import fs from "fs"; import mock from "mock-fs"; import { test, beforeEach, TestContext } from "vitest"; -import path from "path"; +import path, { sep } from "path"; beforeEach(() => { mock.restore(); @@ -349,6 +349,15 @@ for (const type of apiTypes) { mock.restore(); }); + test(`[${type}] crawl all files and invert path separator`, async (t) => { + const api = new fdir() + .withPathSeparator(sep === "/" ? "\\" : "/") + .crawl("node_modules"); + const files = await api[type](); + + t.expect(files.every((f) => !f.includes(sep))).toBeTruthy(); + }); + test("crawl all files (including symlinks) and throw errors", async (t) => { mock({ "/other/dir": {}, @@ -393,6 +402,7 @@ test(`paths should never start with ./`, async (t) => { new fdir().withBasePath().crawl("./node_modules"), new fdir().withBasePath().crawl("./"), new fdir().withRelativePaths().crawl("./"), + new fdir().withRelativePaths().crawl("."), new fdir().withDirs().crawl("."), new fdir().onlyDirs().crawl("."), ]; @@ -413,6 +423,14 @@ test(`ignore withRelativePath if root === ./`, async (t) => { t.expect(relativeFiles.every((r) => files.includes(r))).toBe(true); }); +test(`add path separator if root path does not end with one`, async (t) => { + const relativeFiles = await new fdir() + .withRelativePaths() + .crawl("node_modules") + .withPromise(); + t.expect(relativeFiles.every((r) => !r.startsWith(sep))).toBe(true); +}); + test(`there should be no empty directory when using withDirs`, async (t) => { const files = await new fdir().withDirs().crawl("./").withPromise(); t.expect(files.every((r) => r.length > 0)).toBe(true); diff --git a/package-lock.json b/package-lock.json index 191bf5c..494b736 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fdir", - "version": "6.0.1", + "version": "6.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fdir", - "version": "6.0.1", + "version": "6.0.2", "license": "MIT", "devDependencies": { "@types/glob": "^8.0.1", diff --git a/src/api/functions/join-path.ts b/src/api/functions/join-path.ts index 0609efd..e8c599b 100644 --- a/src/api/functions/join-path.ts +++ b/src/api/functions/join-path.ts @@ -1,14 +1,12 @@ -import { sep } from "path"; -import { Options } from "../../types"; +import { Options, PathSeparator } from "../../types"; function joinPathWithBasePath(filename: string, directoryPath: string) { return directoryPath + filename; } -function joinPathWithRelativePath(relativePath: string) { - relativePath += relativePath[relativePath.length - 1] === sep ? "" : sep; +function joinPathWithRelativePath(root: string) { return function (filename: string, directoryPath: string) { - return directoryPath.substring(relativePath.length) + filename; + return directoryPath.substring(root.length) + filename; }; } @@ -16,8 +14,12 @@ function joinPath(filename: string) { return filename; } -export function joinDirectoryPath(filename: string, directoryPath: string) { - return directoryPath + filename + sep; +export function joinDirectoryPath( + filename: string, + directoryPath: string, + separator: PathSeparator +) { + return directoryPath + filename + separator; } export type JoinPathFunction = ( diff --git a/src/api/walker.ts b/src/api/walker.ts index 04133d7..fdc9e3d 100644 --- a/src/api/walker.ts +++ b/src/api/walker.ts @@ -1,5 +1,5 @@ -import { resolve as pathResolve, sep } from "path"; -import { cleanPath } from "../utils"; +import { resolve as pathResolve } from "path"; +import { cleanPath, convertSlashes } from "../utils"; import { ResultCallback, WalkerState, Options } from "../types"; import * as joinPath from "./functions/join-path"; import * as pushDirectory from "./functions/push-directory"; @@ -73,18 +73,21 @@ export class Walker { } private normalizePath(path: string) { + const { resolvePaths, normalizePath, pathSeparator } = this.state.options; const pathNeedsCleaning = (process.platform === "win32" && path.includes("/")) || path.startsWith("."); - if (this.state.options.resolvePaths) path = pathResolve(path); - if (this.state.options.normalizePath || pathNeedsCleaning) - path = cleanPath(path); + if (resolvePaths) path = pathResolve(path); + if (normalizePath || pathNeedsCleaning) path = cleanPath(path); if (path === ".") return ""; - const needsSeperator = path[path.length - 1] !== sep; - return needsSeperator ? path + sep : path; + const needsSeperator = path[path.length - 1] !== pathSeparator; + return convertSlashes( + needsSeperator ? path + pathSeparator : path, + pathSeparator + ); } private walk = (entries: Dirent[], directoryPath: string, depth: number) => { @@ -106,11 +109,19 @@ export class Walker { const filename = this.joinPath(entry.name, directoryPath); this.pushFile(filename, files, this.state.counts, filters); } else if (entry.isDirectory()) { - let path = joinPath.joinDirectoryPath(entry.name, directoryPath); + let path = joinPath.joinDirectoryPath( + entry.name, + directoryPath, + this.state.options.pathSeparator + ); if (exclude && exclude(entry.name, path)) continue; this.walkDirectory(this.state, path, depth - 1, this.walk); } else if (entry.isSymbolicLink() && resolveSymlinks) { - let path = joinPath.joinDirectoryPath(entry.name, directoryPath); + let path = joinPath.joinDirectoryPath( + entry.name, + directoryPath, + this.state.options.pathSeparator + ); this.resolveSymlink!(path, this.state, (stat, resolvedPath) => { if (stat.isDirectory()) { resolvedPath = this.normalizePath(resolvedPath); diff --git a/src/builder/index.ts b/src/builder/index.ts index d1fde16..a4fabb6 100644 --- a/src/builder/index.ts +++ b/src/builder/index.ts @@ -1,3 +1,4 @@ +import { sep } from "path"; import { Output, OnlyCountsOutput, @@ -25,6 +26,7 @@ export class Builder { private options: Options = { maxDepth: Infinity, suppressErrors: true, + pathSeparator: sep, filters: [], }; @@ -37,6 +39,11 @@ export class Builder { return this as Builder; } + withPathSeparator(separator: "/" | "\\") { + this.options.pathSeparator = separator; + return this; + } + withBasePath() { this.options.includeBasePath = true; return this; diff --git a/src/types.ts b/src/types.ts index c6b08aa..e2bc00d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -40,7 +40,7 @@ export type ResultCallback = ( export type FilterPredicate = (path: string, isDirectory: boolean) => boolean; export type ExcludePredicate = (dirName: string, dirPath: string) => boolean; - +export type PathSeparator = "/" | "\\"; export type Options = { includeBasePath?: boolean; includeDirs?: boolean; @@ -56,5 +56,6 @@ export type Options = { excludeFiles?: boolean; exclude?: ExcludePredicate; relativePaths?: boolean; + pathSeparator: PathSeparator; signal?: AbortSignal; }; diff --git a/src/utils.ts b/src/utils.ts index 1e00e0e..a975dc7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,10 +1,18 @@ import { sep, normalize } from "path"; +import { PathSeparator } from "./types"; export function cleanPath(path: string) { let normalized = normalize(path); - // to account for / path + // we have to remove the last path separator + // to account for / root path if (normalized.length > 1 && normalized[normalized.length - 1] === sep) normalized = normalized.substring(0, normalized.length - 1); + return normalized; } + +const SLASHES_REGEX = /[\\/]+/g; +export function convertSlashes(path: string, separator: PathSeparator) { + return path.replace(SLASHES_REGEX, separator); +}