UNPKG

files

Version:

Filesystem API easily usable with Promises and arrays

645 lines (550 loc) 19.8 kB
// Native file system and path import fs from "fs"; import path from "path"; import { dirname } from "path"; import { Readable } from "stream"; import { fileURLToPath } from "url"; import { promisify } from "util"; import cmd from "atocha"; import swear from "swear"; // fs-promises import { abs, copy, dir, exists, home, join, list, mkdir, move, name, read, remove, rename, sep, stat, tmp, walk, write, } from "."; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Find whether it's Linux or Mac, where we can use `find` const mac = () => process.platform === "darwin"; const linux = () => process.platform === "linux"; const windows = () => process.platform === "win32"; const unix = () => mac() || linux(); const root = linux() ? "/home/" : mac() ? "/Users/" : "C:\\projects"; describe("abs", () => { it("gets the defaults right", async () => { expect(await abs()).toBe(__dirname); expect(await abs("demo")).toBe(await join(__dirname, "/demo")); }); it("works with swear()", async () => { expect(await abs(swear("demo"))).toBe(await join(__dirname, "/demo")); }); it("get the absolute path of the passed args", async () => { expect(await abs("demo", process.cwd())).toBe( await join(__dirname, "/demo"), ); expect(await abs("demo", __dirname)).toBe(await join(__dirname, "/demo")); }); it("ignores the second parameter if not a string", async () => { expect(await abs("demo", 0)).toBe(await join(__dirname, "/demo")); expect(await abs("demo", 5)).toBe(await join(__dirname, "/demo")); expect(await abs("demo", true)).toBe(await join(__dirname, "/demo")); }); it("works with home", async () => { expect(await abs("~/")).toBe(await home()); expect(await abs("~/hello")).toBe((await home()) + "/hello"); expect(await abs("~/hello/")).toBe((await home()) + "/hello/"); }); }); describe("copy", () => { const src = "demo/a/readme.md"; // Create and destroy it for each test it("copy the file maintaining the original", async () => { const dst = "demo/abc.md"; await remove(dst); expect(await exists(src)).toBe(true); expect(await exists(dst)).toBe(false); const res = await copy(src, dst); expect(await exists(src)).toBe(true); expect(await exists(dst)).toBe(true); expect(res).toBe(await abs(dst)); await remove(dst); }); it("copy the file into a nested structure", async () => { const dst = "demo/copy/readme.md"; await remove(dst); expect(await exists(src)).toBe(true); expect(await exists(dst)).toBe(false); const res = await copy(src, dst); expect(await exists(src)).toBe(true); expect(await exists(dst)).toBe(true); expect(res).toBe(await abs(dst)); await remove("demo/copy"); }); }); describe("dir", () => { it("defaults to the current dir", async () => { expect(await dir()).toContain(root); }); it("returns the parent if already a path", async () => { expect(await dir("demo/a")).toContain(`files${sep}demo`); expect(await dir("demo/a")).not.toContain(`files${sep}demo${sep}a`); expect(await dir("demo/a/")).toContain(`files${sep}demo`); expect(await dir("demo/a/")).not.toContain(`files${sep}demo${sep}a`); }); it("works with swear()", async () => { expect(await dir(swear("demo/a/b/readme.md"))).toContain( `files${sep}demo${sep}a${sep}b`, ); }); it("can put the full folder path", async () => { expect(await dir("demo/a/b/readme.md")).toContain( `files${sep}demo${sep}a${sep}b`, ); expect(await dir(dir("demo/a/b/readme.md"))).not.toContain( `files${sep}demo${sep}a${sep}b`, ); expect(await dir(dir(dir("demo/a/b/readme.md")))).not.toContain( `files${sep}demo${sep}a`, ); }); it("can work with relative paths", async () => { expect(await dir("./demo/")).toBe( await abs("./demo/") .replace(/(\/|\\)$/, "") .split(sep) .slice(0, -1) .join(sep), ); }); }); describe("list", () => { it("defaults to the current folder", async () => { expect(await list()).toContain(__dirname + sep + "package.json"); }); it("works with swear()", async () => { expect(await list(swear(process.cwd()))).toContain( __dirname + sep + "package.json", ); }); it("can load the demo", async () => { const files = await list("demo", __dirname); expect(files).not.toContain(__dirname + sep + "package.json"); expect(files).toContain(__dirname + sep + `demo${sep}a`); expect(files).toContain(__dirname + sep + `demo${sep}readme.md`); }); }); describe("exists", () => { it("defaults to the current dir", async () => { expect(await exists()).toBe(true); }); it("works with swear()", async () => { expect(await exists()).toBe(true); }); it("can check the demo", async () => { expect(await exists("demo")).toBe(true); expect(await exists("demo/readme.md")).toBe(true); expect(await exists(await home("Documents"))).toBe(true); expect(await exists("aaa")).toBe(false); }); }); describe("home", () => { const homeDir = unix() ? "echo $HOME" : "echo %systemdrive%%homepath%"; it("uses the home directory", async () => { expect(await home()).toEqual(await cmd(homeDir)); // expect((await home()).slice(-1)).toBe('/'); }); it("works with swear()", async () => { expect(await home(swear(""))).toContain(await cmd(homeDir)); }); }); describe("join", () => { it("can do a simple join", async () => { expect(await join(__dirname, "demo")).toBe(path.join(__dirname, "demo")); }); it("works with swear()", async () => { expect(await join(swear(__dirname), swear("demo"))).toBe( path.join(__dirname, "demo"), ); }); }); describe("mkdir", () => { beforeEach(async () => promisify(fs.rmdir)(await abs("demo/b")).catch((err) => {}), ); afterEach(async () => promisify(fs.rmdir)(await abs("demo/b")).catch((err) => {}), ); it("create a new directory", async () => { expect(await exists("demo/b")).toBe(false); const res = await mkdir("demo/b"); expect(await exists("demo/b")).toBe(true); expect(res).toBe(await abs("demo/b")); }); it("does not throw if it already exists", async () => { expect(await exists("demo/a")).toBe(true); const res = await mkdir("demo/a"); expect(await exists("demo/a")).toBe(true); expect(res).toBe(await abs("demo/a")); }); it("creates it even if the parent does not exist", async () => { await remove("demo/c"); expect(await exists("demo/c")).toBe(false); const res = await mkdir("demo/c/d/e"); expect(await exists("demo/c/d/e")).toBe(true); expect(res).toBe(await abs("demo/c/d/e")); await remove("demo/c"); }); }); describe("move", () => { const src = "demo/move.txt"; // Create and destroy it for each test beforeEach(() => write(src, "hello")); afterEach(() => remove(src)); it("can simply move a file", async () => { const dst = "demo/move-zzz.txt"; expect(await exists(dst)).toBe(false); const res = await move(src, dst); expect(await exists(src)).toBe(false); expect(await exists(dst)).toBe(true); expect(res).toBe(await abs(dst)); await remove(dst); }); it("can work with nested folders", async () => { const dst = "demo/move/zzz.txt"; expect(await exists(dst)).toBe(false); const res = await move(src, dst); expect(await exists(src)).toBe(false); expect(await exists(dst)).toBe(true); expect(res).toBe(await abs(dst)); await remove("demo/move"); }); it("works with folders", async () => { const src = "demo/move"; const dst = "demo/moved"; await write("demo/move/test.txt", "hello"); expect(await exists(dst)).toBe(false); const res = await move(src, dst); expect(await exists(src)).toBe(false); expect(await exists(dst)).toBe(true); expect(res).toBe(await abs(dst)); await remove(dst); }); }); describe("name", () => { it("find the file name in the path", async () => { expect(await name("demo/abs.js")).toBe("abs.js"); }); it("works with swear()", async () => { expect(await name(swear("demo/abs.js"))).toBe("abs.js"); expect(await name(abs("demo/abs.js"))).toBe("abs.js"); }); it("performs well without extension", async () => { expect(await name("demo/abs")).toBe("abs"); expect(await name(abs("demo/abs"))).toBe("abs"); }); }); describe("read", () => { it("can read a markdown file", async () => { expect(await read("demo/readme.md")).toContain("# Hello!"); }); it("can webstream read it", async () => { const stream = await read("demo/readme.md", { type: "web" }); const enc = new TextDecoder("utf-8"); let str = ""; for await (const chunk of stream) { str += enc.decode(chunk); } expect(str).toContain("# Hello!"); }); it("can nodestream read it", async () => { const stream = await read("demo/readme.md", { type: "node" }); const enc = new TextDecoder("utf-8"); let str = ""; for await (const chunk of stream) { str += enc.decode(chunk); } expect(str).toContain("# Hello!"); }); it("works with swear()", async () => { expect(await read(swear("demo/readme.md"))).toContain("# Hello!"); }); it("is empty if it is not a file", async () => { expect(await read(swear("demo"))).toBe(null); }); it("can auto-parse json", async () => { expect(await read("demo/test.json", { type: "json" })).toEqual({ hello: "world", }); }); it("can json parse it", async () => { expect(await read("demo/test.json").then(JSON.parse)).toEqual({ hello: "world", }); }); it("can read a dir", async () => { expect(await list("demo/a").map(read)).toEqual([null, "# Sub-level\n"]); }); }); describe("remove", () => { it("removes a file", async () => { await write("demo/remove.md", "Hello!"); expect(await read("demo/remove.md")).toBe("Hello!"); const file = await remove("demo/remove.md"); expect(await exists("demo/remove.md")).toBe(false); expect(file).toBe(await abs("demo/remove.md")); }); it("removes a directory", async () => { await mkdir("demo/b"); expect(await exists("demo/b")).toBe(true); const file = await remove("demo/b"); expect(await exists("demo/b")).toBe(false); expect(file).toBe(await abs("demo/b")); }); it("removes a directory with files", async () => { await mkdir("demo/b"); await write("demo/b/remove.md", "Hello!"); expect(await exists("demo/b")).toBe(true); expect(await read("demo/b/remove.md")).toBe("Hello!"); const file = await remove("demo/b"); expect(await exists("demo/b")).toBe(false); expect(file).toBe(await abs("demo/b")); }); it("removes a directory with deeply nested files", async () => { await mkdir("demo/x"); await write("demo/x/remove.md", "Hello!"); await mkdir("demo/x/c"); await write("demo/x/c/remove.md", "Hello!"); expect(await exists("demo/x")).toBe(true); expect(await read("demo/x/remove.md")).toBe("Hello!"); expect(await exists("demo/x/c")).toBe(true); expect(await read("demo/x/c/remove.md")).toBe("Hello!"); const file = await remove("demo/x"); expect(await exists("demo/x")).toBe(false); expect(file).toBe(await abs("demo/x")); }); it("cannot remove the root", async () => { await expect(remove("/")).rejects.toThrow(/remove the root/); }); it("will ignore a non-existing file", async () => { expect(await exists("demo/d")).toBe(false); await expect(await remove("demo/d")).toEqual(await abs("demo/d")); }); }); describe("rename", () => { const src = "demo/rename.txt"; // Create and destroy it for each test beforeEach(() => write(src, "hello")); afterEach(() => remove(src)); it("can simply move a file", async () => { const dst = "demo/rename-zzz.txt"; expect(await exists(dst)).toBe(false); const res = await rename(src, dst); expect(await exists(src)).toBe(false); expect(await exists(dst)).toBe(true); expect(res).toBe(await abs(dst)); await remove(dst); }); it("can work with nested folders", async () => { const dst = "demo/rename/zzz.txt"; expect(await exists(dst)).toBe(false); const res = await rename(src, dst); expect(await exists(src)).toBe(false); expect(await exists(dst)).toBe(true); expect(res).toBe(await abs(dst)); await remove("demo/rename"); }); it("works with folders", async () => { const src = "demo/rename"; const dst = "demo/renamed"; await write("demo/rename/test.txt", "hello"); expect(await exists(dst)).toBe(false); const res = await rename(src, dst); expect(await exists(src)).toBe(false); expect(await exists(dst)).toBe(true); expect(res).toBe(await abs(dst)); await remove(dst); }); }); describe("stat", () => { it("defaults to the current dir", async () => { expect(await stat().isDirectory()).toBe(true); expect(await stat(process.cwd()).isDirectory()).toBe(true); expect(await stat(__dirname).isDirectory()).toBe(true); }); it("works with swear()", async () => { expect(await stat(swear(process.cwd())).isDirectory()).toBe(true); expect(await stat(swear(__dirname)).isDirectory()).toBe(true); }); it("can analyze whether a path is a directory or not", async () => { expect(await stat("demo").isDirectory()).toBe(true); expect(await stat("demo/readme.md").isDirectory()).toBe(false); expect(await stat(__filename).isDirectory()).toBe(false); }); it("can read some dates", async () => { const date = await stat("demo/readme.md").atime; expect(date.constructor.name).toBe("Date"); expect(date).toEqual(new Date(date)); }); }); describe("tmp", () => { it("works empty", async () => { if (linux()) { expect(await tmp()).toBe("/tmp"); } else if (mac()) { expect((await tmp()) + "/").toBe(await cmd("echo $TMPDIR")); } else if (windows()) { expect(await tmp()).toBe("C:\\Users\\appveyor\\AppData\\Local\\Temp\\1"); } else { console.log("Platform not supported officially"); } }); it("works with swear()", async () => { if (linux()) { expect(await tmp(swear("demo"))).toBe("/tmp/demo"); } else if (mac()) { expect(await tmp("demo")).toBe((await cmd("echo $TMPDIR")) + "demo"); } else if (windows()) { expect(await tmp("demo")).toBe( "C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\demo", ); } else { console.log("Platform not supported officially"); } }); it("works with a path", async () => { if (linux()) { expect(await tmp("demo")).toBe("/tmp/demo"); } else if (mac()) { expect(await tmp("demo")).toBe((await cmd("echo $TMPDIR")) + "demo"); } else if (windows()) { expect(await tmp("demo")).toBe( "C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\demo", ); } else { console.log("Platform not supported officially"); } }); it("can reset the doc", async () => { await tmp("demo").then(remove); expect(await tmp("demo").then(list)).toEqual([]); mkdir(await tmp("demo/a")); if (linux()) { expect(await tmp("demo").then(list)).toEqual(["/tmp/demo/a"]); } else if (mac()) { expect(await tmp("demo").then(list)).toEqual([ (await cmd("echo $TMPDIR")) + "demo/a", ]); } else if (windows()) { expect(await tmp("demo").then(list)).toEqual([ "C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\demo\\a", ]); } else { console.log("Platform not supported officially"); } await tmp("demo").then(remove).then(mkdir); expect(await tmp("demo").then(list)).toEqual([]); }); }); describe("walk", () => { it("defaults to the current directory", async () => { const dest = __dirname + sep + "package.json"; expect(await walk()).toContain(dest); expect(await walk(".")).toContain(dest); expect(await walk(process.cwd())).toContain(dest); expect(await walk(import.meta.dirname)).toContain(dest); }); it("avoid double slashes when ending on '/'", async () => { expect(await walk("./")).toContain(__dirname + sep + "package.json"); }); it("works with swear()", async () => { expect(await walk(swear(process.cwd()))).toContain( __dirname + sep + "package.json", ); }); it("is empty if it doesn not exist", async () => { expect(await walk("demo/c")).toEqual([]); }); it("can deep walk", async () => { const files = await walk("demo"); expect(files).toContain(__dirname + sep + `demo${sep}readme.md`); expect(files).toContain(__dirname + sep + `demo${sep}a${sep}readme.md`); expect(files).toContain( __dirname + sep + `demo${sep}a${sep}b${sep}readme.md`, ); }); }); describe("write", () => { beforeEach(async () => promisify(fs.unlink)(await abs("demo/deleteme.md")).catch((err) => {}), ); afterEach(async () => promisify(fs.unlink)(await abs("demo/deleteme.md")).catch((err) => {}), ); it("creates a new file", async () => { expect(await exists("demo/deleteme.md")).toBe(false); await write("demo/deleteme.md", "Hello!"); expect(await read("demo/deleteme.md")).toBe("Hello!"); expect(await exists("demo/deleteme.md")).toBe(true); }); it("creates a new file from JSON", async () => { expect(await exists("demo/deleteme.md")).toBe(false); await write("demo/deleteme.md", { hello: "world" }); expect(await read("demo/deleteme.md")).toBe('{"hello":"world"}'); expect(await exists("demo/deleteme.md")).toBe(true); }); it("creates a new file from a buffer", async () => { expect(await exists("demo/deleteme.md")).toBe(false); await write("demo/deleteme.md", Buffer.from("Hello!", "utf8")); expect(await read("demo/deleteme.md")).toBe("Hello!"); expect(await exists("demo/deleteme.md")).toBe(true); }); it("can accept a promise", async () => { expect(await exists("demo/deleteme.md")).toBe(false); await write("demo/deleteme.md", read("demo/a/readme.md")); expect(await read("demo/deleteme.md")).toBe("# Sub-level\n"); expect(await exists("demo/deleteme.md")).toBe(true); }); it("creates a new file from a Readable", async () => { expect(await exists("demo/deleteme.md")).toBe(false); const stream = new Readable(); stream.push("Hello!"); // the string you want stream.push(null); // indicates end-of-file basically - the end of the stream await write("demo/deleteme.md", stream); expect(await read("demo/deleteme.md")).toBe("Hello!"); expect(await exists("demo/deleteme.md")).toBe(true); }); it("creates a new file from a ReadableStream", async () => { expect(await exists("demo/deleteme.md")).toBe(false); const stream = new ReadableStream({ start(controller) { controller.enqueue("Hello!"); controller.close(); }, }); await write("demo/deleteme.md", stream); expect(await read("demo/deleteme.md")).toBe("Hello!"); expect(await exists("demo/deleteme.md")).toBe(true); }); it("creates a new empty file", async () => { expect(await exists("demo/deleteme.md")).toBe(false); await write("demo/deleteme.md"); expect(await exists("demo/deleteme.md")).toBe(true); }); }); describe("other tests", () => { it("can combine async inside another methods", async () => { expect(await list(tmp("demo/x"))).toEqual([]); }); it("can walk and filter and map", async () => { const files = await walk("demo") .filter((file) => /readme\.md$/.test(file)) .map(read); expect(files).toContain("# Sub-sub-level\n"); }); });