forked from tools/josh
Update to TypeScript 3.7
And clear empty lines and consistify error usage.
This commit is contained in:
parent
f5126065bf
commit
67898842ba
Binary file not shown.
|
@ -30,7 +30,7 @@
|
|||
"mocha": "^6.2.2",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^8.4.1",
|
||||
"typescript": "^3.6.4",
|
||||
"typescript": "^3.7.2",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-cli": "^3.3.10"
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as Cookies from "js-cookie";
|
|||
import "./Extensions"
|
||||
import {Environment} from "./Environment";
|
||||
import {Directory, File, FileSystem, Path} from "./FileSystem"
|
||||
import {IllegalStateError} from "./Shared";
|
||||
import {IllegalArgumentError, IllegalStateError} from "./Shared";
|
||||
import {InputArgs} from "./Shell";
|
||||
import {EscapeCharacters} from "./Terminal";
|
||||
import {UserList} from "./UserList";
|
||||
|
@ -72,10 +72,10 @@ export class Commands {
|
|||
`In its first form, the file or directory at SOURCE is copied to DESTINATION.
|
||||
If DESTINATION is an existing directory, SOURCE is copied into that directory, retaining the file name from SOURCE.
|
||||
If DESTINATION does not exist, SOURCE is copied to the exact location of DESTINATION.
|
||||
|
||||
|
||||
In its second form, all files and directories at SOURCES are copied to DESTINATION.
|
||||
DESTINATION must be a pre-existing directory, and all SOURCES are copied into DESTINATION retaining the file names from SOURCES.
|
||||
|
||||
|
||||
In both forms, sources are not copied if they are directories unless the -R options is given.`.trimLines(),
|
||||
new InputValidator({minArgs: 2})
|
||||
),
|
||||
|
@ -124,7 +124,7 @@ export class Commands {
|
|||
`make directories`,
|
||||
`mkdir [-p] DIRECTORY ...`,
|
||||
`Creates the directories given by DIRECTORY.
|
||||
|
||||
|
||||
If more than one directory is given, the directories are created in the order they are given in.
|
||||
If the -p option is given, parent directories that do not exist are created as well.`.trimLines(),
|
||||
new InputValidator({minArgs: 1})
|
||||
|
@ -137,7 +137,7 @@ export class Commands {
|
|||
`In its first form, the file or directory at SOURCE is moved to DESTINATION.
|
||||
If DESTINATION is an existing directory, SOURCE is moved into that directory, retaining the file name from SOURCE.
|
||||
If DESTINATION does not exist, SOURCE is moved to the exact location of DESTINATION.
|
||||
|
||||
|
||||
In its second form, all files and directories at SOURCES are moved to DESTINATION.
|
||||
DESTINATION must be a pre-existing directory, and all SOURCES are moved into DESTINATION retaining the file names from SOURCES.`.trimLines(),
|
||||
new InputValidator({minArgs: 2})
|
||||
|
@ -147,7 +147,7 @@ export class Commands {
|
|||
`open web page`,
|
||||
`open [-b | --blank] FILE`,
|
||||
`Opens the web page linked to by FILE in this browser window.
|
||||
|
||||
|
||||
If -b or --blank is set, the web page is opened in a new tab.`.trimLines(),
|
||||
new InputValidator({minArgs: 1, maxArgs: 1})
|
||||
),
|
||||
|
@ -170,13 +170,13 @@ export class Commands {
|
|||
`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.
|
||||
|
||||
|
||||
Unless --no-preserve-root is set, the root directory cannot be removed.`.trimLines(),
|
||||
new InputValidator({minArgs: 1})
|
||||
),
|
||||
|
@ -185,7 +185,7 @@ export class Commands {
|
|||
`remove directories`,
|
||||
`rmdir DIRECTORY...`,
|
||||
`Removes the directories given by DIRECTORY.
|
||||
|
||||
|
||||
If more than one directory is given, the directories are removed in the order they are given in.`.trimLines(),
|
||||
new InputValidator({minArgs: 1})
|
||||
),
|
||||
|
@ -202,7 +202,7 @@ export class Commands {
|
|||
`change file timestamps`,
|
||||
`touch FILE...`,
|
||||
`Update the access and modification times of each FILE to the current time.
|
||||
|
||||
|
||||
If a file does not exist, it is created.`.trimLines(),
|
||||
new InputValidator({minArgs: 1})
|
||||
),
|
||||
|
@ -252,13 +252,12 @@ export class Commands {
|
|||
private createUsageErrorOutput(commandName: string, errorMessage: string | undefined): string {
|
||||
const command = this.commands[commandName];
|
||||
if (command === undefined)
|
||||
throw new Error(`Unknown command \`${commandName}\`.`);
|
||||
throw new IllegalArgumentError(`Unknown command \`${commandName}\`.`);
|
||||
|
||||
return "" +
|
||||
`Invalid usage of ${commandName}.${errorMessage === undefined ? "" : ` ${errorMessage}`}
|
||||
|
||||
<b>Usage</b>
|
||||
${command.usage}`.trimLines();
|
||||
return `Invalid usage of ${commandName}. ${errorMessage ?? ""}
|
||||
|
||||
<b>Usage</b>
|
||||
${command.usage}`.trimLines();
|
||||
}
|
||||
|
||||
|
||||
|
@ -337,18 +336,17 @@ export class Commands {
|
|||
const commandName = it.toLowerCase();
|
||||
const command = this.commands[commandName];
|
||||
|
||||
return "" +
|
||||
`<b>Name</b>
|
||||
${commandName}
|
||||
|
||||
<b>Summary</b>
|
||||
${command.summary}
|
||||
return `<b>Name</b>
|
||||
${commandName}
|
||||
|
||||
<b>Usage</b>
|
||||
${command.usage}
|
||||
<b>Summary</b>
|
||||
${command.summary}
|
||||
|
||||
<b>Description</b>
|
||||
${command.desc}`.trimLines();
|
||||
<b>Usage</b>
|
||||
${command.usage}
|
||||
|
||||
<b>Description</b>
|
||||
${command.desc}`.trimLines();
|
||||
})
|
||||
.join("\n\n\n");
|
||||
} else {
|
||||
|
@ -360,13 +358,12 @@ export class Commands {
|
|||
const commandEntries = commandNames
|
||||
.map((it, i) => `${commandLinks[i]}${this.commands[it].summary}`);
|
||||
|
||||
return "" +
|
||||
`The source code of this website is <a href="https://git.fwdekker.com/FWDekker/fwdekker.com">available on git</a>.
|
||||
return `The source code of this website is <a href="https://git.fwdekker.com/FWDekker/fwdekker.com">available on git</a>.
|
||||
|
||||
<b>List of commands</b>
|
||||
${commandEntries.join("\n")}
|
||||
<b>List of commands</b>
|
||||
${commandEntries.join("\n")}
|
||||
|
||||
Write "help [COMMAND]" or click a command in the list above for more information on a command.`.trimLines();
|
||||
Write "help [COMMAND]" or click a command in the list above for more information on a command.`.trimLines();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -478,15 +475,14 @@ export class Commands {
|
|||
});
|
||||
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
return "" +
|
||||
`Shutdown NOW!
|
||||
|
||||
*** FINAL System shutdown message from ${userName}@fwdekker.com ***
|
||||
|
||||
System going down IMMEDIATELY
|
||||
|
||||
|
||||
System shutdown time has arrived`.trimLines();
|
||||
return `Shutdown NOW!
|
||||
|
||||
*** FINAL System shutdown message from ${userName}@fwdekker.com ***
|
||||
|
||||
System going down IMMEDIATELY
|
||||
|
||||
|
||||
System shutdown time has arrived`.trimLines();
|
||||
}
|
||||
|
||||
private pwd(): string {
|
||||
|
@ -578,19 +574,19 @@ export class Commands {
|
|||
// Move into directory
|
||||
if (!(this.fileSystem.get(destination) instanceof Directory)) {
|
||||
if (sources.length === 1)
|
||||
throw new Error(`'${destination}' already exists.`);
|
||||
throw new IllegalArgumentError(`'${destination}' already exists.`);
|
||||
else
|
||||
throw new Error(`'${destination}' is not a directory.`);
|
||||
throw new IllegalArgumentError(`'${destination}' is not a directory.`);
|
||||
}
|
||||
|
||||
mappings = sources.map(source => [source, destination.getChild(source.fileName)]);
|
||||
} else {
|
||||
// Move to exact location
|
||||
if (sources.length !== 1)
|
||||
throw new Error(`'${destination}' is not a directory.`);
|
||||
throw new IllegalArgumentError(`'${destination}' is not a directory.`);
|
||||
|
||||
if (!(this.fileSystem.get(destination.parent) instanceof Directory))
|
||||
throw new Error(`'${destination.parent}' is not a directory.`);
|
||||
throw new IllegalArgumentError(`'${destination.parent}' is not a directory.`);
|
||||
|
||||
mappings = sources.map(path => [path, destination]);
|
||||
}
|
||||
|
|
|
@ -94,10 +94,7 @@ export class Environment {
|
|||
* @param def the default value to return in case there is no environment variable with the given key
|
||||
*/
|
||||
getOrDefault(key: string, def: string): string {
|
||||
const value = this._variables[key];
|
||||
if (value === undefined)
|
||||
return def;
|
||||
return value;
|
||||
return this._variables[key] ?? def;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,7 @@ interface String {
|
|||
/**
|
||||
* Returns this string with all leading and trailing whitespace removed from each line.
|
||||
*/
|
||||
String.prototype.trimLines = function (): string {
|
||||
String.prototype.trimLines = function(): string {
|
||||
return this.split("\n").map(it => it.trim()).join("\n");
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,7 @@ String.prototype.trimLines = function (): string {
|
|||
* @param regex the regex to find matches with
|
||||
* @param replacement the string to replace matches with
|
||||
*/
|
||||
String.prototype.replaceAll = function (regex: RegExp, replacement: string): string {
|
||||
String.prototype.replaceAll = function(regex: RegExp, replacement: string): string {
|
||||
let string = this.toString();
|
||||
|
||||
while (regex.test(string))
|
||||
|
@ -37,8 +37,8 @@ interface Array<T> {
|
|||
* @param transform transforms elements of the array into a string that is used for comparing
|
||||
* @param caseSensitive `true` if and only if the comparator should be sensitive to the case of characters
|
||||
*/
|
||||
Array.prototype.sortAlphabetically = function (transform: (_: any) => string = (it) => it,
|
||||
caseSensitive: boolean = true) {
|
||||
Array.prototype.sortAlphabetically = function(transform: (_: any) => string = (it) => it,
|
||||
caseSensitive: boolean = true) {
|
||||
return this.sort((a, b) => {
|
||||
const aName = caseSensitive ? transform(a) : transform(a).toLowerCase();
|
||||
const bName = caseSensitive ? transform(b) : transform(b).toLowerCase();
|
||||
|
|
|
@ -56,14 +56,14 @@ export class FileSystem {
|
|||
if (createParents)
|
||||
this.add(target.parent, new Directory(), true);
|
||||
else
|
||||
throw new Error(`The directory '${target.parent}' does not exist.`);
|
||||
throw new IllegalArgumentError(`The directory '${target.parent}' does not exist.`);
|
||||
}
|
||||
|
||||
const parent = this.get(target.parent);
|
||||
if (!(parent instanceof Directory))
|
||||
throw new Error(`'${target.parent}' is not a directory.`);
|
||||
throw new IllegalArgumentError(`'${target.parent}' is not a directory.`);
|
||||
if (parent.hasNode(target.fileName))
|
||||
throw new Error(`A file or directory already exists at '${target}'.`);
|
||||
throw new IllegalArgumentError(`A file or directory already exists at '${target}'.`);
|
||||
|
||||
parent.addNode(target.fileName, node);
|
||||
}
|
||||
|
@ -79,13 +79,13 @@ export class FileSystem {
|
|||
*/
|
||||
copy(source: Path, destination: Path, isRecursive: boolean): void {
|
||||
if (destination.ancestors.indexOf(source) >= 0)
|
||||
throw new Error("Cannot move directory into itself.");
|
||||
throw new IllegalArgumentError("Cannot move directory into itself.");
|
||||
|
||||
const sourceNode = this.get(source);
|
||||
if (sourceNode === undefined)
|
||||
throw new Error(`File or directory '${source}' does not exist.`);
|
||||
throw new IllegalArgumentError(`File or directory '${source}' does not exist.`);
|
||||
if (sourceNode instanceof Directory && !isRecursive)
|
||||
throw new Error(`'${source}' is a directory.`);
|
||||
throw new IllegalArgumentError(`'${source}' is a directory.`);
|
||||
|
||||
this.add(destination, sourceNode.copy(), false);
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ export class FileSystem {
|
|||
return true;
|
||||
|
||||
const parent = this.get(target.parent);
|
||||
if (parent === undefined || !(parent instanceof Directory))
|
||||
if (!(parent instanceof Directory))
|
||||
return false;
|
||||
|
||||
return parent.hasNode(target.fileName);
|
||||
|
@ -132,11 +132,11 @@ export class FileSystem {
|
|||
*/
|
||||
move(source: Path, destination: Path): void {
|
||||
if (destination.ancestors.indexOf(source) >= 0)
|
||||
throw new Error("Cannot move directory into itself.");
|
||||
throw new IllegalArgumentError("Cannot move directory into itself.");
|
||||
|
||||
const sourceNode = this.get(source);
|
||||
if (sourceNode === undefined)
|
||||
throw new Error(`File or directory '${source}' does not exist.`);
|
||||
throw new IllegalArgumentError(`File or directory '${source}' does not exist.`);
|
||||
|
||||
this.add(destination, sourceNode, false);
|
||||
this.remove(source, true, true, false);
|
||||
|
@ -157,7 +157,7 @@ export class FileSystem {
|
|||
if (force)
|
||||
return;
|
||||
else
|
||||
throw new Error(`The file or directory '${targetPath}' does not exist.`);
|
||||
throw new IllegalArgumentError(`The file or directory '${targetPath}' does not exist.`);
|
||||
}
|
||||
|
||||
const parent = this.get(targetPath.parent);
|
||||
|
@ -166,9 +166,9 @@ export class FileSystem {
|
|||
|
||||
if (target instanceof Directory) {
|
||||
if (targetPath.toString() === "/" && !noPreserveRoot)
|
||||
throw new Error(`Cannot remove root directory.`);
|
||||
throw new IllegalArgumentError(`Cannot remove root directory.`);
|
||||
if (!recursive)
|
||||
throw new Error(`'${targetPath}' is a directory.`);
|
||||
throw new IllegalArgumentError(`'${targetPath}' is a directory.`);
|
||||
}
|
||||
|
||||
parent.removeNode(targetPath.fileName);
|
||||
|
@ -393,7 +393,7 @@ export class Directory extends Node {
|
|||
*/
|
||||
getNode(name: string): Node {
|
||||
if (!this.hasNode(name))
|
||||
throw new Error(`Directory does not have a node with name '${name}'.`);
|
||||
throw new IllegalArgumentError(`Directory does not have a node with name '${name}'.`);
|
||||
|
||||
return this._nodes[name];
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ export function parseCssPixels(string: string | null): number {
|
|||
return 0;
|
||||
} else {
|
||||
if (!string.endsWith("px"))
|
||||
throw new Error("CSS string is not expressed in pixels.");
|
||||
throw new IllegalArgumentError("CSS string is not expressed in pixels.");
|
||||
|
||||
return parseFloat(string);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as Cookies from "js-cookie";
|
|||
import {Commands} from "./Commands";
|
||||
import {Environment} from "./Environment";
|
||||
import {Directory, File, FileSystem, Node, Path} from "./FileSystem";
|
||||
import {asciiHeaderHtml, IllegalStateError, stripHtmlTags} from "./Shared";
|
||||
import {asciiHeaderHtml, IllegalArgumentError, IllegalStateError, stripHtmlTags} from "./Shared";
|
||||
import {EscapeCharacters, InputHistory} from "./Terminal";
|
||||
import {UserList} from "./UserList";
|
||||
|
||||
|
@ -62,16 +62,15 @@ export class Shell {
|
|||
if (this.environment.get("user") === "")
|
||||
return "";
|
||||
|
||||
return "" +
|
||||
`${asciiHeaderHtml}
|
||||
return `${asciiHeaderHtml}
|
||||
|
||||
Student MSc Computer Science <span class="smallScreenOnly">
|
||||
</span>@ <a href="https://www.tudelft.nl/en/">TU Delft</a>, the Netherlands
|
||||
<span class="wideScreenOnly">${(new Date()).toISOString()}
|
||||
</span>
|
||||
Type "<a href="#" onclick="execute('help');">help</a>" for help.
|
||||
Student MSc Computer Science <span class="smallScreenOnly">
|
||||
</span>@ <a href="https://www.tudelft.nl/en/">TU Delft</a>, the Netherlands
|
||||
<span class="wideScreenOnly">${(new Date()).toISOString()}
|
||||
</span>
|
||||
Type "<a href="#" onclick="execute('help');">help</a>" for help.
|
||||
|
||||
`.trimLines();
|
||||
`.trimLines();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,24 +79,15 @@ export class Shell {
|
|||
generatePrefix(): string {
|
||||
const userName = this.environment.get("user");
|
||||
if (userName === "") {
|
||||
if (this.attemptUser === undefined)
|
||||
return "login as: ";
|
||||
else
|
||||
return `Password for ${this.attemptUser}@fwdekker.com: `;
|
||||
return this.attemptUser === undefined
|
||||
? "login as: "
|
||||
: `Password for ${this.attemptUser}@fwdekker.com: `;
|
||||
}
|
||||
|
||||
const cwd = new Path(this.environment.get("cwd"));
|
||||
const parts = cwd.ancestors.reverse();
|
||||
parts.push(cwd);
|
||||
const link = parts
|
||||
.map(part => {
|
||||
const node = this.fileSystem.get(part);
|
||||
if (node === undefined)
|
||||
throw new IllegalStateError(`Ancestor '${part}' of cwd does not exist.`);
|
||||
|
||||
return node.nameString(part.fileName + "/", part);
|
||||
})
|
||||
.join("");
|
||||
const link = cwd.ancestors.reverse()
|
||||
.concat(cwd)
|
||||
.map(part => this.fileSystem.get(part)?.nameString(part.fileName + "/", part)).join("");
|
||||
|
||||
return `${userName}@fwdekker.com <span class="prefixPath">${link}</span>> `;
|
||||
}
|
||||
|
@ -178,7 +168,7 @@ export class Shell {
|
|||
|
||||
const target = this.fileSystem.get(path);
|
||||
if (!(target instanceof File))
|
||||
throw new Error("File unexpectedly disappeared since last check.");
|
||||
throw new IllegalStateError("File unexpectedly disappeared since last check.");
|
||||
|
||||
if (append)
|
||||
target.contents += data;
|
||||
|
@ -427,7 +417,7 @@ export class InputParser {
|
|||
switch (char) {
|
||||
case "\\":
|
||||
if (i === input.length - 1)
|
||||
throw new Error("Unexpected end of input. `\\` was used but there was nothing to escape.");
|
||||
throw new IllegalArgumentError("Unexpected end of input. `\\` was used but there was nothing to escape.");
|
||||
|
||||
const nextChar = input[i + 1];
|
||||
if (isInSingleQuotes || isInDoubleQuotes)
|
||||
|
@ -485,7 +475,7 @@ export class InputParser {
|
|||
}
|
||||
|
||||
if (isInSingleQuotes || isInDoubleQuotes)
|
||||
throw new Error("Unexpected end of input. Missing closing quotation mark.");
|
||||
throw new IllegalArgumentError("Unexpected end of input. Missing closing quotation mark.");
|
||||
|
||||
return [token, ""];
|
||||
}
|
||||
|
@ -544,7 +534,7 @@ export class InputParser {
|
|||
|
||||
const argsParts = arg.split(/=(.*)/, 2);
|
||||
if (argsParts.length === 0 || argsParts.length > 2)
|
||||
throw new Error("Unexpected number of parts.");
|
||||
throw new IllegalArgumentError("Unexpected number of parts.");
|
||||
if (argsParts[0].indexOf(' ') >= 0)
|
||||
break;
|
||||
|
||||
|
@ -565,7 +555,7 @@ export class InputParser {
|
|||
options[keys] = value;
|
||||
} else {
|
||||
if (value !== null)
|
||||
throw new Error("Cannot assign value to multiple short options.");
|
||||
throw new IllegalArgumentError("Cannot assign value to multiple short options.");
|
||||
|
||||
for (const key of keys)
|
||||
options[key] = value;
|
||||
|
|
Loading…
Reference in New Issue