Teascade.net/terminal/js/jsx.js

337 lines
12 KiB
JavaScript

/**
* This file is licensed under the GPLv3 license (c) Aleksi 'Allexit' Talarmo
* See COPYING-GPL and README.md for more information.
*/
var JavaScriptX;
(function (JavaScriptX) {
class JSX {
constructor() {
this.environmentVariables = {
"system": "~/.system/"
};
}
create(terminal, stdio, args) {
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) => {
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;
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) {
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;
}
}
JavaScriptX.JSX = JSX;
class FileSystem {
constructor() {
this.rootFolder = new Folder("~");
}
}
class File {
constructor(name, hidden = false) {
this.created = new Date().getTime();
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() {
let path = "";
let curr = 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.
*/
class Folder extends File {
constructor(name, hidden = false) {
super(name, hidden);
this.files = [];
}
/**
* Add file to this folder.
*/
addFile(file) {
this.files.push(file);
file.parentFolder = this;
}
}
JavaScriptX.Folder = Folder;
/**
* File that contains JavaScript program which can be run
*/
class BinaryFile extends File {
constructor(name, program, hidden = false) {
super(name, hidden);
this.program = program;
}
/**
* Launch the program specified in this file.
*/
run(terminal, args) {
terminal.launchProgram(this.program, args);
}
}
JavaScriptX.BinaryFile = BinaryFile;
/**
* File that contains text
*/
class TextFile extends File {
constructor(name, content = "", hidden = false) {
super(name, hidden);
this.content = content;
}
readContent() {
return this.content.slice();
}
setContent(content) {
this.content = content;
}
}
JavaScriptX.TextFile = TextFile;
/**
* Formats the given string to a certain length and alignment.
* Example: formatToLength("hello", 10, "right") -> ".....hello" (where dots represent spaces)
*/
function formatToLength(text, length, alignment = "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;
}
JavaScriptX.formatToLength = formatToLength;
})(JavaScriptX || (JavaScriptX = {}));