Changelog:

- Added `getGroupByName() getGroupByGid() getUserByName getUserByUid()` methods to the `Bash` class.

- Fixed a bug inside the `localStorage` caching logic that saved only 32 characters out of 36 in the signature UUID.

- Added support for reverse order sorting in the quick sort implementation.

**Commands:**

- ls
	-l
	-a
	-A
	-U
	-g
	-G
	-h
	-f
	-n
	-N
	-r
	-Q
	-p
	-o
- cd
This commit is contained in:
2025-11-24 03:49:51 +01:00
committed by Kamil Olszewski
parent e853268e52
commit e1fe000608
10 changed files with 215 additions and 114 deletions

View File

@@ -161,4 +161,36 @@ export class Bash {
return `${(bytes / Math.pow(k, i)).toFixed(dp)}${units[i]}`;
}
getGroupByName(name: string): Group {
const out: Group | undefined = this._group.find((group) => group.groupname === name);
console.log(out);
if (out) return out;
else throw new Error(`Cannot find a user group named ${name}`);
}
getUserByName(name: string): User {
const out: User | undefined = this._passwd.find((user) => user.username === name);
console.log(out);
if (out) return out;
else throw new Error(`Cannot find a user named ${name}`);
}
getGroupByGid(gid: number): Group {
const out: Group | undefined = this._group.find((group) => group.gid === gid);
console.log(out);
if (out) return out;
else throw new Error(`Cannot find a user group named ${name}`);
}
getUserByUid(uid: number): User {
const out: User | undefined = this._passwd.find((user) => user.uid === uid);
console.log(out);
if (out) return out;
else throw new Error(`Cannot find a user group named ${name}`);
}
}

View File

@@ -0,0 +1,54 @@
import { ExitCode, type Bash } from '../bash';
import { Type, type TreeNode } from '../fs';
import type { CommandArgs, ICommand, Result } from '../static';
export const cmd_cd = function (this: Bash, args: CommandArgs): Result {
let result: Result = { exitCode: ExitCode.ERROR };
const path = args.args[0];
let targetNode: TreeNode | null;
if (args.args.length > 1) return result; // Too many args
// if no args cd into home dir
if (args.args.length === 0) {
this.getFs().cwd = this.getFs().home;
result.exitCode = ExitCode.SUCCESS;
return result;
}
// if the arg is - cd make your current dir the prev dir and vice versa
if (args.args[0] === '-') {
[this.getFs().cwd, this.getFs().pwd] = [this.getFs().pwd, this.getFs().cwd];
result.exitCode = ExitCode.SUCCESS;
return result;
}
// Change the input STRING path from relative to absolute by replacing ~ with the home directory path
//TODO: Change that to a global function inside fs class to parse all possible path formats????? already exists, need to verify
let resolvedPath = path.startsWith('~')
? path.replace('~', this.getFs().pathArrayToString(this.getFs().home))
: path;
this.getFs().pwd = this.getFs().cwd;
targetNode = this.getFs()._getNodeByPathArray(this.getFs().resolvePath(resolvedPath)); // Conversion from STRING path to ARRAY
if (targetNode === null) return result;
if (targetNode.type !== Type.Directory) return result;
//if () return ExitCode.ERROR; // Check for read permissions on node and user
this.getFs().cwd = this.getFs().resolvePath(resolvedPath); // CD was successfull, change current dir to the verified target dir
result.exitCode = ExitCode.SUCCESS;
console.log(this.getCwd());
return result;
};
export const cd: ICommand = {
method: cmd_cd,
flags: [] as string[],
help: 'PATH TO HELP.md',
root: false
};

View File

@@ -79,11 +79,12 @@ function result_ls(this: Bash, data: any, args: CommandArgs): HTMLElement {
const f_a: boolean = flagInfo.has('a') || flagInfo.has('f');
const f_h: boolean = flagInfo.has('h');
if (flagInfo.has('l')) {
if (flagInfo.has('l') || flagInfo.has('g') || flagInfo.has('o')) {
const w: HTMLElement = document.createElement('div');
for (const node of nodes) {
if (!flagInfo.has('U') && !flagInfo.has('f')) asciiByteQSort(node.children);
if (!flagInfo.has('U') && !flagInfo.has('f'))
asciiByteQSort(node.children, flagInfo.has('r'));
const elem: HTMLElement = document.createElement('div');
const rows: string[] = [];
@@ -92,11 +93,26 @@ function result_ls(this: Bash, data: any, args: CommandArgs): HTMLElement {
const sizes = node.children.map((child) => (child.type === Type.Directory ? '4096' : '1'));
const maxSizeWidth = Math.max(...sizes.map((size) => size.length));
for (const child of node.children) {
if (child.name.startsWith('.') && !(f_a || flagInfo.has('A'))) continue;
const cols: LsEntry = {
perms: formatPermission(child),
children: formatChildren(child),
owners: formatOwners.call(this, child, flagInfo),
size: formatSize.call(this, f_h, child, maxSizeWidth),
modt: formatModtime(child),
name: formatName(child, flagInfo)
};
rows.push(LsEntryUtils.toString(cols));
}
if (f_a && !flagInfo.has('A')) {
const current: LsEntry = {
perms: formatPermission(node),
children: formatChildren(node),
owners: formatOwners(node, flagInfo),
owners: formatOwners.call(this, node, flagInfo),
size: formatSize.call(this, f_h, node, maxSizeWidth),
modt: formatModtime(node),
name: '.'
@@ -105,7 +121,7 @@ function result_ls(this: Bash, data: any, args: CommandArgs): HTMLElement {
? {
perms: formatPermission(node.parent),
children: formatChildren(node.parent),
owners: formatOwners(node.parent, flagInfo),
owners: formatOwners.call(this, node.parent, flagInfo),
size: formatSize.call(this, f_h, node.parent, maxSizeWidth),
modt: formatModtime(node.parent),
name: '..'
@@ -115,24 +131,11 @@ function result_ls(this: Bash, data: any, args: CommandArgs): HTMLElement {
name: '..'
};
rows.push(LsEntryUtils.toString(current), LsEntryUtils.toString(parent));
}
for (const child of node.children) {
if (child.name.startsWith('.') && !(f_a || flagInfo.has('A'))) continue;
const cols: LsEntry = {
perms: formatPermission(child),
children: formatChildren(child),
owners: formatOwners(child, flagInfo),
size: formatSize.call(this, f_h, child, maxSizeWidth),
modt: formatModtime(child),
name: /\s/.test(child.name) ? `'${child.name}'` : `${child.name}`
};
if (flagInfo.has('g')) cols.owners = '';
rows.push(LsEntryUtils.toString(cols));
if (flagInfo.has('r')) {
rows.push(LsEntryUtils.toString(parent), LsEntryUtils.toString(current));
} else {
rows.unshift(LsEntryUtils.toString(current), LsEntryUtils.toString(parent));
}
}
//TODO: Calculate the total size of contents in the node
@@ -170,12 +173,31 @@ function parsePerms(perms: NodePerms): string {
return parts.join('');
}
function formatOwners(node: TreeNode, flag: any): string {
function formatOwners(this: Bash, node: TreeNode, flag: any): string {
const owner: string = node.owner;
const group: string = node.group;
if (flag.has('G') || flag.has('o')) {
if (flag.has('n')) {
const uid: number = this.getUserByName(owner).uid;
return `${uid}`;
}
return `${owner}`;
}
if (flag.has('g')) {
return '';
if (flag.has('n')) {
const gid: number = this.getGroupByName(group).gid;
return `${gid}`;
}
return `${group}`;
}
if (flag.has('n')) {
const uid: number = this.getUserByName(owner).uid;
const gid: number = this.getGroupByName(group).gid;
return `${uid} ${gid}`;
}
return `${owner} ${group}`;
@@ -216,6 +238,19 @@ function formatModtime(node: TreeNode): string {
].join(' ');
}
function formatName(node: TreeNode, flag: any) {
let name: string;
const char: string = flag.has('Q') ? '"' : "'";
if (flag.has('N')) {
name = node.name;
} else {
name = /\s/.test(node.name) ? `${char}${node.name}${char}` : `${node.name}`; //test if any spaces specifically '\s' (escape and 's' for space)
}
return flag.has('p') && node.type === Type.Directory ? `${name}/` : name;
}
const checkFlags = (pFlags: string[], dFlags: string[]) => {
const flagSet = new Set(pFlags);
@@ -245,7 +280,8 @@ export const ls: ICommand = {
'o',
'n',
'N',
'L'
'L',
'm'
] as string[],
help: 'PATH TO HELP.MD',
root: false

View File

@@ -1,23 +1,23 @@
import type { TreeNode } from './fs';
export function asciiByteQSort(array: TreeNode[]) {
qSort(array, 0, array.length - 1);
export function asciiByteQSort(array: TreeNode[], reverse: boolean) {
qSort(array, 0, array.length - 1, reverse);
}
function qSort(array: TreeNode[], start: number, end: number) {
function qSort(array: TreeNode[], start: number, end: number, reverse: boolean) {
if (end <= start) return;
let pivot: number = partition(array, start, end);
qSort(array, start, pivot - 1);
qSort(array, pivot + 1, end);
let pivot: number = partition(array, start, end, reverse);
qSort(array, start, pivot - 1, reverse);
qSort(array, pivot + 1, end, reverse);
}
function partition(part: TreeNode[], start: number, end: number): number {
function partition(part: TreeNode[], start: number, end: number, reverse: boolean): number {
let pivot: TreeNode = part[end];
let i: number = start - 1;
for (let j = start; j <= end; j++) {
if (compareStrings(part[j].name, pivot.name) < 0) {
if (compareStrings(part[j].name, pivot.name, reverse) < 0) {
i++;
let temp = part[i];
part[i] = part[j];
@@ -32,7 +32,7 @@ function partition(part: TreeNode[], start: number, end: number): number {
return i;
}
function compareStrings(a: string, b: string): number {
function compareStrings(a: string, b: string, reverse: boolean): number {
const minLength = Math.min(a.length, b.length);
for (let i = 0; i < minLength; i++) {
@@ -40,8 +40,9 @@ function compareStrings(a: string, b: string): number {
const charCodeB = b.charCodeAt(i);
if (charCodeA !== charCodeB) {
return charCodeA - charCodeB;
return reverse ? charCodeB - charCodeA : charCodeA - charCodeB;
}
}
return a.length - b.length;
return reverse ? b.length - a.length : a.length - b.length;
}

View File

@@ -1,7 +1,6 @@
import { Bash, ExitCode, type Group, type User } from './bash';
import { Type, type TreeNode } from './fs';
import type { Char } from '../char';
import { ls } from './commands/ls';
import { cd } from './commands/cd';
export type ICommand = {
method: (this: Bash, args: CommandArgs) => Result;
@@ -63,6 +62,14 @@ export const PASSWD: User[] = [
gid: 1000,
home: '/home/user',
history: [] //TODO: Delete this and declare a new history array when logging the user in.
},
{
username: 'kamil',
passwd: '000',
uid: 1003,
gid: 1000,
home: '/home/kamil',
history: [] //TODO: Delete this and declare a new history array when logging the user in.
}
];
@@ -71,61 +78,6 @@ export const cmd_return = function (this: Bash, args: CommandArgs): Result {
return result;
};
export const cmd_cd = function (this: Bash, args: CommandArgs): Result {
let result: Result = { exitCode: ExitCode.ERROR };
const path = args.args[0];
let targetNode: TreeNode | null;
if (args.args.length > 1) return result; // Too many args
// if no args cd into home dir
if (args.args.length === 0) {
this.getFs().cwd = this.getFs().home;
result.exitCode = ExitCode.SUCCESS;
return result;
}
// if the arg is - cd make your current dir the prev dir and vice versa
if (args.args[0] === '-') {
[this.getFs().cwd, this.getFs().pwd] = [this.getFs().pwd, this.getFs().cwd];
result.exitCode = ExitCode.SUCCESS;
return result;
}
// Change the input STRING path from relative to absolute by replacing ~ with the home directory path
//TODO: Change that to a global function inside fs class to parse all possible path formats????? already exists, need to verify
let resolvedPath = path.startsWith('~')
? path.replace('~', this.getFs().pathArrayToString(this.getFs().home))
: path;
this.getFs().pwd = this.getFs().cwd;
targetNode = this.getFs()._getNodeByPathArray(this.getFs().resolvePath(resolvedPath)); // Conversion from STRING path to ARRAY
if (targetNode === null) return result;
if (targetNode.type !== Type.Directory) return result;
//if () return ExitCode.ERROR; // Check for read permissions on node and user
this.getFs().cwd = this.getFs().resolvePath(resolvedPath); // CD was successfull, change current dir to the verified target dir
result.exitCode = ExitCode.SUCCESS;
return result;
};
/* const compareArrays = (A: string[], B: string[]): { value: string; isInB: boolean }[] => {
const result = A.map((item) => ({ value: item, isInB: B.includes(item) }));
// Validate all B items are in A
const invalidItems = B.filter((item) => !A.includes(item));
if (invalidItems.length > 0) {
throw new Error(`Items '${invalidItems.join("', '")}' from B not found in A`);
}
return result;
}; */
export const COMMANDS = {
return: {
method: cmd_return,
@@ -133,12 +85,7 @@ export const COMMANDS = {
help: 'PATH TO HELP.MD',
root: false
},
cd: {
method: cmd_cd,
flags: [] as string[],
help: 'PATH TO HELP.MD',
root: false
},
cd,
ls
} as const satisfies Record<string, ICommand>;