From 4f4b72b9156611f9282a21b30c4be67fc14beee7 Mon Sep 17 00:00:00 2001 From: binekrasik Date: Sun, 17 May 2026 23:52:31 +0200 Subject: [PATCH] chore: make webfs usable --- index.html | 5 ----- src/app.ts | 2 +- src/fs/Item.ts | 19 +++++++++++++++++-- src/init.ts | 0 src/program/Cat.ts | 4 ---- src/program/Echo.ts | 15 +++++++++++---- src/program/Ls.ts | 14 ++++++++++---- src/program/Mkdir.ts | 12 ++++++++++-- src/program/Rm.ts | 8 ++++++-- src/program/Touch.ts | 5 +++-- src/shell/Shell.ts | 2 ++ src/shell/Wush.ts | 23 ++++++++++++++++++----- src/styles/app.scss | 2 +- src/terminal/Terminal.ts | 4 ++-- 14 files changed, 81 insertions(+), 34 deletions(-) delete mode 100644 src/init.ts diff --git a/index.html b/index.html index 774f101..0efc117 100644 --- a/index.html +++ b/index.html @@ -4,11 +4,6 @@ - - - diff --git a/src/app.ts b/src/app.ts index 4251edf..0c76dcf 100644 --- a/src/app.ts +++ b/src/app.ts @@ -22,7 +22,7 @@ request.onerror = _ => { "webfs error", "Failed to initialize the webfs database using IndexedDB. Make sure webshell does not have any IndexedDB related permissions disabled and try again.", 'critical', - ).Show(-1) + ).Show() } request.onsuccess = event => { diff --git a/src/fs/Item.ts b/src/fs/Item.ts index 4e83c25..629494c 100644 --- a/src/fs/Item.ts +++ b/src/fs/Item.ts @@ -212,8 +212,23 @@ export class Item { private normalizePath(path: string): string { if (!path.startsWith('/')) path = '/' + path - if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1) - return path + + // Resolve . and .. segments. + // Split on '/' and walk each part: skip empty strings (from leading + // slash or consecutive slashes) and '.', pop on '..', push otherwise. + const parts = path.split('/') + const resolved: string[] = [] + for (const part of parts) { + if (part === '' || part === '.') { + continue + } else if (part === '..') { + if (resolved.length > 0) resolved.pop() // never climbs above root + } else { + resolved.push(part) + } + } + + return '/' + resolved.join('/') } private getParentPath(): string { diff --git a/src/init.ts b/src/init.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/program/Cat.ts b/src/program/Cat.ts index cc2f74f..e94f27b 100644 --- a/src/program/Cat.ts +++ b/src/program/Cat.ts @@ -3,10 +3,6 @@ import type { SimpleStream } from '../utils/SimpleStream' import { Program } from './Program' export class Cat extends Program { - constructor() { - super() - } - async Exec(_: SimpleStream, stdout: SimpleStream, __: Item, args: string[]): Promise { let item: Item = await Item.open(args[1]) diff --git a/src/program/Echo.ts b/src/program/Echo.ts index 40a19d6..57df71e 100644 --- a/src/program/Echo.ts +++ b/src/program/Echo.ts @@ -7,17 +7,24 @@ export class Echo extends Program { super() } - async Exec(_: SimpleStream, stdout: SimpleStream, ___: Item, args: string[]): Promise { - let item: Item = await Item.open(args[1]) + async Exec(_: SimpleStream, stdout: SimpleStream, workdir: Item, args: string[]): Promise { + if (args.length < 2) { + stdout.emit("echo: error: missing path argument\n") + return 1 + } + + const item = await Item.open(args[1].startsWith('/') + ? args[1] + : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`) if (!(await item.Exists())) { stdout.emit(`echo: error: item ${item.GetPath()} doesn't exist.\n`) - return 1 + return 2 } if (item.IsDirectory()) { stdout.emit(`echo: error: can't write data to a directory; item ${item.GetPath()} is a directory.\n`) - return 2 + return 3 } await item.Write(args.slice(2).join(' ')) diff --git a/src/program/Ls.ts b/src/program/Ls.ts index e5a63f0..f85c317 100644 --- a/src/program/Ls.ts +++ b/src/program/Ls.ts @@ -8,7 +8,12 @@ export class Ls extends Program { } async Exec(_: SimpleStream, stdout: SimpleStream, workdir: Item, args: string[]): Promise { - let item: Item = args[1] ? await Item.openDir(args[1]) : workdir + const item = args[1] + ? await Item.openDir(args[1].startsWith('/') + ? args[1] + : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`) + : workdir + if (args[1] && !item.IsDirectory()) { stdout.emit("ls: error: the provided path is not a directory\n") return 1 @@ -22,10 +27,11 @@ export class Ls extends Program { console.log(item) stdout.emit(`-> Listing contents of item: '${item.GetPath()}'\n`) - stdout.emit(` [ attr name ]\n`) + stdout.emit(` [ drwx name ]\n`) const items = await item.List() - items.forEach((entry, i) => { - stdout.emit(` | ${''.padEnd(4, '-').padEnd(8, ' ')} ${entry.GetName()}\n`) + items.forEach(entry => { + stdout.emit(item.IsDirectory().toString()) + stdout.emit(` | ${(item.IsDirectory() ? 'd' : '').padEnd(4, '-').padEnd(8, ' ')} ${entry.GetName()}\n`) }) return 0 diff --git a/src/program/Mkdir.ts b/src/program/Mkdir.ts index 10b387a..49be8fd 100644 --- a/src/program/Mkdir.ts +++ b/src/program/Mkdir.ts @@ -8,12 +8,20 @@ export class Mkdir extends Program { } async Exec(_: SimpleStream, stdout: SimpleStream, workdir: Item, args: string[]): Promise { - const item = await Item.openDir(args[1]) + if (args.length < 2) { + stdout.emit("mkdir: error: missing path argument\n") + return 1 + } + + const item = await Item.openDir(args[1].startsWith('/') + ? args[1] + : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`) + stdout.emit(`does ${args[1]} exist? ${await item.Exists()}\n`) if (await item.Exists()) { stdout.emit(`mkdir: directory ${item.GetPath()} already exists.\n`) - return 1 + return 2 } item.Create() diff --git a/src/program/Rm.ts b/src/program/Rm.ts index 9c98347..f6607ed 100644 --- a/src/program/Rm.ts +++ b/src/program/Rm.ts @@ -16,9 +16,13 @@ export class Rm extends Program { let item: Item try { - item = await Item.openDir(args[1]) + item = await Item.openDir(args[1].startsWith('/') + ? args[1] + : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`) } catch { - item = await Item.open(args[1]) + item = await Item.open(args[1].startsWith('/') + ? args[1] + : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`) } if (!(await item.Exists())) { diff --git a/src/program/Touch.ts b/src/program/Touch.ts index 43f50d5..9b655e4 100644 --- a/src/program/Touch.ts +++ b/src/program/Touch.ts @@ -10,8 +10,9 @@ export class Touch extends Program { async Exec(_: SimpleStream, stdout: SimpleStream, workdir: Item, args: string[]): Promise { stdout.emit(`touching children, please wait...\n`) - const item = await Item.open(args[1]) - stdout.emit(`does ${args[1]} exist? ${await item.Exists()}\n`) + const path = args.slice(1).join() + const item = await Item.open(path.includes('/') ? path : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${path}`) + stdout.emit(`does ${item.GetPath()} exist? ${await item.Exists()}\n`) if (await item.Exists()) { stdout.emit("touch: the file already exists.\n") diff --git a/src/shell/Shell.ts b/src/shell/Shell.ts index 0360faa..c89a34e 100644 --- a/src/shell/Shell.ts +++ b/src/shell/Shell.ts @@ -1,3 +1,4 @@ +import type { Item } from '../fs/Item' import type { Program } from '../program/Program' import type { Terminal } from '../terminal/Terminal' import type { EventBroadcaster } from '../utils/EventBroadcaster' @@ -17,4 +18,5 @@ export abstract class Shell { abstract ExecuteProgram(name: string, args: string[]): void abstract Init(): Promise abstract HandleKeyInput(key: string, isCharacter: boolean): void + abstract SetWorkingDirectory(directory: Item): Promise } diff --git a/src/shell/Wush.ts b/src/shell/Wush.ts index d95ef61..9c427f9 100644 --- a/src/shell/Wush.ts +++ b/src/shell/Wush.ts @@ -21,6 +21,8 @@ import { ResetIndexedDb } from '../program/ResetIndexedDb' import { Cat } from '../program/Cat' import { Echo } from '../program/Echo' import { Mkdir } from '../program/Mkdir' +import { Pwd } from '../program/Pwd' +import { Cd } from '../program/Cd' export class Wush extends Shell { public readonly Version = "0.1.0" @@ -47,7 +49,7 @@ export class Wush extends Shell { readonly stdout: SimpleStream private programs: { [name: string]: Program } = {} - private workingDirectory: Item = null as unknown as Item // workdir is initialized in the Init so this should be safe + private workingDirectory: Item = null as unknown as Item // workdir is initialized in Init so this should be safe constructor(broadcaster: EventBroadcaster, terminal: Terminal) { super(broadcaster, terminal) @@ -65,9 +67,6 @@ export class Wush extends Shell { // load workdir this.workingDirectory = await Item.Root() - if (!this.workingDirectory.Exists()) - this.workingDirectory.Create() - // load core programs this.programs['clear'] = new Clear() this.programs['eval'] = new Eval() @@ -83,6 +82,8 @@ export class Wush extends Shell { this.programs['cat'] = new Cat() this.programs['echo'] = new Echo() this.programs['mkdir'] = new Mkdir() + this.programs['pwd'] = new Pwd() + this.programs['cd'] = new Cd(this) this.stdout.on(data => this.WriteEscapedString(data)) this.Prompt() @@ -121,7 +122,19 @@ export class Wush extends Shell { } Prompt() { - this.terminal.Write(`hi [${this.execExitCode}] -> `) + this.terminal.Write(`boykisser in ${this.workingDirectory.GetPath()} -> `) + } + + /** + * Changes the working directory + * @param directory the directory to enter into + * @throws an error if the directory cannot be opened + */ + async SetWorkingDirectory(directory: Item) { + if (!(await directory.Exists()) || !directory.IsDirectory()) + throw new Error(`Directory ${directory.GetPath()} doesn't exist`) + + this.workingDirectory = directory } WriteStdin(data: string) { diff --git a/src/styles/app.scss b/src/styles/app.scss index 980872a..6ac355a 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -2,7 +2,7 @@ @use "./colors.scss" as colors; body { - margin: 0; + margin: 10px; background: colors.$terminal-background; } diff --git a/src/terminal/Terminal.ts b/src/terminal/Terminal.ts index c90c27f..00a27fe 100644 --- a/src/terminal/Terminal.ts +++ b/src/terminal/Terminal.ts @@ -155,8 +155,8 @@ export class Terminal { UpdateCursor() { this.SetCursorStyle(this.cursorStyle) - this.cursor.style.left = `${this.cursorPosition.col * this.cellWidth}px` - this.cursor.style.top = `${this.cursorPosition.row * this.cellHeight}px` + this.cursor.style.left = `${this.cursorPosition.col * this.cellWidth + this.terminal.offsetLeft}px ` + this.cursor.style.top = `${this.cursorPosition.row * this.cellHeight + this.terminal.offsetTop}px` } GetCursorPosition(): CursorPosition {