2018-11-28 22:07:51 +01:00
|
|
|
class Commands {
|
2018-11-28 22:23:11 +01:00
|
|
|
constructor(terminal, fileSystem) {
|
|
|
|
this._terminal = terminal;
|
2018-11-28 23:22:17 +01:00
|
|
|
this._fs = fileSystem;
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2018-11-28 22:07:51 +01:00
|
|
|
this._list = {
|
|
|
|
clear: {
|
2018-11-28 22:23:11 +01:00
|
|
|
fun: this.clear,
|
2018-11-28 22:07:51 +01:00
|
|
|
summary: `clear terminal output`,
|
|
|
|
usage: `clear`,
|
2018-11-28 23:22:17 +01:00
|
|
|
desc: `Clears all previous terminal output.`
|
2018-11-28 22:07:51 +01:00
|
|
|
},
|
|
|
|
cd: {
|
2018-11-28 22:23:11 +01:00
|
|
|
fun: this.cd,
|
2018-11-28 22:07:51 +01:00
|
|
|
summary: `change directory`,
|
|
|
|
usage: `cd [DIRECTORY]`,
|
|
|
|
desc: "" +
|
|
|
|
`Changes the current working directory to [DIRECTORY].
|
2018-11-29 01:08:58 +01:00
|
|
|
If [DIRECTORY] is empty, nothing happens.`.trimLines()
|
2018-11-28 22:07:51 +01:00
|
|
|
},
|
2018-11-29 13:34:46 +01:00
|
|
|
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()
|
|
|
|
},
|
2018-11-28 22:07:51 +01:00
|
|
|
echo: {
|
|
|
|
fun: Commands.echo,
|
|
|
|
summary: `display text`,
|
|
|
|
usage: `echo [TEXT]`,
|
|
|
|
desc: `Displays [TEXT].`.trimLines()
|
|
|
|
},
|
|
|
|
exit: {
|
2018-11-29 16:10:02 +01:00
|
|
|
fun: this.exit,
|
2018-11-28 22:07:51 +01:00
|
|
|
summary: `close session`,
|
|
|
|
usage: `exit`,
|
2018-11-28 23:22:17 +01:00
|
|
|
desc: `Closes the terminal session.`
|
2018-11-28 22:07:51 +01:00
|
|
|
},
|
|
|
|
help: {
|
|
|
|
fun: this.help,
|
|
|
|
summary: `display documentation`,
|
|
|
|
usage: `help [COMMAND]`,
|
|
|
|
desc: "" +
|
|
|
|
`Displays help documentation for [COMMAND].
|
2018-11-29 01:08:58 +01:00
|
|
|
If [COMMAND] is empty, a list of all commands is shown.`.trimLines()
|
2018-11-28 22:07:51 +01:00
|
|
|
},
|
|
|
|
ls: {
|
|
|
|
fun: this.ls,
|
|
|
|
summary: `list directory contents`,
|
|
|
|
usage: `ls [DIRECTORY]`,
|
|
|
|
desc: "" +
|
|
|
|
`Displays the files and directories in [DIRECTORY].
|
2018-11-29 01:08:58 +01:00
|
|
|
If [DIRECTORY] is empty, the files and directories in the current working directory are shown.`.trimLines()
|
2018-11-28 22:07:51 +01:00
|
|
|
},
|
2018-12-02 17:01:11 +01:00
|
|
|
man: {
|
|
|
|
fun: this.man,
|
|
|
|
summary: `display manual documentation pages`,
|
|
|
|
usage: `man PAGE`,
|
|
|
|
desc: `Displays the manual page with the name PAGE.`
|
|
|
|
},
|
2018-11-28 22:07:51 +01:00
|
|
|
mkdir: {
|
2018-11-28 22:23:11 +01:00
|
|
|
fun: this.mkdir,
|
2018-11-29 09:08:24 +01:00
|
|
|
summary: `make directories`,
|
|
|
|
usage: `mkdir DIRECTORY...`,
|
|
|
|
desc: "" +
|
|
|
|
`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()
|
2018-11-28 23:22:17 +01:00
|
|
|
},
|
2018-11-29 13:18:48 +01:00
|
|
|
mv: {
|
|
|
|
fun: this.mv,
|
|
|
|
summary: `move file`,
|
|
|
|
usage: `mv SOURCE DESTINATION`,
|
|
|
|
desc: `Renames SOURCE to DESTINATION.`
|
|
|
|
},
|
2018-11-29 00:15:08 +01:00
|
|
|
open: {
|
|
|
|
fun: this.open,
|
|
|
|
summary: `open web page`,
|
|
|
|
usage: `open [-b | --blank] FILE`,
|
|
|
|
desc: "" +
|
2018-11-29 01:08:58 +01:00
|
|
|
`Opens the web page linked to by FILE in this browser window.
|
2018-11-29 00:15:08 +01:00
|
|
|
|
2018-11-29 01:08:58 +01:00
|
|
|
If -b or --blank is set, the web page is opened in a new tab.`.trimLines()
|
2018-11-29 00:15:08 +01:00
|
|
|
},
|
2018-11-28 23:22:17 +01:00
|
|
|
poweroff: {
|
2018-11-29 16:10:02 +01:00
|
|
|
fun: this.poweroff,
|
2018-11-28 23:47:11 +01:00
|
|
|
summary: `close down the system`,
|
2018-11-28 23:22:17 +01:00
|
|
|
usage: `poweroff`,
|
2018-11-28 23:47:11 +01:00
|
|
|
desc: `Automated shutdown procedure to nicely notify users when the system is shutting down.`
|
2018-11-28 22:07:51 +01:00
|
|
|
},
|
|
|
|
pwd: {
|
2018-11-28 22:23:11 +01:00
|
|
|
fun: this.pwd,
|
2018-11-28 22:07:51 +01:00
|
|
|
summary: `print working directory`,
|
|
|
|
usage: `pwd`,
|
2018-11-28 23:22:17 +01:00
|
|
|
desc: `Displays the current working directory.`
|
2018-11-28 22:07:51 +01:00
|
|
|
},
|
|
|
|
rm: {
|
2018-11-28 22:23:11 +01:00
|
|
|
fun: this.rm,
|
2018-11-28 22:07:51 +01:00
|
|
|
summary: `remove file`,
|
2018-11-29 13:50:36 +01:00
|
|
|
usage: `rm [-f | --force] [-r | -R | --recursive] [--no-preserve-root] FILE...`,
|
2018-11-29 09:20:23 +01:00
|
|
|
desc:
|
|
|
|
`Removes the files given by FILE.
|
|
|
|
|
2018-11-29 09:37:42 +01:00
|
|
|
If more than one file is given, the files are removed in the order they are given in.
|
|
|
|
|
2018-11-29 13:50:36 +01:00
|
|
|
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()
|
2018-11-28 22:07:51 +01:00
|
|
|
},
|
|
|
|
rmdir: {
|
2018-11-28 22:23:11 +01:00
|
|
|
fun: this.rmdir,
|
2018-11-29 09:08:24 +01:00
|
|
|
summary: `remove directories`,
|
2018-11-29 13:38:00 +01:00
|
|
|
usage: `rmdir DIRECTORY...`,
|
2018-11-29 00:15:08 +01:00
|
|
|
desc: "" +
|
2018-11-29 09:08:24 +01:00
|
|
|
`Removes the directories given by DIRECTORY.
|
|
|
|
|
2018-11-29 13:38:00 +01:00
|
|
|
If more than one directory is given, the directories are removed in the order they are given in.`.trimLines()
|
2018-11-29 20:56:27 +01:00
|
|
|
},
|
|
|
|
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()
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
|
|
|
|
2018-11-28 22:07:51 +01:00
|
|
|
parse(input) {
|
2018-11-29 08:58:53 +01:00
|
|
|
const args = new InputArgs(input);
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 08:58:53 +01:00
|
|
|
if (Object.keys(this._list).indexOf(args.getCommand()) >= 0) {
|
|
|
|
return this._list[args.getCommand()].fun.bind(this)(args);
|
|
|
|
} else if (args.getCommand().trim() === "") {
|
2018-11-28 22:41:59 +01:00
|
|
|
return "";
|
2018-11-28 22:07:51 +01:00
|
|
|
} else {
|
2018-11-29 13:11:57 +01:00
|
|
|
return `Unknown command '${args.getCommand()}'`
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
cd(args) {
|
2018-11-29 08:58:53 +01:00
|
|
|
return this._fs.cd(args.getArg(0));
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 13:34:46 +01:00
|
|
|
cp(args) {
|
|
|
|
return this._fs.cp(args.getArg(0), args.getArg(1));
|
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
clear() {
|
|
|
|
this._terminal.clear();
|
2018-11-28 22:41:59 +01:00
|
|
|
return "";
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 22:07:51 +01:00
|
|
|
static echo(args) {
|
2018-11-29 08:58:53 +01:00
|
|
|
return args.getArgs()
|
|
|
|
.join(" ")
|
2018-11-28 22:41:59 +01:00
|
|
|
.replace("hunter2", "*******");
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
exit() {
|
|
|
|
this._terminal.logOut();
|
2018-11-28 22:41:59 +01:00
|
|
|
return "";
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 22:07:51 +01:00
|
|
|
help(args) {
|
2018-11-29 08:58:53 +01:00
|
|
|
const command = args.getArg(0, "").toLowerCase();
|
2018-11-28 22:07:51 +01:00
|
|
|
const commandNames = Object.keys(this._list);
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 22:07:51 +01:00
|
|
|
if (commandNames.indexOf(command) >= 0) {
|
|
|
|
const info = this._list[command];
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 22:07:51 +01:00
|
|
|
return "" +
|
|
|
|
`${command} - ${info.summary}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 01:08:58 +01:00
|
|
|
<b>Usage</b>
|
|
|
|
${info.usage}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 01:08:58 +01:00
|
|
|
<b>Description</b>
|
|
|
|
${info.desc}`.trimLines();
|
2018-11-28 22:07:51 +01:00
|
|
|
} 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}`
|
|
|
|
);
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 22:07:51 +01:00
|
|
|
return "" +
|
|
|
|
`<b>List of commands</b>
|
2018-11-29 01:08:58 +01:00
|
|
|
${commandEntries.join("\n")}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 01:08:58 +01:00
|
|
|
Write "help [COMMAND]" for more information on a command.`.trimLines();
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 22:07:51 +01:00
|
|
|
ls(args) {
|
2018-11-29 08:58:53 +01:00
|
|
|
return this._fs.ls(args.getArg(0));
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-12-02 17:01:11 +01:00
|
|
|
man(args) {
|
|
|
|
if (args.getArgs().length === 0) {
|
|
|
|
return "What manual page do you want?";
|
|
|
|
} else if (Object.keys(this._list).indexOf(args.getArg(0)) < 0) {
|
|
|
|
return `No manual entry for ${args.getArg(0)}`;
|
|
|
|
} else {
|
|
|
|
return this.help(args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
mkdir(args) {
|
2018-11-29 09:08:24 +01:00
|
|
|
return this._fs.mkdirs(args.getArgs());
|
2018-11-28 23:22:17 +01:00
|
|
|
}
|
|
|
|
|
2018-11-29 13:18:48 +01:00
|
|
|
mv(args) {
|
|
|
|
return this._fs.mv(args.getArg(0), args.getArg(1));
|
|
|
|
}
|
|
|
|
|
2018-11-29 00:15:08 +01:00
|
|
|
open(args) {
|
2018-11-29 08:58:53 +01:00
|
|
|
const fileName = args.getArg(0);
|
2018-12-02 17:17:39 +01:00
|
|
|
const target = args.hasAnyOption(["b", "blank"]) ? "_blank" : "_self";
|
2018-11-29 00:15:08 +01:00
|
|
|
|
|
|
|
const file = this._fs._getFile(fileName);
|
|
|
|
if (file === undefined) {
|
|
|
|
return `The file '${fileName}' does not exist`;
|
|
|
|
}
|
|
|
|
if (!FileSystem.isFile(file)) {
|
2018-11-29 13:50:36 +01:00
|
|
|
return `'${fileName}' is not a file`;
|
2018-11-29 00:15:08 +01:00
|
|
|
}
|
2018-11-30 17:11:56 +01:00
|
|
|
if (!(file instanceof UrlFile)) {
|
2018-11-29 00:15:08 +01:00
|
|
|
return `Could not open '${fileName}'`;
|
|
|
|
}
|
|
|
|
|
2018-11-29 12:42:14 +01:00
|
|
|
window.open(file.url, target);
|
2018-11-29 00:15:08 +01:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
poweroff() {
|
2018-11-28 23:22:17 +01:00
|
|
|
const date = new Date();
|
|
|
|
date.setSeconds(date.getSeconds() + 30);
|
|
|
|
document.cookie = `poweroff=true; expires=${date.toUTCString()}; path=/`;
|
|
|
|
|
|
|
|
setTimeout(() => location.reload(), 2000);
|
|
|
|
return "" +
|
|
|
|
`Shutdown NOW!
|
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
*** FINAL System shutdown message from ${terminal._user}@fwdekker.com ***
|
2018-11-28 23:22:17 +01:00
|
|
|
|
|
|
|
System going down IMMEDIATELY
|
|
|
|
|
|
|
|
|
|
|
|
System shutdown time has arrived`.trimLines();
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
pwd() {
|
2018-11-28 23:22:17 +01:00
|
|
|
return this._fs.pwd;
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
rm(args) {
|
2018-11-29 13:50:36 +01:00
|
|
|
return this._fs.rms(
|
|
|
|
args.getArgs(),
|
2018-12-02 17:17:39 +01:00
|
|
|
args.hasAnyOption(["f", "force"]),
|
|
|
|
args.hasAnyOption(["r", "R", "recursive"]),
|
|
|
|
args.hasOption("no-preserve-root")
|
2018-11-29 13:50:36 +01:00
|
|
|
);
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
rmdir(args) {
|
2018-11-29 20:56:27 +01:00
|
|
|
return this._fs.rmdirs(args.getArgs());
|
|
|
|
}
|
|
|
|
|
|
|
|
touch(args) {
|
|
|
|
return this._fs.createFiles(args.getArgs());
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-29 08:58:53 +01:00
|
|
|
|
|
|
|
class InputArgs {
|
|
|
|
constructor(input) {
|
2018-12-02 17:17:39 +01:00
|
|
|
const inputParts = (input.match(/("[^"]+"|[^"\s]+)/g) || [])
|
2018-11-29 10:08:44 +01:00
|
|
|
.map(it => it.replace(/^"/, "").replace(/"$/, ""));
|
2018-12-02 17:17:39 +01:00
|
|
|
|
|
|
|
this._command = (inputParts[0] || "").toLowerCase();
|
|
|
|
|
|
|
|
this._options = {};
|
2018-11-29 08:58:53 +01:00
|
|
|
let i;
|
2018-12-02 17:17:39 +01:00
|
|
|
for (i = 1; i < inputParts.length; i++) {
|
|
|
|
const arg = inputParts[i];
|
|
|
|
const argParts = arg.split("=");
|
|
|
|
|
|
|
|
if (arg.startsWith("--")) {
|
|
|
|
// --option, --option=value
|
|
|
|
const argName = argParts[0].substr(2);
|
|
|
|
this._options[argName] = (argParts[1] || "");
|
|
|
|
} else if (arg.startsWith("-")) {
|
|
|
|
// -o, -o=value, -opq
|
|
|
|
if (argParts[0].length === 2) {
|
|
|
|
// -o, -o=value
|
|
|
|
const argName = argParts[0].substr(1);
|
|
|
|
|
|
|
|
this._options[argName] = (argParts[1] || "");
|
|
|
|
} else if (argParts.length === 1) {
|
|
|
|
// -opq
|
|
|
|
const argNames = argParts[0].substr(1).split("");
|
|
|
|
|
|
|
|
argNames.forEach(argName => {
|
|
|
|
this._options[argName] = "";
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Invalid
|
|
|
|
throw "Cannot assign value to multiple options!";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Not an option
|
2018-11-29 08:58:53 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._options[argParts[0]] = (argParts[1] || "");
|
|
|
|
}
|
|
|
|
|
2018-12-02 17:17:39 +01:00
|
|
|
this._args = inputParts.slice(i);
|
2018-11-29 08:58:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getArgs() {
|
|
|
|
return this._args.slice();
|
|
|
|
}
|
|
|
|
|
|
|
|
getArg(index, def) {
|
|
|
|
return (def === undefined)
|
|
|
|
? this._args[index]
|
|
|
|
: this._args[index] || def;
|
|
|
|
}
|
|
|
|
|
|
|
|
hasArg(index) {
|
|
|
|
return (this._args[index] !== undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
getCommand() {
|
|
|
|
return this._command;
|
|
|
|
}
|
|
|
|
|
|
|
|
getOption(key, def) {
|
|
|
|
return (def === undefined)
|
|
|
|
? this._options[key]
|
|
|
|
: this._options[key] || def;
|
|
|
|
}
|
|
|
|
|
|
|
|
hasOption(key) {
|
|
|
|
return (this.getOption(key) !== undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
hasAnyOption(keys) {
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
if (this.hasOption(keys[i])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|