337 lines
12 KiB
JavaScript
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 = {}));
|