2019-10-31 19:08:09 +01:00
|
|
|
import * as Cookies from "js-cookie";
|
2019-11-02 13:10:49 +01:00
|
|
|
import "./Extensions"
|
2019-11-01 11:59:33 +01:00
|
|
|
import {File, FileSystem} from "./FileSystem"
|
|
|
|
import {IllegalStateError, stripHtmlTags} from "./Shared";
|
2019-11-02 21:28:32 +01:00
|
|
|
import {InputArgs} from "./Shell";
|
2019-11-02 15:16:50 +01:00
|
|
|
import {EscapeCharacters} from "./Terminal";
|
2019-11-01 11:59:33 +01:00
|
|
|
import {UserSession} from "./UserSession";
|
2019-10-20 23:55:04 +02:00
|
|
|
|
|
|
|
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* A collection of commands executed within a particular user session.
|
|
|
|
*/
|
2019-10-20 23:55:04 +02:00
|
|
|
export class Commands {
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* The user session describing the user that executes commands.
|
|
|
|
*/
|
|
|
|
private readonly userSession: UserSession;
|
|
|
|
/**
|
|
|
|
* The file system to interact with.
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
private readonly fileSystem: FileSystem;
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* The list of all available commands.
|
|
|
|
*/
|
2019-10-21 17:07:16 +02:00
|
|
|
private readonly commands: { [key: string]: Command };
|
2019-10-21 02:25:42 +02:00
|
|
|
|
|
|
|
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* Constructs a new collection of commands executed within the given user session.
|
|
|
|
*
|
|
|
|
* @param userSession the user session describing the user that executes commands
|
|
|
|
* @param fileSystem the file system to interact with
|
|
|
|
*/
|
|
|
|
constructor(userSession: UserSession, fileSystem: FileSystem) {
|
|
|
|
this.userSession = userSession;
|
2019-10-21 02:25:42 +02:00
|
|
|
this.fileSystem = fileSystem;
|
|
|
|
this.commands = {
|
2019-10-31 01:34:36 +01:00
|
|
|
"cat": new Command(
|
|
|
|
this.cat,
|
|
|
|
`concatenate and print files`,
|
|
|
|
`cat FILE ...`,
|
|
|
|
`Reads files sequentially, writing them to the standard output.`,
|
|
|
|
new InputValidator({minArgs: 1})
|
|
|
|
),
|
2019-10-30 22:19:07 +01:00
|
|
|
"clear": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.clear,
|
|
|
|
`clear terminal output`,
|
|
|
|
`clear`,
|
2019-10-31 00:29:55 +01:00
|
|
|
`Clears all previous terminal output.`.trimLines(),
|
|
|
|
new InputValidator({maxArgs: 0})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-30 22:19:07 +01:00
|
|
|
"cd": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.cd,
|
|
|
|
`change directory`,
|
|
|
|
`cd [DIRECTORY]`,
|
|
|
|
`Changes the current working directory to [DIRECTORY].
|
2019-10-31 00:29:55 +01:00
|
|
|
If [DIRECTORY] is empty, nothing happens.`.trimLines(),
|
|
|
|
new InputValidator({maxArgs: 1})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-30 22:19:07 +01:00
|
|
|
"cp": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.cp,
|
|
|
|
`copy file`,
|
2019-10-31 14:15:27 +01:00
|
|
|
`cp [-R] SOURCE DESTINATION`,
|
2019-10-21 02:25:42 +02:00
|
|
|
`Copies SOURCE to DESTINATION.
|
2019-10-31 14:15:27 +01:00
|
|
|
SOURCE is what should be copied, and DESTINATION is where it should be copied to.
|
|
|
|
If DESTINATION is an existing directory, SOURCE is copied into that directory.
|
|
|
|
Unless -R is given, SOURCE must be a file.`.trimLines(),
|
2019-10-31 00:29:55 +01:00
|
|
|
new InputValidator({minArgs: 2, maxArgs: 2})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-30 22:19:07 +01:00
|
|
|
"echo": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.echo,
|
|
|
|
`display text`,
|
|
|
|
`echo [TEXT]`,
|
2019-10-31 00:29:55 +01:00
|
|
|
`Displays [TEXT].`.trimLines(),
|
2019-10-31 01:07:27 +01:00
|
|
|
new InputValidator()
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-30 22:19:07 +01:00
|
|
|
"exit": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.exit,
|
|
|
|
`close session`,
|
|
|
|
`exit`,
|
2019-10-31 00:29:55 +01:00
|
|
|
`Closes the terminal session.`.trimLines(),
|
|
|
|
new InputValidator({maxArgs: 0})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-30 22:19:07 +01:00
|
|
|
"help": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.help,
|
|
|
|
`display documentation`,
|
2019-10-31 01:07:27 +01:00
|
|
|
`help [COMMAND...]`,
|
|
|
|
`Displays help documentation for each command in [COMMAND...].
|
|
|
|
If no commands are given, a list of all commands is shown.`.trimLines(),
|
|
|
|
new InputValidator()
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-30 22:19:07 +01:00
|
|
|
"ls": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.ls,
|
|
|
|
`list directory contents`,
|
2019-10-31 01:07:27 +01:00
|
|
|
`ls [DIRECTORY...]`,
|
|
|
|
`Displays the files and directories in [DIRECTORY...].
|
|
|
|
If no directory is given, the files and directories in the current working directory are shown.
|
|
|
|
If more than one directory is given, the files and directories are shown for each given directory in order.`.trimLines(),
|
|
|
|
new InputValidator()
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-30 22:19:07 +01:00
|
|
|
"man": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.man,
|
|
|
|
`display manual documentation pages`,
|
2019-10-31 01:07:27 +01:00
|
|
|
`man PAGE...`,
|
|
|
|
`Displays the manual pages with names PAGE....`.trimLines(),
|
|
|
|
new InputValidator()
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-31 01:07:27 +01:00
|
|
|
"mkdir": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.mkdir,
|
|
|
|
`make directories`,
|
|
|
|
`mkdir DIRECTORY...`,
|
|
|
|
`Creates the directories given by DIRECTORY.
|
2018-11-29 09:08:24 +01:00
|
|
|
|
2019-10-31 00:48:40 +01:00
|
|
|
If more than one directory is given, the directories are created in the order they are given in.`.trimLines(),
|
2019-10-31 00:29:55 +01:00
|
|
|
new InputValidator({minArgs: 1})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-31 01:07:27 +01:00
|
|
|
"mv": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.mv,
|
|
|
|
`move file`,
|
|
|
|
`mv SOURCE DESTINATION`,
|
2019-10-31 00:29:55 +01:00
|
|
|
`Renames SOURCE to DESTINATION.`.trimLines(),
|
|
|
|
new InputValidator({minArgs: 2, maxArgs: 2})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-31 01:07:27 +01:00
|
|
|
"open": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.open,
|
|
|
|
`open web page`,
|
|
|
|
`open [-b | --blank] FILE`,
|
|
|
|
`Opens the web page linked to by FILE in this browser window.
|
2018-11-29 00:15:08 +01:00
|
|
|
|
2019-10-31 00:29:55 +01:00
|
|
|
If -b or --blank is set, the web page is opened in a new tab.`.trimLines(),
|
|
|
|
new InputValidator({minArgs: 1, maxArgs: 1})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-31 01:07:27 +01:00
|
|
|
"poweroff": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.poweroff,
|
|
|
|
`close down the system`,
|
|
|
|
`poweroff`,
|
2019-10-31 00:29:55 +01:00
|
|
|
`Automated shutdown procedure to nicely notify users when the system is shutting down.`.trimLines(),
|
|
|
|
new InputValidator({maxArgs: 0})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-31 01:07:27 +01:00
|
|
|
"pwd": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.pwd,
|
|
|
|
`print working directory`,
|
|
|
|
`pwd`,
|
2019-10-31 00:29:55 +01:00
|
|
|
`Displays the current working directory.`.trimLines(),
|
|
|
|
new InputValidator({maxArgs: 0})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-31 01:07:27 +01:00
|
|
|
"rm": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
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.
|
|
|
|
|
2019-10-31 00:29:55 +01:00
|
|
|
Unless --no-preserve-root is set, the root directory cannot be removed.`.trimLines(),
|
|
|
|
new InputValidator({minArgs: 1})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-31 01:07:27 +01:00
|
|
|
"rmdir": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.rmdir,
|
|
|
|
`remove directories`,
|
|
|
|
`rmdir DIRECTORY...`,
|
|
|
|
`Removes the directories given by DIRECTORY.
|
2018-11-29 09:20:23 +01:00
|
|
|
|
2019-10-31 00:29:55 +01:00
|
|
|
If more than one directory is given, the directories are removed in the order they are given in.`.trimLines(),
|
|
|
|
new InputValidator({minArgs: 1})
|
2019-10-21 02:25:42 +02:00
|
|
|
),
|
2019-10-31 01:07:27 +01:00
|
|
|
"touch": new Command(
|
2019-10-21 02:25:42 +02:00
|
|
|
this.touch,
|
|
|
|
`change file timestamps`,
|
|
|
|
`touch FILE...`,
|
|
|
|
`Update the access and modification times of each FILE to the current time.
|
2018-11-29 09:37:42 +01:00
|
|
|
|
2019-10-31 00:29:55 +01:00
|
|
|
If a file does not exist, it is created.`.trimLines(),
|
|
|
|
new InputValidator({minArgs: 1})
|
2019-10-26 16:08:46 +02:00
|
|
|
),
|
2019-10-31 01:07:27 +01:00
|
|
|
"whoami": new Command(
|
2019-10-26 16:08:46 +02:00
|
|
|
this.whoami,
|
|
|
|
`print short description of user`,
|
|
|
|
`whoami`,
|
2019-10-31 00:29:55 +01:00
|
|
|
`Print a description of the user associated with the current effective user ID.`,
|
|
|
|
new InputValidator({maxArgs: 0})
|
2019-10-21 02:25:42 +02:00
|
|
|
)
|
2018-11-28 22:07:51 +01:00
|
|
|
};
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
|
|
|
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* Parses and executes the given input string and returns the output generated by that command.
|
|
|
|
*
|
2019-11-02 21:28:32 +01:00
|
|
|
* @param input the input string to parse and execute
|
2019-10-29 12:36:03 +01:00
|
|
|
* @return the output generated by that command
|
|
|
|
*/
|
2019-11-02 21:28:32 +01:00
|
|
|
execute(input: InputArgs): string {
|
|
|
|
if (input.command === "factory-reset") {
|
2019-10-30 22:19:07 +01:00
|
|
|
Cookies.remove("files");
|
2019-10-30 22:58:00 +01:00
|
|
|
Cookies.remove("cwd");
|
2019-10-31 14:04:42 +01:00
|
|
|
Cookies.remove("user");
|
2019-10-30 22:19:07 +01:00
|
|
|
location.reload();
|
2019-10-31 22:46:42 +01:00
|
|
|
throw new Error("Goodbye");
|
2019-10-30 22:19:07 +01:00
|
|
|
}
|
|
|
|
|
2019-10-31 00:29:55 +01:00
|
|
|
if (input.command === "")
|
2019-11-02 15:16:50 +01:00
|
|
|
return "";
|
2019-10-31 00:29:55 +01:00
|
|
|
if (!this.commands.hasOwnProperty(input.command))
|
2019-11-02 15:16:50 +01:00
|
|
|
return `Unknown command '${input.command}'`;
|
2019-10-31 00:29:55 +01:00
|
|
|
|
|
|
|
const command = this.commands[input.command];
|
|
|
|
const validation = command.validator.validate(input);
|
|
|
|
if (!validation[0])
|
|
|
|
return this.createUsageErrorOutput(input.command, validation[1]);
|
|
|
|
|
|
|
|
return command.fun.bind(this)(input);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an output action corresponding to an error message about invalid usage of a command.
|
|
|
|
*
|
|
|
|
* @param commandName the name of the command that was used incorrectly
|
|
|
|
* @param errorMessage the message describing how the command was used incorrectly; preferably ended with a `.`
|
|
|
|
* @return an output action corresponding to an error message about invalid usage of a command
|
|
|
|
*/
|
2019-11-02 15:16:50 +01:00
|
|
|
private createUsageErrorOutput(commandName: string, errorMessage: string | undefined): string {
|
2019-10-31 00:29:55 +01:00
|
|
|
const command = this.commands[commandName];
|
|
|
|
if (command === undefined)
|
2019-11-02 15:16:50 +01:00
|
|
|
throw new Error(`Unknown command \`${commandName}\`.`);
|
2019-10-31 00:29:55 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
return "" +
|
2019-10-31 00:29:55 +01:00
|
|
|
`Invalid usage of ${commandName}.${errorMessage === undefined ? "" : ` ${errorMessage}`}
|
|
|
|
|
|
|
|
<b>Usage</b>
|
2019-11-02 15:16:50 +01:00
|
|
|
${command.usage}`.trimLines();
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private cat(input: InputArgs): string {
|
|
|
|
return input.args
|
|
|
|
.map(it => {
|
|
|
|
const node = this.fileSystem.getNode(it);
|
|
|
|
if (node === undefined || !(node instanceof File))
|
|
|
|
return `cat: ${it}: No such file`;
|
2019-10-31 01:34:36 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
return node.contents;
|
|
|
|
})
|
|
|
|
.join("\n");
|
2019-10-31 01:34:36 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private cd(input: InputArgs): string {
|
|
|
|
return this.fileSystem.cd(input.args[0]);
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private cp(input: InputArgs): string {
|
|
|
|
return this.fileSystem.cp(input.args[0], input.args[1], input.hasAnyOption(["r", "R"]));
|
2018-11-29 13:34:46 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private clear(): string {
|
|
|
|
return EscapeCharacters.Escape + EscapeCharacters.Clear;
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private echo(input: InputArgs): string {
|
|
|
|
return input.args.join(" ").replace("hunter2", "*******") + "\n";
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private exit(): string {
|
2019-10-29 12:36:03 +01:00
|
|
|
this.userSession.logOut();
|
2019-11-02 15:16:50 +01:00
|
|
|
return "";
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private help(input: InputArgs): string {
|
2019-10-21 02:25:42 +02:00
|
|
|
const commandNames = Object.keys(this.commands);
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-10-31 01:07:27 +01:00
|
|
|
if (input.args.length > 0) {
|
2019-11-02 15:16:50 +01:00
|
|
|
return input.args
|
|
|
|
.map(it => {
|
|
|
|
if (!this.commands.hasOwnProperty(it))
|
|
|
|
return `Unknown command ${it}.`;
|
|
|
|
|
|
|
|
const commandName = it.toLowerCase();
|
|
|
|
const command = this.commands[commandName];
|
|
|
|
|
|
|
|
return "" +
|
|
|
|
`<b>Name</b>
|
|
|
|
${commandName}
|
|
|
|
|
|
|
|
<b>Summary</b>
|
|
|
|
${command.summary}
|
|
|
|
|
|
|
|
<b>Usage</b>
|
|
|
|
${command.usage}
|
|
|
|
|
|
|
|
<b>Description</b>
|
|
|
|
${command.desc}`.trimLines();
|
|
|
|
})
|
|
|
|
.join("\n\n\n");
|
2018-11-28 22:07:51 +01:00
|
|
|
} else {
|
|
|
|
const commandWidth = Math.max.apply(null, commandNames.map(it => it.length)) + 4;
|
2019-10-31 00:48:40 +01:00
|
|
|
const commandPaddings = commandNames.map(it => commandWidth - it.length);
|
|
|
|
const commandLinks = commandNames
|
2019-10-31 23:22:37 +01:00
|
|
|
.map(it => `<a href="#" onclick="execute('help ${it}')">${it}</a>`)
|
2019-10-31 00:48:40 +01:00
|
|
|
.map((it, i) => `${it.padEnd(it.length + commandPaddings[i], ' ')}`);
|
|
|
|
const commandEntries = commandNames
|
|
|
|
.map((it, i) => `${commandLinks[i]}${this.commands[it].summary}`);
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
return "" +
|
2019-06-10 15:36:03 +02:00
|
|
|
`The source code of this website is <a href="https://git.fwdekker.com/FWDekker/fwdekker.com">available on git</a>.
|
2019-06-08 04:20:25 +02:00
|
|
|
|
|
|
|
<b>List of commands</b>
|
2018-11-29 01:08:58 +01:00
|
|
|
${commandEntries.join("\n")}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
Write "help [COMMAND]" or click a command in the list above for more information on a command.`.trimLines();
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private ls(input: InputArgs): string {
|
2019-10-31 01:07:27 +01:00
|
|
|
if (input.args.length <= 1)
|
2019-11-02 15:16:50 +01:00
|
|
|
return this.fileSystem.ls(input.args[0] || "");
|
2019-10-31 01:07:27 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
return input.args
|
2019-10-31 01:07:27 +01:00
|
|
|
.map(arg => "" +
|
2019-10-31 22:17:46 +01:00
|
|
|
`<b>${this.fileSystem.getPathTo(arg)}/</b>
|
2019-10-31 01:07:27 +01:00
|
|
|
${this.fileSystem.ls(arg)}`.trimLines())
|
2019-11-02 15:16:50 +01:00
|
|
|
.join("\n\n");
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private man(input: InputArgs): string {
|
2019-10-31 00:29:55 +01:00
|
|
|
if (input.args.length === 0)
|
2019-11-02 15:16:50 +01:00
|
|
|
return "What manual page do you want?";
|
2019-10-31 00:29:55 +01:00
|
|
|
else if (Object.keys(this.commands).indexOf(input.args[0]) < 0)
|
2019-11-02 15:16:50 +01:00
|
|
|
return `No manual entry for ${input.args[0]}`;
|
2019-06-10 15:31:46 +02:00
|
|
|
else
|
2019-10-31 00:29:55 +01:00
|
|
|
return this.help(input);
|
2018-12-02 17:01:11 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private mkdir(input: InputArgs): string {
|
|
|
|
return this.fileSystem.mkdirs(input.args);
|
2018-11-28 23:22:17 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private mv(input: InputArgs): string {
|
|
|
|
return this.fileSystem.mv(input.args[0], input.args[1]);
|
2018-11-29 13:18:48 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private open(input: InputArgs): string {
|
2019-10-31 00:29:55 +01:00
|
|
|
const fileName = input.args[0];
|
|
|
|
const target = input.hasAnyOption(["b", "blank"]) ? "_blank" : "_self";
|
2018-11-29 00:15:08 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
const node = this.fileSystem.getNode(fileName);
|
|
|
|
if (node === undefined)
|
2019-11-02 15:16:50 +01:00
|
|
|
return `The file '${fileName}' does not exist`;
|
2019-10-21 02:25:42 +02:00
|
|
|
if (!(node instanceof File))
|
2019-11-02 15:16:50 +01:00
|
|
|
return `'${fileName}' is not a file`;
|
2018-11-29 00:15:08 +01:00
|
|
|
|
2019-10-31 01:27:31 +01:00
|
|
|
window.open(node.contents, target);
|
2019-11-02 15:16:50 +01:00
|
|
|
return "";
|
2018-11-29 00:15:08 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private poweroff(): string {
|
2019-10-30 22:19:07 +01:00
|
|
|
const user = this.userSession.currentUser;
|
|
|
|
if (user === undefined)
|
2019-10-31 22:46:42 +01:00
|
|
|
throw new IllegalStateError("Cannot execute `poweroff` while not logged in.");
|
2019-10-30 22:19:07 +01:00
|
|
|
|
2019-10-30 22:13:28 +01:00
|
|
|
Cookies.set("poweroff", "true", {
|
2019-10-31 08:51:09 +01:00
|
|
|
"expires": new Date(new Date().setSeconds(new Date().getSeconds() + 30)),
|
2019-10-30 22:13:28 +01:00
|
|
|
"path": "/"
|
|
|
|
});
|
2018-11-28 23:22:17 +01:00
|
|
|
|
|
|
|
setTimeout(() => location.reload(), 2000);
|
2019-11-02 15:16:50 +01:00
|
|
|
return "" +
|
2018-11-28 23:22:17 +01:00
|
|
|
`Shutdown NOW!
|
|
|
|
|
2019-10-30 22:19:07 +01:00
|
|
|
*** FINAL System shutdown message from ${user.name}@fwdekker.com ***
|
2018-11-28 23:22:17 +01:00
|
|
|
|
|
|
|
System going down IMMEDIATELY
|
|
|
|
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
System shutdown time has arrived`.trimLines();
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private pwd(): string {
|
|
|
|
return this.fileSystem.cwd;
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private rm(input: InputArgs): string {
|
|
|
|
return this.fileSystem.rms(
|
|
|
|
input.args,
|
|
|
|
input.hasAnyOption(["f", "force"]),
|
|
|
|
input.hasAnyOption(["r", "R", "recursive"]),
|
|
|
|
input.hasOption("no-preserve-root")
|
|
|
|
);
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private rmdir(input: InputArgs): string {
|
|
|
|
return this.fileSystem.rmdirs(input.args);
|
2018-11-29 20:56:27 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private touch(input: InputArgs): string {
|
|
|
|
return this.fileSystem.createFiles(input.args);
|
2019-10-21 02:25:42 +02:00
|
|
|
}
|
2019-10-26 16:08:46 +02:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
private whoami(): string {
|
2019-10-29 12:36:03 +01:00
|
|
|
const user = this.userSession.currentUser;
|
2019-10-26 16:08:46 +02:00
|
|
|
if (user === undefined)
|
2019-10-31 22:46:42 +01:00
|
|
|
throw new IllegalStateError("Cannot execute `whoami` while not logged in.");
|
2019-10-26 16:08:46 +02:00
|
|
|
|
2019-11-02 15:16:50 +01:00
|
|
|
return user.description;
|
2019-10-26 16:08:46 +02:00
|
|
|
}
|
2019-10-21 02:25:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* A command that can be executed.
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
class Command {
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* The function to execute with the command is executed.
|
|
|
|
*/
|
2019-11-02 15:16:50 +01:00
|
|
|
readonly fun: (args: InputArgs) => string;
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* A short summary of what the command does.
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
readonly summary: string;
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* A string describing how the command is to be used.
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
readonly usage: string;
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* A longer description of what the command does and how its parameters work.
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
readonly desc: string;
|
2019-10-31 00:29:55 +01:00
|
|
|
/**
|
|
|
|
* A function that validates input for this command.
|
|
|
|
*/
|
|
|
|
readonly validator: InputValidator;
|
2019-10-21 02:25:42 +02:00
|
|
|
|
|
|
|
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
|
|
|
* Constructs a new command.
|
|
|
|
*
|
|
|
|
* @param fun the function to execute with the command is executed
|
|
|
|
* @param summary a short summary of what the command does
|
|
|
|
* @param usage a string describing how the command is to be used
|
|
|
|
* @param desc a longer description of what the command does and how its parameters work
|
2019-10-31 00:29:55 +01:00
|
|
|
* @param validator a function that validates input for this command
|
2019-10-29 12:36:03 +01:00
|
|
|
*/
|
2019-11-02 15:16:50 +01:00
|
|
|
constructor(fun: (args: InputArgs) => string, summary: string, usage: string, desc: string,
|
2019-10-31 00:29:55 +01:00
|
|
|
validator: InputValidator) {
|
2019-10-21 02:25:42 +02:00
|
|
|
this.fun = fun;
|
|
|
|
this.summary = summary;
|
|
|
|
this.usage = usage;
|
|
|
|
this.desc = desc;
|
2019-10-31 00:29:55 +01:00
|
|
|
this.validator = validator;
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
2018-11-28 22:07:51 +01:00
|
|
|
}
|
2018-11-29 08:58:53 +01:00
|
|
|
|
2019-10-31 00:29:55 +01:00
|
|
|
/**
|
|
|
|
* Validates the input of a command.
|
|
|
|
*/
|
|
|
|
class InputValidator {
|
2019-10-29 12:36:03 +01:00
|
|
|
/**
|
2019-10-31 00:29:55 +01:00
|
|
|
* The minimum number of arguments allowed.
|
|
|
|
*/
|
|
|
|
readonly minArgs: number;
|
|
|
|
/**
|
|
|
|
* The maximum number of arguments allowed.
|
|
|
|
*/
|
|
|
|
readonly maxArgs: number;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a new input validator.
|
2019-10-29 12:36:03 +01:00
|
|
|
*
|
2019-10-31 00:29:55 +01:00
|
|
|
* @param minArgs the minimum number of arguments allowed
|
|
|
|
* @param maxArgs the maximum number of arguments allowed
|
2019-10-29 12:36:03 +01:00
|
|
|
*/
|
2019-10-31 00:29:55 +01:00
|
|
|
constructor({minArgs = 0, maxArgs = Number.MAX_SAFE_INTEGER}: { minArgs?: number, maxArgs?: number } = {}) {
|
|
|
|
if (minArgs > maxArgs)
|
2019-10-31 22:46:42 +01:00
|
|
|
throw new IllegalStateError("`minArgs` must be less than or equal to `maxArgs`.");
|
2019-10-31 00:29:55 +01:00
|
|
|
|
|
|
|
this.minArgs = minArgs;
|
|
|
|
this.maxArgs = maxArgs;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns `[true]` if the input is valid, or `[false, string]` where the string is a reason if the input is not
|
|
|
|
* valid.
|
|
|
|
*
|
|
|
|
* @param input the input to validate
|
|
|
|
* @return `[true]` if the input is valid, or `[false, string]` where the string is a reason if the input is not
|
|
|
|
* valid
|
|
|
|
*/
|
|
|
|
validate(input: InputArgs): [true] | [false, string] {
|
|
|
|
if (this.minArgs === this.maxArgs && input.args.length !== this.minArgs)
|
2019-10-31 23:22:37 +01:00
|
|
|
return [false, `Expected ${this.args(this.minArgs)} but got ${input.args.length}.`];
|
2019-10-31 00:29:55 +01:00
|
|
|
if (input.args.length < this.minArgs)
|
2019-10-31 23:22:37 +01:00
|
|
|
return [false, `Expected at least ${this.args(this.minArgs)} but got ${input.args.length}.`];
|
2019-10-31 00:29:55 +01:00
|
|
|
if (input.args.length > this.maxArgs)
|
2019-10-31 23:22:37 +01:00
|
|
|
return [false, `Expected at most ${this.args(this.maxArgs)} but got ${input.args.length}.`];
|
2019-10-31 00:29:55 +01:00
|
|
|
|
|
|
|
return [true];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns `"1 argument"` if the given amount is `1` and returns `"$n arguments"` otherwise.
|
|
|
|
*
|
|
|
|
* @param amount the amount to check
|
|
|
|
* @return `"1 argument"` if the given amount is `1` and returns `"$n arguments"` otherwise.
|
|
|
|
*/
|
2019-10-31 23:22:37 +01:00
|
|
|
private args(amount: number): string {
|
2019-10-31 00:29:55 +01:00
|
|
|
return amount === 1 ? `1 argument` : `${amount} arguments`;
|
2019-10-29 12:36:03 +01:00
|
|
|
}
|
2018-11-29 08:58:53 +01:00
|
|
|
}
|