Changes
4 changed files (+196/-3)
-
-
@@ -49,9 +49,10 @@ ```## Running unit tests You need a read permission for the project directory due to some tests perform actual filesystem access. The most straightforward way to run the tests is to run this command on the repository root: ``` $ deno test --allow-read=. ``` ``` If you omit the `--allow-read=.` permission, tests performing actual file I/O will be skipped.
-
-
-
@@ -11,7 +11,14 @@import { DenoFsReader } from "./deno_fs.ts"; import type { DirectoryReader } from "./interface.ts"; Deno.test("Should read file tree", async () => { Deno.test("Should read file tree", { // Skip this test if read permission is not granted. // Without this, simple `deno test` would fail or prompt permissions, which is annoying. ignore: (await Deno.permissions.query({ name: "read", path: new URL("../", import.meta.url), })).state !== "granted", }, async () => { const reader = new DenoFsReader(new URL("../", import.meta.url)); const root = await reader.getRootDirectory();
-
-
-
@@ -0,0 +1,57 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import { assertEquals, assertObjectMatch, } from "../../deps/deno.land/std/assert/mod.ts"; import { MemoryFsReader } from "./memory_fs.ts"; import type { DirectoryReader, FileReader } from "./interface.ts"; Deno.test("Should create a in-memory file tree", async () => { const reader = new MemoryFsReader([ { path: "foo/null", content: "", }, { path: "foo/bar/baz.txt", content: "Foo Bar Baz", }, ]); const root = await reader.getRootDirectory(); const rootEntries = await root.read(); assertEquals(rootEntries.length, 1); const foo = rootEntries[0] as DirectoryReader; assertObjectMatch(foo, { type: "directory", name: "foo", }); const fooEntries = await foo.read(); assertEquals(fooEntries.length, 2); const bar = fooEntries[1] as DirectoryReader; assertObjectMatch(bar, { type: "directory", name: "bar", }); const barEntries = await bar.read(); assertEquals(barEntries.length, 1); const baz = barEntries[0] as FileReader; assertObjectMatch(baz, { type: "file", name: "baz.txt", }); const decoder = new TextDecoder(); const content = await baz.read(); assertEquals(decoder.decode(content), "Foo Bar Baz"); });
-
-
-
@@ -0,0 +1,128 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // // SPDX-License-Identifier: Apache-2.0 import type { DirectoryReader, FileReader, FileSystemReader, RootDirectoryReader, } from "./interface.ts"; interface FileBuilder { path: string | readonly string[]; content: string | Uint8Array; } type InternalTree = Map<string, Uint8Array | InternalTree>; /** * In-memory readonly filesystem. * * This was created for testing purpose. */ export class MemoryFsReader implements FileSystemReader { #tree: InternalTree; constructor(files: readonly FileBuilder[]) { this.#tree = new Map(); const encoder = new TextEncoder(); for (const file of files) { const path = typeof file.path === "string" ? file.path.split("/") : file.path; const content = typeof file.content === "string" ? encoder.encode(file.content) : file.content; this.#createRecur(path, content); } } #createRecur( path: readonly string[], content: Uint8Array, parent: InternalTree = this.#tree, ): void { switch (path.length) { case 0: throw new Error("Path can't be empty"); case 1: { const [name] = path; const existing = parent.get(name); if (existing && existing instanceof Map) { throw new Error( `Trying to create a file named "${name}", but directory with same name already exists.`, ); } parent.set(name, content); return; } default: { const [name, ...rest] = path; let dir = parent.get(name); if (dir && dir instanceof Uint8Array) { throw new Error( `Trying to create a directory named "${name}", but file with same name already exists.`, ); } if (!dir) { dir = new Map(); parent.set(name, dir); } this.#createRecur(rest, content, dir); return; } } } #mapToReaders( map: InternalTree, parent: DirectoryReader | RootDirectoryReader, ): Array<FileReader | DirectoryReader> { const readers: Array<FileReader | DirectoryReader> = []; for (const [name, contentOrSubTree] of map.entries()) { const path = parent.type === "root" ? [name] : [...parent.path, name]; if (contentOrSubTree instanceof Map) { const dir: DirectoryReader = { type: "directory", name, path, parent, read: () => Promise.resolve(this.#mapToReaders(contentOrSubTree, dir)), }; readers.push(dir); continue; } readers.push({ type: "file", name, path, parent, read: () => Promise.resolve(contentOrSubTree), }); } return readers; } getRootDirectory(): Promise<RootDirectoryReader> { const root: RootDirectoryReader = { type: "root", read: () => Promise.resolve(this.#mapToReaders(this.#tree, root)), }; return Promise.resolve(root); } }
-