Teascade.net/terminal/ts/jsx.ts

383 lines
12 KiB
TypeScript

/**
* This file is licensed under the GPLv3 license (c) Aleksi 'Allexit' Talarmo
* See COPYING-GPL and README.md for more information.
*/
namespace JavaScriptX {
export class JSX implements JSTerminal.Program {
terminal: JSTerminal.Terminal;
stdio: Std.IO;
filesystem: FileSystem;
currentFolder: Folder;
environmentVariables: { [variable: string]: string } = {
"system": "~/.system/"
};
create(terminal: JSTerminal.Terminal, stdio: Std.IO, args?: string[]) {
this.terminal = terminal;
this.stdio = stdio;
this.filesystem = new FileSystem();
this.currentFolder = this.filesystem.rootFolder;
let system = new Folder(".system", true);
this.filesystem.rootFolder.addFile(system);
system.addFile(new BinaryFile("echo", new JSXCommands.Echo()));
system.addFile(new BinaryFile("ls", new JSXCommands.List(this)));
system.addFile(new BinaryFile("cd", new JSXCommands.ChangeDirectory(this)));
system.addFile(new BinaryFile("mkdir", new JSXCommands.MakeDirectory(this)));
system.addFile(new BinaryFile("woman", new JSXCommands.Woman(this)));
system.addFile(new BinaryFile("cat", new JSXCommands.Caternate(this)));
system.addFile(new BinaryFile("touch", new JSXCommands.Touch(this)));
system.addFile(new BinaryFile("jedi", new JSXCommands.JEdi(this)));
this.filesystem.rootFolder.addFile(new TextFile("textfile.txt", "Hello, world!"));
stdio.println("Welcome to ");
stdio.print("JSX", "red");
stdio.print(" version ");
stdio.print("0.4.0 beta", "green");
stdio.print(" (K) All rites reversed");
stdio.println("\nTo get started: call a ");
stdio.print("woman", "blue");
stdio.print(" for help.");
stdio.refresh();
this.takeCommand();
}
/**
* Inherited from Program-interface. Not to be manually called.
*/
enable() {
this.takeCommand();
}
/**
* Inherited from Program-interface. Not to be manually called.
*/
disable() {
}
/**
* Inherited from Program-interface. Not to be manually called.
*/
onClose() {
return true;
}
/**
* Utility function. Writes a line such as 'Anonymous@localhost: ~/' and then reads line.
* The result of the readline is used to call commands and such.
*/
takeCommand() {
let name = window.location.hostname || "localhost";
this.stdio.println(`\nAnonymous@${name}`, "yellow");
this.stdio.print(":");
let path = this.currentFolder.getPath();
this.stdio.print(path, "green");
this.stdio.readline({
callback: (result: string) => {
let split = result.split(" ");
let command = split[0];
let args = split.slice(1, split.length);
for (let v in this.environmentVariables) {
if (command == v) {
command = this.environmentVariables[v];
break;
}
let file = this.findPath(this.environmentVariables[v]);
if (file instanceof Folder) {
for (let i = 0; i < file.files.length; i++) {
let f = file.files[i];
if (command == f.name) {
command = f.getPath();
break;
}
}
}
}
for (let i = 0; i < args.length; i++) {
let regexp = /\${[\w]*}/g;
let arg = args[i];
let result: string[];
while ((result = regexp.exec(arg)) != null) {
let match = result[0];
let v = match.substr(2, match.length - 3);
let toReplace = null;
for (let env in this.environmentVariables) {
if (env == v) {
toReplace = this.environmentVariables[env];
break;
}
}
if (toReplace != null) {
args[i] = arg.replace(new RegExp("\\" + match, "g"), toReplace);
}
}
}
let file = this.findPath(command);
if (file && (
command.startsWith("./")
|| command.startsWith("~/")
|| command.startsWith("../"))) {
if (file instanceof BinaryFile) {
file.run(this.terminal, args);
return;
} else {
this.stdio.println(`${file.name} is not executable`);
}
} else {
if (command.startsWith(".")) {
this.stdio.println(`File not found`);
} else {
this.stdio.println(`Command not found`);
}
}
this.takeCommand();
},
prefix: " > ",
printAfterDone: true,
style: "red"
})
this.stdio.refresh();
}
/**
* Returns the file at the specified path.
* Example usage: findPath("~/somefolder/hello/");
*/
findPath(path: string): File {
let originalPath = path.slice();
let pathPieces = path.split("/");
let relativeFolder = this.currentFolder;
let fileFound = null;
for (let i = 0; i < pathPieces.length; i++) {
let p = pathPieces[i];
if (p == "") {
if (relativeFolder == null) {
fileFound = null;
break;
}
if (i == pathPieces.length - 1 && relativeFolder instanceof Folder) {
fileFound = relativeFolder;
break;
}
fileFound = null;
break;
} else if (p == "..") {
if (i == 0) {
if (this.currentFolder.parentFolder == null) {
fileFound = null;
break;
}
relativeFolder = this.currentFolder.parentFolder;
}
else if (relativeFolder == null) {
fileFound = null;
break;
} else if (relativeFolder.parentFolder == null) {
fileFound = null;
break;
} else {
relativeFolder = relativeFolder.parentFolder;
}
if (i == pathPieces.length - 1) {
fileFound = relativeFolder;
break;
}
} else if (p == ".") {
if (i == 0) {
relativeFolder = this.currentFolder;
} if (i == pathPieces.length - 1) {
fileFound = relativeFolder;
break;
}
continue;
} else if (p == "~" && i == 0) {
relativeFolder = this.filesystem.rootFolder;
if (i == pathPieces.length - 1) {
fileFound = relativeFolder;
break;
}
continue;
} else if (i == pathPieces.length - 1) {
if (relativeFolder == null) {
fileFound = null;
break;
}
let idx = relativeFolder.files.map((f) => { return f.name })
.indexOf(p);
let found = relativeFolder.files[idx];
if (idx == -1) {
break;
} else {
fileFound = found;
}
} else {
if (relativeFolder == null) {
fileFound = null;
break;
}
let idx = relativeFolder.files.map((f) => { return f.name })
.indexOf(p);
let found = relativeFolder.files[idx];
if (idx == -1) {
break;
} else if (found instanceof Folder) {
relativeFolder = found;
} else {
break;
}
}
}
return fileFound;
}
}
class FileSystem {
rootFolder: Folder;
constructor() {
this.rootFolder = new Folder("~");
}
}
abstract class File {
name: string;
parentFolder: Folder;
hidden: boolean;
created: number = new Date().getTime();
constructor(name: string, hidden: boolean = false) {
this.name = name;
this.hidden = hidden;
}
/**
* Returns the string path of the file.
* Example: If the file is in root folder (~) and inside a 'somefolder'-folder, it would return "~/somefolder/<filename>"
*/
getPath(): string {
let path = "";
let curr: File = this;
while (curr != null) {
if (curr instanceof Folder || path != "") {
path = "/" + path;
}
path = curr.name + path;
curr = curr.parentFolder;
}
return path;
}
}
/**
* File that contains other files.
*/
export class Folder extends File {
files: File[];
constructor(name: string, hidden: boolean = false) {
super(name, hidden);
this.files = [];
}
/**
* Add file to this folder.
*/
addFile(file: File) {
this.files.push(file);
file.parentFolder = this;
}
}
/**
* File that contains JavaScript program which can be run
*/
export class BinaryFile extends File {
program: JSTerminal.Program;
constructor(name: string, program: JSTerminal.Program, hidden: boolean = false) {
super(name, hidden);
this.program = program;
}
/**
* Launch the program specified in this file.
*/
run(terminal: JSTerminal.Terminal, args?: string[]) {
terminal.launchProgram(this.program, args);
}
}
/**
* File that contains text
*/
export class TextFile extends File {
content: string;
constructor(name: string, content: string = "", hidden: boolean = false) {
super(name, hidden);
this.content = content;
}
readContent() {
return this.content.slice();
}
setContent(content: string) {
this.content = content;
}
}
/**
* Used as an interface, to give programs documentation that can be read with the 'woman' command.
* Implement along with Program.
*/
export interface Manual {
getManual(): string;
}
/**
* Formats the given string to a certain length and alignment.
* Example: formatToLength("hello", 10, "right") -> ".....hello" (where dots represent spaces)
*/
export function formatToLength(text: string, length: number, alignment: "left" | "right" | "center" = "left") {
if (alignment == "left") {
text += " ".repeat(length);
return text.substr(0, text.length - (text.length - length));
} else if (alignment == "right") {
text = " ".repeat(length) + text;
return text.substr(-length);
} else if (alignment == "center") {
let left = Math.max(0, Math.floor(length / 2 - text.length / 2));
let right = Math.max(0, Math.ceil(length / 2 - text.length / 2));
return " ".repeat(left) + text + " ".repeat(right);
}
return null;
}
}