import { basename, FileSystemAdapter, SEP, Stats, } from "@davidsouther/jiffies/lib/esm/fs.js"; function dirname(path: string): string { return path.split(SEP).slice(0, -1).join(SEP); } export function openNand2TetrisDirectory(): Promise { return window.showDirectoryPicker({ id: "nand2tetris", mode: "readwrite", startIn: "documents", }); } export class FileSystemAccessFileSystemAdapter implements FileSystemAdapter { constructor(private baseDir: FileSystemDirectoryHandle) {} async getFolder( path: string, create = false, ): Promise { let folder = this.baseDir; const parts = path .split(SEP) .slice(1) .filter((part) => part.trim() != ""); for (const part of parts) { folder = await folder.getDirectoryHandle(part, { create }); } return folder; } async copyFile(from: string, to: string): Promise { throw new Error( "unimplemented: FileSystemAccessFileSystemAdapter::copyFile", ); } async mkdir(path: string): Promise { this.getFolder(path, true); } async readFile(path: string): Promise { const folder = await this.getFolder(dirname(path)); const file = await (await folder.getFileHandle(basename(path))).getFile(); return file.text(); } async writeFile(path: string, contents: string): Promise { const folder = await this.getFolder(dirname(path), true); const file = await ( await folder.getFileHandle(basename(path), { create: true }) ).createWritable(); await file.write(contents); await file.close(); } async readdir(path: string): Promise { const folder = await this.getFolder(path); const entries: string[] = []; for await (const [entry, _] of folder.entries()) { entries.push(entry); } return entries; } async scandir(path: string): Promise { const folder = await this.getFolder(path); const entries: Stats[] = []; for await (const [name, handle] of folder.entries()) { entries.push({ name, isDirectory() { return handle.kind == "directory"; }, isFile() { return handle.kind == "file"; }, }); } return entries; } async stat(path: string): Promise { const folder = await this.getFolder(dirname(path)); for await (const [name, handle] of folder.entries()) { if (name == basename(path)) { return { name, isDirectory() { return handle.kind == "directory"; }, isFile() { return handle.kind == "file"; }, }; } } return { name: basename(path), isDirectory() { return false; }, isFile() { return false; }, }; } async rm(path: string): Promise { const folder = await this.getFolder(dirname(path), true); await folder.removeEntry(basename(path), { recursive: true }); } } export class ChainedFileSystemAdapter implements FileSystemAdapter { constructor( protected adapter: FileSystemAdapter, private nextAdapter?: FileSystemAdapter | undefined, ) {} stat(path: string): Promise { return this.adapter.stat(path).catch((e) => { if (this.nextAdapter) { return this.nextAdapter.stat(path); } throw e; }); } readdir(path: string): Promise { return this.adapter.readdir(path).catch((e) => { if (this.nextAdapter) { return this.nextAdapter.readdir(path); } throw e; }); } scandir(path: string): Promise { return this.adapter.scandir(path).catch((e) => { if (this.nextAdapter) { return this.nextAdapter.scandir(path); } throw e; }); } async mkdir(path: string): Promise { if (this.nextAdapter) await this.nextAdapter.mkdir(path); return this.adapter.mkdir(path); } async copyFile(from: string, to: string): Promise { if (this.nextAdapter) await this.nextAdapter.copyFile(from, to); return this.adapter.copyFile(from, to); } readFile(path: string): Promise { return this.adapter.readFile(path).catch((e) => { if (this.nextAdapter) { return this.nextAdapter.readFile(path); } throw e; }); } async writeFile(path: string, contents: string): Promise { if (this.nextAdapter) await this.nextAdapter?.writeFile(path, contents); return this.adapter.writeFile(path, contents); } async rm(path: string): Promise { if (this.nextAdapter) await this.nextAdapter.rm(path); return this.adapter.rm(path); } }