forked from tools/josh
Use types and accessibility modifiers
This commit is contained in:
parent
28010a8cc9
commit
fbf0916a5f
402
js/commands.ts
402
js/commands.ts
|
@ -1,191 +1,181 @@
|
|||
import "./extensions.js"
|
||||
import {FileSystem, UrlFile} from "./fs.js"
|
||||
import {terminal} from "./terminal.js";
|
||||
import {Terminal, terminal} from "./terminal.js";
|
||||
|
||||
|
||||
export class Commands {
|
||||
private _terminal: any;
|
||||
private _fs: any;
|
||||
private _list: any;
|
||||
private readonly terminal: Terminal;
|
||||
private readonly fileSystem: FileSystem;
|
||||
private readonly commands: object;
|
||||
|
||||
|
||||
constructor(terminal, fileSystem) {
|
||||
this._terminal = terminal;
|
||||
this._fs = fileSystem;
|
||||
|
||||
this._list = {
|
||||
clear: {
|
||||
fun: this.clear,
|
||||
summary: `clear terminal output`,
|
||||
usage: `clear`,
|
||||
desc: `Clears all previous terminal output.`
|
||||
},
|
||||
cd: {
|
||||
fun: this.cd,
|
||||
summary: `change directory`,
|
||||
usage: `cd [DIRECTORY]`,
|
||||
desc: "" +
|
||||
`Changes the current working directory to [DIRECTORY].
|
||||
If [DIRECTORY] is empty, nothing happens.`.trimLines()
|
||||
},
|
||||
cp: {
|
||||
fun: this.cp,
|
||||
summary: `copy file`,
|
||||
usage: `cp SOURCE DESTINATION`,
|
||||
desc: "" +
|
||||
`Copies SOURCE to DESTINATION.
|
||||
SOURCE must be a file.
|
||||
If DESTINATION exists and is a directory, SOURCE is copied into the directory.`.trimLines()
|
||||
},
|
||||
echo: {
|
||||
fun: Commands.echo,
|
||||
summary: `display text`,
|
||||
usage: `echo [TEXT]`,
|
||||
desc: `Displays [TEXT].`.trimLines()
|
||||
},
|
||||
exit: {
|
||||
fun: this.exit,
|
||||
summary: `close session`,
|
||||
usage: `exit`,
|
||||
desc: `Closes the terminal session.`
|
||||
},
|
||||
help: {
|
||||
fun: this.help,
|
||||
summary: `display documentation`,
|
||||
usage: `help [COMMAND]`,
|
||||
desc: "" +
|
||||
`Displays help documentation for [COMMAND].
|
||||
If [COMMAND] is empty, a list of all commands is shown.`.trimLines()
|
||||
},
|
||||
ls: {
|
||||
fun: this.ls,
|
||||
summary: `list directory contents`,
|
||||
usage: `ls [DIRECTORY]`,
|
||||
desc: "" +
|
||||
`Displays the files and directories in [DIRECTORY].
|
||||
If [DIRECTORY] is empty, the files and directories in the current working directory are shown.`.trimLines()
|
||||
},
|
||||
man: {
|
||||
fun: this.man,
|
||||
summary: `display manual documentation pages`,
|
||||
usage: `man PAGE`,
|
||||
desc: `Displays the manual page with the name PAGE.`
|
||||
},
|
||||
mkdir: {
|
||||
fun: this.mkdir,
|
||||
summary: `make directories`,
|
||||
usage: `mkdir DIRECTORY...`,
|
||||
desc: "" +
|
||||
`Creates the directories given by DIRECTORY.
|
||||
constructor(terminal: Terminal, fileSystem: FileSystem) {
|
||||
this.terminal = terminal;
|
||||
this.fileSystem = fileSystem;
|
||||
this.commands = {
|
||||
clear: new Command(
|
||||
this.clear,
|
||||
`clear terminal output`,
|
||||
`clear`,
|
||||
`Clears all previous terminal output.`.trimLines()
|
||||
),
|
||||
cd: new Command(
|
||||
this.cd,
|
||||
`change directory`,
|
||||
`cd [DIRECTORY]`,
|
||||
`Changes the current working directory to [DIRECTORY].
|
||||
If [DIRECTORY] is empty, nothing happens.`.trimLines()
|
||||
),
|
||||
cp: new Command(
|
||||
this.cp,
|
||||
`copy file`,
|
||||
`cp SOURCE DESTINATION`,
|
||||
`Copies SOURCE to DESTINATION.
|
||||
SOURCE must be a file.
|
||||
If DESTINATION exists and is a directory, SOURCE is copied into the directory.`.trimLines()
|
||||
),
|
||||
echo: new Command(
|
||||
this.echo,
|
||||
`display text`,
|
||||
`echo [TEXT]`,
|
||||
`Displays [TEXT].`.trimLines()
|
||||
),
|
||||
exit: new Command(
|
||||
this.exit,
|
||||
`close session`,
|
||||
`exit`,
|
||||
`Closes the terminal session.`.trimLines()
|
||||
),
|
||||
help: new Command(
|
||||
this.help,
|
||||
`display documentation`,
|
||||
`help [COMMAND]`,
|
||||
`Displays help documentation for [COMMAND].
|
||||
If [COMMAND] is empty, a list of all commands is shown.`.trimLines()
|
||||
),
|
||||
ls: new Command(
|
||||
this.ls,
|
||||
`list directory contents`,
|
||||
`ls [DIRECTORY]`,
|
||||
`Displays the files and directories in [DIRECTORY].
|
||||
If [DIRECTORY] is empty, the files and directories in the current working directory are shown.`.trimLines()
|
||||
),
|
||||
man: new Command(
|
||||
this.man,
|
||||
`display manual documentation pages`,
|
||||
`man PAGE`,
|
||||
`Displays the manual page with the name PAGE.`.trimLines()
|
||||
),
|
||||
mkdir: new Command(
|
||||
this.mkdir,
|
||||
`make directories`,
|
||||
`mkdir DIRECTORY...`,
|
||||
`Creates the directories given by DIRECTORY.
|
||||
|
||||
If more than one directory is given, the directories are created in the order they are given in`.trimLines()
|
||||
},
|
||||
mv: {
|
||||
fun: this.mv,
|
||||
summary: `move file`,
|
||||
usage: `mv SOURCE DESTINATION`,
|
||||
desc: `Renames SOURCE to DESTINATION.`
|
||||
},
|
||||
open: {
|
||||
fun: this.open,
|
||||
summary: `open web page`,
|
||||
usage: `open [-b | --blank] FILE`,
|
||||
desc: "" +
|
||||
`Opens the web page linked to by FILE in this browser window.
|
||||
If more than one directory is given, the directories are created in the order they are given in`.trimLines()
|
||||
),
|
||||
mv: new Command(
|
||||
this.mv,
|
||||
`move file`,
|
||||
`mv SOURCE DESTINATION`,
|
||||
`Renames SOURCE to DESTINATION.`.trimLines()
|
||||
),
|
||||
open: new Command(
|
||||
this.open,
|
||||
`open web page`,
|
||||
`open [-b | --blank] FILE`,
|
||||
`Opens the web page linked to by FILE in this browser window.
|
||||
|
||||
If -b or --blank is set, the web page is opened in a new tab.`.trimLines()
|
||||
},
|
||||
poweroff: {
|
||||
fun: this.poweroff,
|
||||
summary: `close down the system`,
|
||||
usage: `poweroff`,
|
||||
desc: `Automated shutdown procedure to nicely notify users when the system is shutting down.`
|
||||
},
|
||||
pwd: {
|
||||
fun: this.pwd,
|
||||
summary: `print working directory`,
|
||||
usage: `pwd`,
|
||||
desc: `Displays the current working directory.`
|
||||
},
|
||||
rm: {
|
||||
fun: this.rm,
|
||||
summary: `remove file`,
|
||||
usage: `rm [-f | --force] [-r | -R | --recursive] [--no-preserve-root] FILE...`,
|
||||
desc:
|
||||
`Removes the files given by FILE.
|
||||
If -b or --blank is set, the web page is opened in a new tab.`.trimLines()
|
||||
),
|
||||
poweroff: new Command(
|
||||
this.poweroff,
|
||||
`close down the system`,
|
||||
`poweroff`,
|
||||
`Automated shutdown procedure to nicely notify users when the system is shutting down.`.trimLines()
|
||||
),
|
||||
pwd: new Command(
|
||||
this.pwd,
|
||||
`print working directory`,
|
||||
`pwd`,
|
||||
`Displays the current working directory.`.trimLines()
|
||||
),
|
||||
rm: new Command(
|
||||
this.rm,
|
||||
`remove file`,
|
||||
`rm [-f | --force] [-r | -R | --recursive] [--no-preserve-root] FILE...`,
|
||||
`Removes the files given by FILE.
|
||||
|
||||
If more than one file is given, the files are removed in the order they are given in.
|
||||
|
||||
If -f or --force is set, no warning is given if a file could not be removed.
|
||||
|
||||
If -r, -R, or --recursive is set, files and directories are removed recursively.
|
||||
|
||||
Unless --no-preserve-root is set, the root directory cannot be removed.`.trimLines()
|
||||
),
|
||||
rmdir: new Command(
|
||||
this.rmdir,
|
||||
`remove directories`,
|
||||
`rmdir DIRECTORY...`,
|
||||
`Removes the directories given by DIRECTORY.
|
||||
|
||||
If more than one file is given, the files are removed in the order they are given in.
|
||||
If more than one directory is given, the directories are removed in the order they are given in.`.trimLines()
|
||||
),
|
||||
touch: new Command(
|
||||
this.touch,
|
||||
`change file timestamps`,
|
||||
`touch FILE...`,
|
||||
`Update the access and modification times of each FILE to the current time.
|
||||
|
||||
If -f or --force is set, no warning is given if a file could not be removed.
|
||||
|
||||
If -r, -R, or --recursive is set, files and directories are removed recursively.
|
||||
|
||||
Unless --no-preserve-root is set, the root directory cannot be removed.`.trimLines()
|
||||
},
|
||||
rmdir: {
|
||||
fun: this.rmdir,
|
||||
summary: `remove directories`,
|
||||
usage: `rmdir DIRECTORY...`,
|
||||
desc: "" +
|
||||
`Removes the directories given by DIRECTORY.
|
||||
|
||||
If more than one directory is given, the directories are removed in the order they are given in.`.trimLines()
|
||||
},
|
||||
touch: {
|
||||
fun: this.touch,
|
||||
summary: `change file timestamps`,
|
||||
usage: `touch FILE...`,
|
||||
desc: "" +
|
||||
`Update the access and modification times of each FILE to the current time.
|
||||
|
||||
If a file does not exist, it is created.`.trimLines()
|
||||
}
|
||||
If a file does not exist, it is created.`.trimLines()
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
parse(input) {
|
||||
parse(input: string): string {
|
||||
const args = new InputArgs(input);
|
||||
|
||||
if (Object.keys(this._list).indexOf(args.getCommand()) >= 0)
|
||||
return this._list[args.getCommand()].fun.bind(this)(args);
|
||||
else if (args.getCommand().trim() === "")
|
||||
if (args.command.trim() === "")
|
||||
return "";
|
||||
else if (this.commands.hasOwnProperty(args.command))
|
||||
return this.commands[args.command].fun.bind(this)(args);
|
||||
else
|
||||
return `Unknown command '${args.getCommand()}'`
|
||||
return `Unknown command '${args.command}'`;
|
||||
}
|
||||
|
||||
|
||||
cd(args) {
|
||||
return this._fs.cd(args.getArg(0));
|
||||
private cd(input: InputArgs): string {
|
||||
return this.fileSystem.cd(input.getArg(0));
|
||||
}
|
||||
|
||||
cp(args) {
|
||||
return this._fs.cp(args.getArg(0), args.getArg(1));
|
||||
private cp(input: InputArgs): string {
|
||||
return this.fileSystem.cp(input.getArg(0), input.getArg(1));
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._terminal.clear();
|
||||
private clear(): string {
|
||||
this.terminal.clear();
|
||||
return "";
|
||||
}
|
||||
|
||||
static echo(args) {
|
||||
return args.getArgs()
|
||||
private echo(input): string {
|
||||
return input.args
|
||||
.join(" ")
|
||||
.replace("hunter2", "*******");
|
||||
}
|
||||
|
||||
exit() {
|
||||
this._terminal.logOut();
|
||||
private exit(): string {
|
||||
this.terminal.logOut();
|
||||
return "";
|
||||
}
|
||||
|
||||
help(args) {
|
||||
const command = args.getArg(0, "").toLowerCase();
|
||||
const commandNames = Object.keys(this._list);
|
||||
private help(input: InputArgs): string {
|
||||
const command = input.getArg(0, "").toLowerCase();
|
||||
const commandNames = Object.keys(this.commands);
|
||||
|
||||
if (commandNames.indexOf(command) >= 0) {
|
||||
const info = this._list[command];
|
||||
const info = this.commands[command];
|
||||
|
||||
return "" +
|
||||
`${command} - ${info.summary}
|
||||
|
@ -198,7 +188,7 @@ export class Commands {
|
|||
} else {
|
||||
const commandWidth = Math.max.apply(null, commandNames.map(it => it.length)) + 4;
|
||||
const commandEntries = commandNames.map(
|
||||
it => `${it.padEnd(commandWidth, ' ')}${this._list[it].summary}`
|
||||
it => `${it.padEnd(commandWidth, ' ')}${this.commands[it].summary}`
|
||||
);
|
||||
|
||||
return "" +
|
||||
|
@ -211,44 +201,44 @@ export class Commands {
|
|||
}
|
||||
}
|
||||
|
||||
ls(args) {
|
||||
return this._fs.ls(args.getArg(0));
|
||||
private ls(input: InputArgs): string {
|
||||
return this.fileSystem.ls(input.getArg(0));
|
||||
}
|
||||
|
||||
man(args) {
|
||||
if (args.getArgs().length === 0)
|
||||
private man(args: InputArgs): string {
|
||||
if (args.args.length === 0)
|
||||
return "What manual page do you want?";
|
||||
else if (Object.keys(this._list).indexOf(args.getArg(0)) < 0)
|
||||
else if (Object.keys(this.commands).indexOf(args.getArg(0)) < 0)
|
||||
return `No manual entry for ${args.getArg(0)}`;
|
||||
else
|
||||
return this.help(args);
|
||||
}
|
||||
|
||||
mkdir(args) {
|
||||
return this._fs.mkdirs(args.getArgs());
|
||||
private mkdir(args: InputArgs): string {
|
||||
return this.fileSystem.mkdirs(args.args);
|
||||
}
|
||||
|
||||
mv(args) {
|
||||
return this._fs.mv(args.getArg(0), args.getArg(1));
|
||||
private mv(args: InputArgs): string {
|
||||
return this.fileSystem.mv(args.getArg(0), args.getArg(1));
|
||||
}
|
||||
|
||||
open(args) {
|
||||
private open(args: InputArgs): string {
|
||||
const fileName = args.getArg(0);
|
||||
const target = args.hasAnyOption(["b", "blank"]) ? "_blank" : "_self";
|
||||
|
||||
const file = this._fs._getFile(fileName);
|
||||
if (file === undefined)
|
||||
const node = this.fileSystem.getNode(fileName);
|
||||
if (node === undefined)
|
||||
return `The file '${fileName}' does not exist`;
|
||||
if (!FileSystem.isFile(file))
|
||||
if (!(node instanceof File))
|
||||
return `'${fileName}' is not a file`;
|
||||
if (!(file instanceof UrlFile))
|
||||
if (!(node instanceof UrlFile))
|
||||
return `Could not open '${fileName}'`;
|
||||
|
||||
window.open(file.url, target);
|
||||
window.open(node.url, target);
|
||||
return "";
|
||||
}
|
||||
|
||||
poweroff() {
|
||||
private poweroff(): string {
|
||||
const date = new Date();
|
||||
date.setSeconds(date.getSeconds() + 30);
|
||||
document.cookie = `poweroff=true; expires=${date.toUTCString()}; path=/`;
|
||||
|
@ -257,7 +247,7 @@ export class Commands {
|
|||
return "" +
|
||||
`Shutdown NOW!
|
||||
|
||||
*** FINAL System shutdown message from ${terminal._user}@fwdekker.com ***
|
||||
*** FINAL System shutdown message from ${terminal.currentUser}@fwdekker.com ***
|
||||
|
||||
System going down IMMEDIATELY
|
||||
|
||||
|
@ -265,39 +255,55 @@ export class Commands {
|
|||
System shutdown time has arrived`.trimLines();
|
||||
}
|
||||
|
||||
pwd() {
|
||||
return this._fs.pwd;
|
||||
private pwd(): string {
|
||||
return this.fileSystem.pwd;
|
||||
}
|
||||
|
||||
rm(args) {
|
||||
return this._fs.rms(
|
||||
args.getArgs(),
|
||||
private rm(args: InputArgs): string {
|
||||
return this.fileSystem.rms(
|
||||
args.args,
|
||||
args.hasAnyOption(["f", "force"]),
|
||||
args.hasAnyOption(["r", "R", "recursive"]),
|
||||
args.hasOption("no-preserve-root")
|
||||
);
|
||||
}
|
||||
|
||||
rmdir(args) {
|
||||
return this._fs.rmdirs(args.getArgs());
|
||||
private rmdir(args: InputArgs): string {
|
||||
return this.fileSystem.rmdirs(args.args);
|
||||
}
|
||||
|
||||
touch(args) {
|
||||
return this._fs.createFiles(args.getArgs());
|
||||
private touch(args: InputArgs): string {
|
||||
return this.fileSystem.createFiles(args.args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Command {
|
||||
readonly fun: (args: InputArgs) => string;
|
||||
readonly summary: string;
|
||||
readonly usage: string;
|
||||
readonly desc: string;
|
||||
|
||||
|
||||
constructor(fun: (args: InputArgs) => string, summary: string, usage: string, desc: string) {
|
||||
this.fun = fun;
|
||||
this.summary = summary;
|
||||
this.usage = usage;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
|
||||
class InputArgs {
|
||||
private _command: any;
|
||||
private _options: any;
|
||||
private _args: any;
|
||||
readonly command: string;
|
||||
private readonly _options: object;
|
||||
private readonly _args: string[];
|
||||
|
||||
|
||||
constructor(input) {
|
||||
constructor(input: string) {
|
||||
const inputParts = (input.match(/("[^"]+"|[^"\s]+)/g) || [])
|
||||
.map(it => it.replace(/^"/, "").replace(/"$/, ""));
|
||||
|
||||
this._command = (inputParts[0] || "").toLowerCase();
|
||||
this.command = (inputParts[0] || "").toLowerCase();
|
||||
|
||||
this._options = {};
|
||||
let i;
|
||||
|
@ -320,9 +326,7 @@ class InputArgs {
|
|||
// -opq
|
||||
const argNames = argParts[0].substr(1).split("");
|
||||
|
||||
argNames.forEach(argName => {
|
||||
this._options[argName] = "";
|
||||
});
|
||||
argNames.forEach(argName => this._options[argName] = "");
|
||||
} else {
|
||||
// Invalid
|
||||
throw "Cannot assign value to multiple options!";
|
||||
|
@ -339,35 +343,37 @@ class InputArgs {
|
|||
}
|
||||
|
||||
|
||||
getArgs() {
|
||||
get options(): object {
|
||||
return Object.assign({}, this._options);
|
||||
}
|
||||
|
||||
get args(): string[] {
|
||||
return this._args.slice();
|
||||
}
|
||||
|
||||
getArg(index, def) {
|
||||
|
||||
getArg(index: number, def: string = undefined): string {
|
||||
return (def === undefined)
|
||||
? this._args[index]
|
||||
: this._args[index] || def;
|
||||
: (this.hasArg(index) ? this._args[index] : def);
|
||||
}
|
||||
|
||||
hasArg(index) {
|
||||
return (this._args[index] !== undefined);
|
||||
hasArg(index: number): boolean {
|
||||
return index >= 0 && index < this._args.length;
|
||||
}
|
||||
|
||||
getCommand() {
|
||||
return this._command;
|
||||
}
|
||||
|
||||
getOption(key, def = undefined) {
|
||||
getOption(key: string, def: string = undefined) {
|
||||
return (def === undefined)
|
||||
? this._options[key]
|
||||
: this._options[key] || def;
|
||||
: (this.hasOption(key) ? this._options[key] : def);
|
||||
}
|
||||
|
||||
hasOption(key) {
|
||||
return (this.getOption(key) !== undefined);
|
||||
hasOption(key: string): boolean {
|
||||
return this._options.hasOwnProperty(key);
|
||||
}
|
||||
|
||||
hasAnyOption(keys) {
|
||||
hasAnyOption(keys: string[]): boolean {
|
||||
for (let i = 0; i < keys.length; i++)
|
||||
if (this.hasOption(keys[i]))
|
||||
return true;
|
||||
|
|
284
js/fs.ts
284
js/fs.ts
|
@ -1,16 +1,15 @@
|
|||
import {emptyFunction} from "./shared.js";
|
||||
import {relToAbs} from "./terminal.js";
|
||||
|
||||
|
||||
export class FileSystem {
|
||||
pwd: string;
|
||||
private _root: Directory;
|
||||
private root: Directory;
|
||||
private files: Directory;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.pwd = "/";
|
||||
this._root = new Directory({
|
||||
this.root = new Directory({
|
||||
personal: new Directory({
|
||||
steam: new UrlFile("https://steamcommunity.com/id/Waflix"),
|
||||
nukapedia: new UrlFile("http://fallout.wikia.com/wiki/User:FDekker"),
|
||||
|
@ -32,28 +31,33 @@ export class FileSystem {
|
|||
}
|
||||
|
||||
|
||||
_getFile(pathString) {
|
||||
getNode(pathString: string): Node {
|
||||
const path = new Path(this.pwd, pathString);
|
||||
|
||||
let file = this._root;
|
||||
let node: Node = this.root;
|
||||
path.parts.forEach(part => {
|
||||
if (part === "")
|
||||
if (part === "" || node === undefined || node instanceof File)
|
||||
return;
|
||||
if (file === undefined)
|
||||
return;
|
||||
if (FileSystem.isFile(file)) {
|
||||
file = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
file = file.getNode(part);
|
||||
if (node instanceof Directory)
|
||||
node = node.getNode(part);
|
||||
else
|
||||
throw "Node must be file or directory.";
|
||||
});
|
||||
|
||||
return file;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets navigation in the file system.
|
||||
*/
|
||||
reset() {
|
||||
this.pwd = "/";
|
||||
this.files = this.root;
|
||||
}
|
||||
|
||||
|
||||
_executeForEach(inputs, fun) {
|
||||
private executeForEach(inputs: string[], fun: (string) => string): string {
|
||||
const outputs = [];
|
||||
|
||||
inputs.forEach(input => {
|
||||
|
@ -67,70 +71,47 @@ export class FileSystem {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true iff {@code node} represents a directory.
|
||||
*
|
||||
* @param node {Object} a node from the file system
|
||||
* @returns {boolean} true iff {@code node} represents a directory
|
||||
*/
|
||||
static isDirectory(node) {
|
||||
return node instanceof Directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true iff {@code node} represents a file.
|
||||
*
|
||||
* @param node {Object} an object from the file system
|
||||
* @returns {boolean} true iff {@code node} represents a file
|
||||
*/
|
||||
static isFile(node) {
|
||||
return node instanceof File;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the current directory to {@code path}, if it exists.
|
||||
*
|
||||
* @param pathString the absolute or relative path to change the current directory to
|
||||
* @returns {string} an empty string if the change was successful, or an error message explaining what went wrong
|
||||
*/
|
||||
cd(pathString) {
|
||||
if (pathString === undefined) {
|
||||
cd(pathString: string): string {
|
||||
if (pathString === undefined)
|
||||
return "";
|
||||
}
|
||||
|
||||
const path = new Path(this.pwd, pathString);
|
||||
|
||||
const file = this._getFile(path.path);
|
||||
if (file === undefined)
|
||||
const node = this.getNode(path.path);
|
||||
if (node === undefined)
|
||||
return `The directory '${path.path}' does not exist`;
|
||||
if (!FileSystem.isDirectory(file))
|
||||
return `'${path.path}' is not a directory`;
|
||||
if (!(node instanceof Directory))
|
||||
return `'${path.path}' is not a directory.`;
|
||||
|
||||
this.pwd = path.path;
|
||||
this.files = file;
|
||||
|
||||
this.files = node;
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty file at {@code path} if it does not exist.
|
||||
*
|
||||
* @param path the path to create a file at if it does not exist
|
||||
* @param pathString the path to create a file at if it does not exist
|
||||
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
||||
*/
|
||||
createFile(path) {
|
||||
path = new Path(this.pwd, path);
|
||||
private createFile(pathString: string): string {
|
||||
const path = new Path(this.pwd, pathString);
|
||||
|
||||
const headNode = this._getFile(path.head);
|
||||
const headNode = this.getNode(path.head);
|
||||
if (headNode === undefined)
|
||||
return `The directory '${path.head}' does not exist`;
|
||||
if (!FileSystem.isDirectory(headNode))
|
||||
if (!(headNode instanceof Directory))
|
||||
return `${path.head} is not a directory`;
|
||||
|
||||
const tailNode = headNode.getNode(path.tail);
|
||||
if (tailNode !== undefined)
|
||||
return "";
|
||||
return ""; // File already exists
|
||||
|
||||
headNode.addNode(path.tail, new File());
|
||||
return "";
|
||||
|
@ -142,10 +123,8 @@ export class FileSystem {
|
|||
* @param paths {string[]} the absolute or relative paths to the files to be created
|
||||
* @returns {string} the warnings generated during creation of the files
|
||||
*/
|
||||
createFiles(paths) {
|
||||
return this._executeForEach(paths, path => {
|
||||
return this.createFile(path);
|
||||
});
|
||||
createFiles(paths: string[]): string {
|
||||
return this.executeForEach(paths, path => this.createFile(path));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,13 +138,13 @@ export class FileSystem {
|
|||
* @param destinationString {string} the absolute or relative path to the destination
|
||||
* @returns {string} an empty string if the copy was successful, or a message explaining what went wrong
|
||||
*/
|
||||
cp(sourceString, destinationString) {
|
||||
cp(sourceString: string, destinationString: string): string {
|
||||
const sourcePath = new Path(this.pwd, sourceString);
|
||||
const sourceTailNode = this._getFile(sourcePath.path);
|
||||
const sourceTailNode = this.getNode(sourcePath.path);
|
||||
|
||||
const destinationPath = new Path(this.pwd, destinationString);
|
||||
const destinationHeadNode = this._getFile(destinationPath.head);
|
||||
const destinationTailNode = this._getFile(destinationPath.path);
|
||||
const destinationHeadNode = this.getNode(destinationPath.head);
|
||||
const destinationTailNode = this.getNode(destinationPath.path);
|
||||
|
||||
if (sourceTailNode === undefined)
|
||||
return `The file '${sourcePath.path}' does not exist`;
|
||||
|
@ -198,27 +177,27 @@ export class FileSystem {
|
|||
* @param pathString {string} the absolute or relative path to the directory to return
|
||||
* @returns {Object} the directory at {@code path}, or the current directory if no path is given
|
||||
*/
|
||||
ls(pathString) {
|
||||
ls(pathString: string): string {
|
||||
const path = new Path(this.pwd, pathString);
|
||||
|
||||
const dir = this._getFile(path.path);
|
||||
if (dir === undefined)
|
||||
const node = this.getNode(path.path);
|
||||
if (node === undefined)
|
||||
return `The directory '${path.path}' does not exist`;
|
||||
if (!FileSystem.isDirectory(dir))
|
||||
if (!(node instanceof Directory))
|
||||
return `'${path.path}' is not a directory`;
|
||||
|
||||
const dirList = [new Directory({}).nameString("."), new Directory({}).nameString("..")];
|
||||
const fileList = [];
|
||||
|
||||
const nodes = dir.getNodes();
|
||||
const nodes = node.nodes;
|
||||
Object.keys(nodes)
|
||||
.sortAlphabetically((x) => x)
|
||||
.forEach(name => {
|
||||
const node = nodes[name];
|
||||
|
||||
if (FileSystem.isDirectory(node))
|
||||
if (node instanceof Directory)
|
||||
dirList.push(node.nameString(name));
|
||||
else if (FileSystem.isFile(node))
|
||||
else if (node instanceof File)
|
||||
fileList.push(node.nameString(name));
|
||||
else
|
||||
throw `${name} is neither a file nor a directory!`;
|
||||
|
@ -233,13 +212,13 @@ export class FileSystem {
|
|||
* @param pathString {string} the absolute or relative path to the directory to create
|
||||
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
||||
*/
|
||||
mkdir(pathString) {
|
||||
private mkdir(pathString: string): string {
|
||||
const path = new Path(pathString, undefined);
|
||||
|
||||
const headNode = this._getFile(path.head);
|
||||
const headNode = this.getNode(path.head);
|
||||
if (headNode === undefined)
|
||||
return `The directory '${path.head}' does not exist`;
|
||||
if (!FileSystem.isDirectory(headNode))
|
||||
if (!(headNode instanceof Directory))
|
||||
return `'${path.head}' is not a directory`;
|
||||
if (headNode.getNode(path.tail))
|
||||
return `The directory '${path.tail}' already exists`;
|
||||
|
@ -254,8 +233,8 @@ export class FileSystem {
|
|||
* @param paths {string[]} the absolute or relative paths to the directories to create
|
||||
* @returns {string} the warnings generated during creation of the directories
|
||||
*/
|
||||
mkdirs(paths) {
|
||||
return this._executeForEach(paths, this.mkdir.bind(this));
|
||||
mkdirs(paths: string[]): string {
|
||||
return this.executeForEach(paths, this.mkdir.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -269,15 +248,17 @@ export class FileSystem {
|
|||
* @param destinationString {string} the absolute or relative path to the destination
|
||||
* @returns {string} an empty string if the move was successful, or a message explaining what went wrong
|
||||
*/
|
||||
mv(sourceString, destinationString) {
|
||||
mv(sourceString: string, destinationString: string): string {
|
||||
const sourcePath = new Path(sourceString, undefined);
|
||||
const sourceHeadNode = this._getFile(sourcePath.head);
|
||||
const sourceTailNode = this._getFile(sourcePath.path);
|
||||
const sourceHeadNode = this.getNode(sourcePath.head);
|
||||
const sourceTailNode = this.getNode(sourcePath.path);
|
||||
|
||||
const destinationPath = new Path(destinationString, undefined);
|
||||
const destinationHeadNode = this._getFile(destinationPath.head);
|
||||
const destinationTailNode = this._getFile(destinationPath.path);
|
||||
const destinationHeadNode = this.getNode(destinationPath.head);
|
||||
const destinationTailNode = this.getNode(destinationPath.path);
|
||||
|
||||
if (!(sourceHeadNode instanceof Directory))
|
||||
return `The path '${sourcePath.head}' does not point to a directory`;
|
||||
if (sourceTailNode === undefined)
|
||||
return `The file '${sourcePath.path}' does not exist`;
|
||||
if (destinationHeadNode === undefined)
|
||||
|
@ -297,20 +278,11 @@ export class FileSystem {
|
|||
return `The file '${targetName}' already exists`;
|
||||
|
||||
sourceHeadNode.removeNode(sourceTailNode);
|
||||
targetNode.addNode(sourceTailNode, undefined);
|
||||
sourceTailNode.name = targetName;
|
||||
targetNode.addNode(targetName, sourceTailNode);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets navigation in the file system.
|
||||
*/
|
||||
reset() {
|
||||
this.pwd = "/";
|
||||
this.files = this._root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a file from the file system.
|
||||
*
|
||||
|
@ -320,20 +292,20 @@ export class FileSystem {
|
|||
* @param noPreserveRoot {boolean} false if the root directory should not be removed
|
||||
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
||||
*/
|
||||
rm(pathString, force = false, recursive = false, noPreserveRoot = false) {
|
||||
private rm(pathString: string, force: boolean = false, recursive: boolean = false, noPreserveRoot: boolean = false): string {
|
||||
const path = new Path(pathString, undefined);
|
||||
|
||||
const parentNode = this._getFile(path.head);
|
||||
const parentNode = this.getNode(path.head);
|
||||
if (parentNode === undefined)
|
||||
return force
|
||||
? ""
|
||||
: `The directory '${path.head}' does not exist`;
|
||||
if (!FileSystem.isDirectory(parentNode))
|
||||
if (!(parentNode instanceof Directory))
|
||||
return force
|
||||
? ""
|
||||
: `'${path.head}' is not a directory`;
|
||||
|
||||
const childNode = this._getFile(path.path);
|
||||
const childNode = this.getNode(path.path);
|
||||
if (childNode === undefined)
|
||||
return force
|
||||
? ""
|
||||
|
@ -342,7 +314,7 @@ export class FileSystem {
|
|||
if (recursive) {
|
||||
if (path.path === "/")
|
||||
if (noPreserveRoot)
|
||||
this._root = new Directory();
|
||||
this.root = new Directory();
|
||||
else
|
||||
return "'/' cannot be removed";
|
||||
else
|
||||
|
@ -367,8 +339,8 @@ export class FileSystem {
|
|||
* @param noPreserveRoot {boolean} false if the root directory should not be removed
|
||||
* @returns {string} the warnings generated during removal of the directories
|
||||
*/
|
||||
rms(paths, force = false, recursive = false, noPreserveRoot = false) {
|
||||
return this._executeForEach(paths, path => {
|
||||
rms(paths: string[], force: boolean = false, recursive: boolean = false, noPreserveRoot: boolean = false): string {
|
||||
return this.executeForEach(paths, path => {
|
||||
return this.rm(path, force, recursive, noPreserveRoot);
|
||||
});
|
||||
}
|
||||
|
@ -379,28 +351,28 @@ export class FileSystem {
|
|||
* @param pathString {string} the absolute or relative path to the directory to be removed
|
||||
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
||||
*/
|
||||
rmdir(pathString) {
|
||||
private rmdir(pathString: string): string {
|
||||
const path = new Path(pathString, undefined);
|
||||
|
||||
if (path.path === "/") {
|
||||
if (this._root.getNodeCount() > 0)
|
||||
if (this.root.nodeCount > 0)
|
||||
return `The directory is not empty.`;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
const parentDir = this._getFile(path.head);
|
||||
const parentDir = this.getNode(path.head);
|
||||
if (parentDir === undefined)
|
||||
return `The directory '${path.head}' does not exist`;
|
||||
if (!FileSystem.isDirectory(parentDir))
|
||||
if (!(parentDir instanceof Directory))
|
||||
return `'${path.head}' is not a directory`;
|
||||
|
||||
const childDir = parentDir.getNode(path.tail);
|
||||
if (childDir === undefined)
|
||||
return `The directory '${path.tail}' does not exist`;
|
||||
if (!FileSystem.isDirectory(childDir))
|
||||
if (!(childDir instanceof Directory))
|
||||
return `'${path.tail}' is not a directory`;
|
||||
if (childDir.getNodeCount() > 0)
|
||||
if (childDir.nodeCount > 0)
|
||||
return `The directory is not empty`;
|
||||
|
||||
parentDir.removeNode(childDir);
|
||||
|
@ -413,22 +385,20 @@ export class FileSystem {
|
|||
* @param paths {string[]} the absolute or relative paths to the directories to be removed
|
||||
* @returns {string} the warnings generated during removal of the directories
|
||||
*/
|
||||
rmdirs(paths) {
|
||||
return this._executeForEach(paths, path => {
|
||||
return this.rmdir(path);
|
||||
});
|
||||
rmdirs(paths: string[]): string {
|
||||
return this.executeForEach(paths, path => this.rmdir(path));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Path {
|
||||
private _path: string;
|
||||
private _parts: string[];
|
||||
private _head: string;
|
||||
private _tail: string;
|
||||
private readonly _parts: string[];
|
||||
readonly path: string;
|
||||
readonly head: string;
|
||||
readonly tail: string;
|
||||
|
||||
|
||||
constructor(currentPath, relativePath) {
|
||||
constructor(currentPath: string, relativePath: string) {
|
||||
let path;
|
||||
if (relativePath === undefined)
|
||||
path = currentPath;
|
||||
|
@ -437,78 +407,62 @@ export class Path {
|
|||
else
|
||||
path = `${currentPath}/${relativePath}`;
|
||||
|
||||
|
||||
this._path = `${path}/`
|
||||
this.path = `${path}/`
|
||||
.replaceAll(/\/\.\//, "/")
|
||||
.replaceAll(/(\/+)([^./]+)(\/+)(\.\.)(\/+)/, "/")
|
||||
.replaceAll(/\/{2,}/, "/")
|
||||
.replace(/^\/?\.?\.\/$/, "/")
|
||||
.toString();
|
||||
|
||||
this._parts = this._path.split("/");
|
||||
this._head = this._parts.slice(0, -2).join("/");
|
||||
this._tail = this._parts.slice(0, -1).slice(-1).join("/");
|
||||
this._parts = this.path.split("/");
|
||||
this.head = this.parts.slice(0, -2).join("/");
|
||||
this.tail = this.parts.slice(0, -1).slice(-1).join("/");
|
||||
}
|
||||
|
||||
|
||||
get path() {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
get parts() {
|
||||
get parts(): string[] {
|
||||
return this._parts.slice();
|
||||
}
|
||||
|
||||
get head() {
|
||||
return this._head;
|
||||
}
|
||||
|
||||
get tail() {
|
||||
return this._tail;
|
||||
}
|
||||
}
|
||||
|
||||
export class Node {
|
||||
copy() {
|
||||
throw "Cannot execute abstract method!";
|
||||
}
|
||||
export abstract class Node {
|
||||
abstract copy(): Node;
|
||||
|
||||
nameString(name) {
|
||||
throw "Cannot execute abstract method!";
|
||||
}
|
||||
abstract nameString(name: string): string;
|
||||
|
||||
visit(fun, pre, post) {
|
||||
throw "Cannot execute abstract method!";
|
||||
}
|
||||
abstract visit(fun: (node: Node) => void, pre: (node: Node) => void, post: (node: Node) => void);
|
||||
}
|
||||
|
||||
export class Directory extends Node {
|
||||
private _parent: this;
|
||||
private _nodes: {};
|
||||
name: string;
|
||||
private readonly _nodes: object;
|
||||
// noinspection TypeScriptFieldCanBeMadeReadonly: False positive
|
||||
private _parent: Directory;
|
||||
|
||||
|
||||
constructor(nodes = {}) {
|
||||
constructor(nodes: object = {}) {
|
||||
super();
|
||||
|
||||
this._parent = this;
|
||||
this._nodes = nodes;
|
||||
|
||||
Object.keys(this._nodes).forEach(name => {
|
||||
this._nodes[name]._parent = this
|
||||
});
|
||||
Object.keys(this._nodes).forEach(name => this._nodes[name]._parent = this);
|
||||
}
|
||||
|
||||
|
||||
getNodes() {
|
||||
get nodes(): object {
|
||||
return Object.assign({}, this._nodes);
|
||||
}
|
||||
|
||||
getNodeCount() {
|
||||
get nodeCount(): number {
|
||||
return Object.keys(this._nodes).length;
|
||||
}
|
||||
|
||||
getNode(name) {
|
||||
get parent(): Directory {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
|
||||
getNode(name: string): Node {
|
||||
switch (name) {
|
||||
case ".":
|
||||
return this;
|
||||
|
@ -519,15 +473,14 @@ export class Directory extends Node {
|
|||
}
|
||||
}
|
||||
|
||||
addNode(name, node) {
|
||||
if (node instanceof Directory) {
|
||||
addNode(name: string, node: Node) {
|
||||
if (node instanceof Directory)
|
||||
node._parent = this;
|
||||
}
|
||||
|
||||
this._nodes[name] = node;
|
||||
}
|
||||
|
||||
removeNode(nodeOrName) {
|
||||
removeNode(nodeOrName: Node | string) {
|
||||
if (nodeOrName instanceof Node) {
|
||||
const name = Object.keys(this._nodes).find(key => this._nodes[key] === nodeOrName);
|
||||
delete this._nodes[name];
|
||||
|
@ -537,23 +490,24 @@ export class Directory extends Node {
|
|||
}
|
||||
|
||||
|
||||
copy() {
|
||||
const copy = new Directory(Object.assign({}, this._nodes));
|
||||
copy(): Directory {
|
||||
const copy = new Directory(this.nodes);
|
||||
copy._parent = undefined;
|
||||
return copy;
|
||||
}
|
||||
|
||||
nameString(name) {
|
||||
nameString(name: string): string {
|
||||
// @ts-ignore: Defined in `terminal.ts`
|
||||
return `<a href="#" class="dirLink" onclick="run('cd ${relToAbs(name)}/');run('ls');">${name}/</a>`;
|
||||
}
|
||||
|
||||
visit(fun, pre: (dir: Directory) => void = emptyFunction, post: (dir: Directory) => void = emptyFunction) {
|
||||
visit(fun: (node: Node) => void,
|
||||
pre: (node: Node) => void = emptyFunction,
|
||||
post: (node: Node) => void = emptyFunction) {
|
||||
pre(this);
|
||||
|
||||
fun(this);
|
||||
Object.keys(this._nodes).forEach(name => {
|
||||
this._nodes[name].visit(fun, pre, post);
|
||||
});
|
||||
Object.keys(this._nodes).forEach(name => this._nodes[name].visit(fun, pre, post));
|
||||
|
||||
post(this);
|
||||
}
|
||||
|
@ -565,15 +519,17 @@ export class File extends Node {
|
|||
}
|
||||
|
||||
|
||||
copy() {
|
||||
copy(): File {
|
||||
return new File();
|
||||
}
|
||||
|
||||
nameString(name) {
|
||||
nameString(name: string): string {
|
||||
return name;
|
||||
}
|
||||
|
||||
visit(fun, pre: (dir: File) => void = emptyFunction, post: (dir: File) => void = emptyFunction) {
|
||||
visit(fun: (node: Node) => void,
|
||||
pre: (node: Node) => void = emptyFunction,
|
||||
post: (node: Node) => void = emptyFunction) {
|
||||
pre(this);
|
||||
fun(this);
|
||||
post(this);
|
||||
|
@ -581,21 +537,21 @@ export class File extends Node {
|
|||
}
|
||||
|
||||
export class UrlFile extends File {
|
||||
url: any;
|
||||
readonly url: string;
|
||||
|
||||
|
||||
constructor(url) {
|
||||
constructor(url: string) {
|
||||
super();
|
||||
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
|
||||
copy() {
|
||||
copy(): UrlFile {
|
||||
return new UrlFile(this.url);
|
||||
}
|
||||
|
||||
nameString(name) {
|
||||
nameString(name: string): string {
|
||||
return `<a href="${this.url}" class="fileLink">${name}</a>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,10 @@ export const emptyFunction = () => {};
|
|||
|
||||
|
||||
export function addOnLoad(fun: () => void) {
|
||||
const oldOnLoad = window.onload || (() => {
|
||||
});
|
||||
const oldOnLoad = window.onload || emptyFunction;
|
||||
|
||||
window.onload = (() => {
|
||||
// @ts-ignore TODO Find out how to resolve this
|
||||
// @ts-ignore: Call works without parameters as well
|
||||
oldOnLoad();
|
||||
fun();
|
||||
});
|
||||
|
@ -31,6 +30,6 @@ export function moveCaretToEndOf(element: Node) {
|
|||
selection.addRange(range);
|
||||
}
|
||||
|
||||
export function q(query: string) {
|
||||
export function q(query: string): HTMLElement {
|
||||
return document.querySelector(query);
|
||||
}
|
||||
|
|
208
js/terminal.ts
208
js/terminal.ts
|
@ -4,66 +4,72 @@ import {Commands} from "./commands.js";
|
|||
|
||||
|
||||
export class Terminal {
|
||||
private _terminal: any;
|
||||
private _input: any;
|
||||
private _output: any;
|
||||
private _prefixDiv: any;
|
||||
private _user: string;
|
||||
private _loggedIn: boolean;
|
||||
private _inputHistory: InputHistory;
|
||||
private _fs:FileSystem;
|
||||
private _commands: Commands;
|
||||
private readonly terminal: HTMLElement;
|
||||
private readonly input: HTMLElement;
|
||||
private readonly output: HTMLElement;
|
||||
private readonly prefixDiv: HTMLElement;
|
||||
|
||||
private readonly inputHistory: InputHistory;
|
||||
private readonly fileSystem: FileSystem;
|
||||
private readonly commands: Commands;
|
||||
|
||||
private _currentUser: string;
|
||||
private isLoggedIn: boolean;
|
||||
|
||||
|
||||
constructor(terminal, input, output, prefixDiv) {
|
||||
this._terminal = terminal;
|
||||
this._input = input;
|
||||
this._output = output;
|
||||
this._prefixDiv = prefixDiv;
|
||||
constructor(terminal: HTMLElement, input: HTMLElement, output: HTMLElement, prefixDiv: HTMLElement) {
|
||||
this.terminal = terminal;
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
this.prefixDiv = prefixDiv;
|
||||
|
||||
this._user = "felix";
|
||||
this._loggedIn = true;
|
||||
this._currentUser = "felix";
|
||||
this.isLoggedIn = true;
|
||||
|
||||
this._inputHistory = new InputHistory();
|
||||
this._fs = new FileSystem();
|
||||
this._commands = new Commands(this, this._fs);
|
||||
this.inputHistory = new InputHistory();
|
||||
this.fileSystem = new FileSystem();
|
||||
this.commands = new Commands(this, this.fileSystem);
|
||||
|
||||
this._terminal.addEventListener("click", this._onclick.bind(this));
|
||||
this._terminal.addEventListener("keypress", this._onkeypress.bind(this));
|
||||
this._terminal.addEventListener("keydown", this._onkeydown.bind(this));
|
||||
this.terminal.addEventListener("click", this.onclick.bind(this));
|
||||
this.terminal.addEventListener("keypress", this.onkeypress.bind(this));
|
||||
this.terminal.addEventListener("keydown", this.onkeydown.bind(this));
|
||||
|
||||
this.reset();
|
||||
this._input.focus();
|
||||
this.input.focus();
|
||||
}
|
||||
|
||||
|
||||
get inputText() {
|
||||
return this._input.innerHTML
|
||||
get inputText(): string {
|
||||
return this.input.innerHTML
|
||||
.replaceAll(/<br>/, "");
|
||||
}
|
||||
|
||||
set inputText(inputText) {
|
||||
this._input.innerHTML = inputText;
|
||||
set inputText(inputText: string) {
|
||||
this.input.innerHTML = inputText;
|
||||
}
|
||||
|
||||
get outputText() {
|
||||
return this._output.innerHTML;
|
||||
get outputText(): string {
|
||||
return this.output.innerHTML;
|
||||
}
|
||||
|
||||
set outputText(outputText) {
|
||||
this._output.innerHTML = outputText;
|
||||
set outputText(outputText: string) {
|
||||
this.output.innerHTML = outputText;
|
||||
}
|
||||
|
||||
get prefixText() {
|
||||
return this._prefixDiv.innerHTML;
|
||||
get prefixText(): string {
|
||||
return this.prefixDiv.innerHTML;
|
||||
}
|
||||
|
||||
set prefixText(prefixText) {
|
||||
this._prefixDiv.innerHTML = prefixText;
|
||||
set prefixText(prefixText: string) {
|
||||
this.prefixDiv.innerHTML = prefixText;
|
||||
}
|
||||
|
||||
get currentUser(): string {
|
||||
return this._currentUser;
|
||||
}
|
||||
|
||||
|
||||
static generateHeader() {
|
||||
static generateHeader(): string {
|
||||
return "" +
|
||||
`${asciiHeaderHtml}
|
||||
|
||||
|
@ -76,16 +82,15 @@ export class Terminal {
|
|||
`.trimLines();
|
||||
}
|
||||
|
||||
generatePrefix() {
|
||||
if (!this._loggedIn) {
|
||||
if (this._user === undefined) {
|
||||
generatePrefix(): string {
|
||||
if (!this.isLoggedIn) {
|
||||
if (this._currentUser === undefined)
|
||||
return "login as: ";
|
||||
} else {
|
||||
return `Password for ${this._user}@fwdekker.com: `;
|
||||
}
|
||||
else
|
||||
return `Password for ${this._currentUser}@fwdekker.com: `;
|
||||
}
|
||||
|
||||
return `${this._user}@fwdekker.com <span style="color: green;">${this._fs.pwd}</span>> `;
|
||||
return `${this._currentUser}@fwdekker.com <span style="color: green;">${this.fileSystem.pwd}</span>> `;
|
||||
}
|
||||
|
||||
|
||||
|
@ -94,39 +99,39 @@ export class Terminal {
|
|||
}
|
||||
|
||||
reset() {
|
||||
this._fs.reset();
|
||||
this.fileSystem.reset();
|
||||
|
||||
this.outputText = Terminal.generateHeader();
|
||||
this.prefixText = this.generatePrefix();
|
||||
}
|
||||
|
||||
|
||||
continueLogin(input) {
|
||||
if (this._user === undefined) {
|
||||
private continueLogin(input: string) {
|
||||
if (this._currentUser === undefined) {
|
||||
this.outputText += `${this.prefixText}${input}\n`;
|
||||
|
||||
this._user = input.trim();
|
||||
this._input.classList.add("terminalCurrentFocusInputHidden");
|
||||
this._currentUser = input.trim();
|
||||
this.input.classList.add("terminalCurrentFocusInputHidden");
|
||||
} else {
|
||||
this.outputText += `${this.prefixText}\n`;
|
||||
|
||||
if ((this._user === "felix" && input === "hotel123")
|
||||
|| (this._user === "root" && input === "password")) {
|
||||
this._loggedIn = true;
|
||||
if ((this._currentUser === "felix" && input === "password")
|
||||
|| (this._currentUser === "root" && input === "root")) {
|
||||
this.isLoggedIn = true;
|
||||
this.outputText += Terminal.generateHeader();
|
||||
} else {
|
||||
this._user = undefined;
|
||||
this._currentUser = undefined;
|
||||
this.outputText += "Access denied\n";
|
||||
}
|
||||
|
||||
this._input.classList.remove("terminalCurrentFocusInputHidden");
|
||||
this.input.classList.remove("terminalCurrentFocusInputHidden");
|
||||
}
|
||||
}
|
||||
|
||||
logOut() {
|
||||
this._user = undefined;
|
||||
this._loggedIn = false;
|
||||
this._inputHistory.clear();
|
||||
this._currentUser = undefined;
|
||||
this.isLoggedIn = false;
|
||||
this.inputHistory.clear();
|
||||
}
|
||||
|
||||
ignoreInput() {
|
||||
|
@ -135,52 +140,51 @@ export class Terminal {
|
|||
this.inputText = "";
|
||||
}
|
||||
|
||||
processInput(input) {
|
||||
processInput(input: string) {
|
||||
this.inputText = "";
|
||||
|
||||
if (!this._loggedIn) {
|
||||
if (!this.isLoggedIn) {
|
||||
this.continueLogin(input);
|
||||
} else {
|
||||
this.outputText += `${this.prefixText}${input}\n`;
|
||||
this._inputHistory.addEntry(input);
|
||||
this.inputHistory.addEntry(input);
|
||||
|
||||
const output = this._commands.parse(input.trim());
|
||||
if (output !== "") {
|
||||
const output = this.commands.parse(input.trim());
|
||||
if (output !== "")
|
||||
this.outputText += output + `\n`;
|
||||
}
|
||||
}
|
||||
|
||||
this.prefixText = this.generatePrefix();
|
||||
}
|
||||
|
||||
|
||||
_onclick() {
|
||||
this._input.focus();
|
||||
private onclick() {
|
||||
this.input.focus();
|
||||
}
|
||||
|
||||
_onkeypress(e) {
|
||||
switch (e.key.toLowerCase()) {
|
||||
private onkeypress(event) {
|
||||
switch (event.key.toLowerCase()) {
|
||||
case "enter":
|
||||
this.processInput(this.inputText.replaceAll(/ /, " "));
|
||||
e.preventDefault();
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_onkeydown(e) {
|
||||
switch (e.key.toLowerCase()) {
|
||||
private onkeydown(event) {
|
||||
switch (event.key.toLowerCase()) {
|
||||
case "arrowup":
|
||||
this.inputText = this._inputHistory.previousEntry();
|
||||
window.setTimeout(() => moveCaretToEndOf(this._input), 0);
|
||||
this.inputText = this.inputHistory.previousEntry();
|
||||
window.setTimeout(() => moveCaretToEndOf(this.input), 0);
|
||||
break;
|
||||
case "arrowdown":
|
||||
this.inputText = this._inputHistory.nextEntry();
|
||||
window.setTimeout(() => moveCaretToEndOf(this._input), 0);
|
||||
this.inputText = this.inputHistory.nextEntry();
|
||||
window.setTimeout(() => moveCaretToEndOf(this.input), 0);
|
||||
break;
|
||||
case "c":
|
||||
if (e.ctrlKey) {
|
||||
if (event.ctrlKey) {
|
||||
this.ignoreInput();
|
||||
e.preventDefault();
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -188,54 +192,53 @@ export class Terminal {
|
|||
}
|
||||
|
||||
class InputHistory {
|
||||
private _history: string[];
|
||||
private _index: number;
|
||||
private history: string[];
|
||||
private index: number;
|
||||
|
||||
|
||||
constructor() {
|
||||
this._history = [];
|
||||
this._index = -1;
|
||||
this.clear();
|
||||
}
|
||||
|
||||
|
||||
addEntry(entry: string) {
|
||||
if (entry.trim() !== "")
|
||||
this._history.unshift(entry);
|
||||
this.history.unshift(entry);
|
||||
|
||||
this._index = -1;
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._history = [];
|
||||
this._index = -1;
|
||||
this.history = [];
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
getEntry(index) {
|
||||
getEntry(index: number): string {
|
||||
if (index >= 0)
|
||||
return this._history[index];
|
||||
return this.history[index];
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
nextEntry() {
|
||||
this._index--;
|
||||
if (this._index < -1)
|
||||
this._index = -1;
|
||||
nextEntry(): string {
|
||||
this.index--;
|
||||
if (this.index < -1)
|
||||
this.index = -1;
|
||||
|
||||
return this.getEntry(this._index);
|
||||
return this.getEntry(this.index);
|
||||
}
|
||||
|
||||
previousEntry() {
|
||||
this._index++;
|
||||
if (this._index >= this._history.length)
|
||||
this._index = this._history.length - 1;
|
||||
previousEntry(): string {
|
||||
this.index++;
|
||||
if (this.index >= this.history.length)
|
||||
this.index = this.history.length - 1;
|
||||
|
||||
return this.getEntry(this._index);
|
||||
return this.getEntry(this.index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export let terminal;
|
||||
export let terminal: Terminal;
|
||||
|
||||
addOnLoad(() => {
|
||||
terminal = new Terminal(
|
||||
|
@ -245,13 +248,10 @@ addOnLoad(() => {
|
|||
q("#terminalCurrentPrefix")
|
||||
);
|
||||
|
||||
// @ts-ignore: Force definition
|
||||
window.relToAbs = (filename: string) => terminal.fileSystem.pwd + filename;
|
||||
// @ts-ignore: Force definition
|
||||
window.run = (command: string) => terminal.processInput(command);
|
||||
|
||||
terminal.processInput("ls");
|
||||
});
|
||||
|
||||
export function run(command: string) {
|
||||
terminal.processInput(command);
|
||||
}
|
||||
|
||||
export function relToAbs(filename) {
|
||||
return terminal._fs.pwd + filename;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue