forked from tools/josh
1
0
Fork 0

Update to TypeScript 3.7

And clear empty lines and consistify error usage.
This commit is contained in:
Florine W. Dekker 2019-11-06 14:47:14 +01:00
parent f5126065bf
commit 67898842ba
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
8 changed files with 81 additions and 98 deletions

BIN
package-lock.json generated

Binary file not shown.

View File

@ -30,7 +30,7 @@
"mocha": "^6.2.2", "mocha": "^6.2.2",
"ts-loader": "^6.2.1", "ts-loader": "^6.2.1",
"ts-node": "^8.4.1", "ts-node": "^8.4.1",
"typescript": "^3.6.4", "typescript": "^3.7.2",
"webpack": "^4.41.2", "webpack": "^4.41.2",
"webpack-cli": "^3.3.10" "webpack-cli": "^3.3.10"
} }

View File

@ -2,7 +2,7 @@ import * as Cookies from "js-cookie";
import "./Extensions" import "./Extensions"
import {Environment} from "./Environment"; import {Environment} from "./Environment";
import {Directory, File, FileSystem, Path} from "./FileSystem" import {Directory, File, FileSystem, Path} from "./FileSystem"
import {IllegalStateError} from "./Shared"; import {IllegalArgumentError, IllegalStateError} from "./Shared";
import {InputArgs} from "./Shell"; import {InputArgs} from "./Shell";
import {EscapeCharacters} from "./Terminal"; import {EscapeCharacters} from "./Terminal";
import {UserList} from "./UserList"; 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. `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 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. 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. 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. 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(), In both forms, sources are not copied if they are directories unless the -R options is given.`.trimLines(),
new InputValidator({minArgs: 2}) new InputValidator({minArgs: 2})
), ),
@ -124,7 +124,7 @@ export class Commands {
`make directories`, `make directories`,
`mkdir [-p] DIRECTORY ...`, `mkdir [-p] DIRECTORY ...`,
`Creates the directories given by 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 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(), If the -p option is given, parent directories that do not exist are created as well.`.trimLines(),
new InputValidator({minArgs: 1}) 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. `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 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. 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. 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(), 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}) new InputValidator({minArgs: 2})
@ -147,7 +147,7 @@ export class Commands {
`open web page`, `open web page`,
`open [-b | --blank] FILE`, `open [-b | --blank] FILE`,
`Opens the web page linked to by FILE in this browser window. `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(), If -b or --blank is set, the web page is opened in a new tab.`.trimLines(),
new InputValidator({minArgs: 1, maxArgs: 1}) new InputValidator({minArgs: 1, maxArgs: 1})
), ),
@ -170,13 +170,13 @@ export class Commands {
`remove file`, `remove file`,
`rm [-f | --force] [-r | -R | --recursive] [--no-preserve-root] FILE...`, `rm [-f | --force] [-r | -R | --recursive] [--no-preserve-root] FILE...`,
`Removes the files given by 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 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 -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. 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(), Unless --no-preserve-root is set, the root directory cannot be removed.`.trimLines(),
new InputValidator({minArgs: 1}) new InputValidator({minArgs: 1})
), ),
@ -185,7 +185,7 @@ export class Commands {
`remove directories`, `remove directories`,
`rmdir DIRECTORY...`, `rmdir DIRECTORY...`,
`Removes the directories given by 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(), If more than one directory is given, the directories are removed in the order they are given in.`.trimLines(),
new InputValidator({minArgs: 1}) new InputValidator({minArgs: 1})
), ),
@ -202,7 +202,7 @@ export class Commands {
`change file timestamps`, `change file timestamps`,
`touch FILE...`, `touch FILE...`,
`Update the access and modification times of each FILE to the current time. `Update the access and modification times of each FILE to the current time.
If a file does not exist, it is created.`.trimLines(), If a file does not exist, it is created.`.trimLines(),
new InputValidator({minArgs: 1}) new InputValidator({minArgs: 1})
), ),
@ -252,13 +252,12 @@ export class Commands {
private createUsageErrorOutput(commandName: string, errorMessage: string | undefined): string { private createUsageErrorOutput(commandName: string, errorMessage: string | undefined): string {
const command = this.commands[commandName]; const command = this.commands[commandName];
if (command === undefined) if (command === undefined)
throw new Error(`Unknown command \`${commandName}\`.`); throw new IllegalArgumentError(`Unknown command \`${commandName}\`.`);
return "" + return `Invalid usage of ${commandName}. ${errorMessage ?? ""}
`Invalid usage of ${commandName}.${errorMessage === undefined ? "" : ` ${errorMessage}`}
<b>Usage</b>
<b>Usage</b> ${command.usage}`.trimLines();
${command.usage}`.trimLines();
} }
@ -337,18 +336,17 @@ export class Commands {
const commandName = it.toLowerCase(); const commandName = it.toLowerCase();
const command = this.commands[commandName]; const command = this.commands[commandName];
return "" + return `<b>Name</b>
`<b>Name</b> ${commandName}
${commandName}
<b>Summary</b>
${command.summary}
<b>Usage</b> <b>Summary</b>
${command.usage} ${command.summary}
<b>Description</b> <b>Usage</b>
${command.desc}`.trimLines(); ${command.usage}
<b>Description</b>
${command.desc}`.trimLines();
}) })
.join("\n\n\n"); .join("\n\n\n");
} else { } else {
@ -360,13 +358,12 @@ export class Commands {
const commandEntries = commandNames const commandEntries = commandNames
.map((it, i) => `${commandLinks[i]}${this.commands[it].summary}`); .map((it, i) => `${commandLinks[i]}${this.commands[it].summary}`);
return "" + return `The source code of this website is <a href="https://git.fwdekker.com/FWDekker/fwdekker.com">available on git</a>.
`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> <b>List of commands</b>
${commandEntries.join("\n")} ${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); setTimeout(() => location.reload(), 2000);
return "" + return `Shutdown NOW!
`Shutdown NOW!
*** FINAL System shutdown message from ${userName}@fwdekker.com ***
*** FINAL System shutdown message from ${userName}@fwdekker.com ***
System going down IMMEDIATELY
System going down IMMEDIATELY
System shutdown time has arrived`.trimLines();
System shutdown time has arrived`.trimLines();
} }
private pwd(): string { private pwd(): string {
@ -578,19 +574,19 @@ export class Commands {
// Move into directory // Move into directory
if (!(this.fileSystem.get(destination) instanceof Directory)) { if (!(this.fileSystem.get(destination) instanceof Directory)) {
if (sources.length === 1) if (sources.length === 1)
throw new Error(`'${destination}' already exists.`); throw new IllegalArgumentError(`'${destination}' already exists.`);
else 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)]); mappings = sources.map(source => [source, destination.getChild(source.fileName)]);
} else { } else {
// Move to exact location // Move to exact location
if (sources.length !== 1) 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)) 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]); mappings = sources.map(path => [path, destination]);
} }

View File

@ -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 * @param def the default value to return in case there is no environment variable with the given key
*/ */
getOrDefault(key: string, def: string): string { getOrDefault(key: string, def: string): string {
const value = this._variables[key]; return this._variables[key] ?? def;
if (value === undefined)
return def;
return value;
} }
/** /**

View File

@ -7,7 +7,7 @@ interface String {
/** /**
* Returns this string with all leading and trailing whitespace removed from each line. * 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"); 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 regex the regex to find matches with
* @param replacement the string to replace 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(); let string = this.toString();
while (regex.test(string)) 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 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 * @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, Array.prototype.sortAlphabetically = function(transform: (_: any) => string = (it) => it,
caseSensitive: boolean = true) { caseSensitive: boolean = true) {
return this.sort((a, b) => { return this.sort((a, b) => {
const aName = caseSensitive ? transform(a) : transform(a).toLowerCase(); const aName = caseSensitive ? transform(a) : transform(a).toLowerCase();
const bName = caseSensitive ? transform(b) : transform(b).toLowerCase(); const bName = caseSensitive ? transform(b) : transform(b).toLowerCase();

View File

@ -56,14 +56,14 @@ export class FileSystem {
if (createParents) if (createParents)
this.add(target.parent, new Directory(), true); this.add(target.parent, new Directory(), true);
else 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); const parent = this.get(target.parent);
if (!(parent instanceof Directory)) 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)) 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); parent.addNode(target.fileName, node);
} }
@ -79,13 +79,13 @@ export class FileSystem {
*/ */
copy(source: Path, destination: Path, isRecursive: boolean): void { copy(source: Path, destination: Path, isRecursive: boolean): void {
if (destination.ancestors.indexOf(source) >= 0) 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); const sourceNode = this.get(source);
if (sourceNode === undefined) 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) 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); this.add(destination, sourceNode.copy(), false);
} }
@ -116,7 +116,7 @@ export class FileSystem {
return true; return true;
const parent = this.get(target.parent); const parent = this.get(target.parent);
if (parent === undefined || !(parent instanceof Directory)) if (!(parent instanceof Directory))
return false; return false;
return parent.hasNode(target.fileName); return parent.hasNode(target.fileName);
@ -132,11 +132,11 @@ export class FileSystem {
*/ */
move(source: Path, destination: Path): void { move(source: Path, destination: Path): void {
if (destination.ancestors.indexOf(source) >= 0) 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); const sourceNode = this.get(source);
if (sourceNode === undefined) 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.add(destination, sourceNode, false);
this.remove(source, true, true, false); this.remove(source, true, true, false);
@ -157,7 +157,7 @@ export class FileSystem {
if (force) if (force)
return; return;
else 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); const parent = this.get(targetPath.parent);
@ -166,9 +166,9 @@ export class FileSystem {
if (target instanceof Directory) { if (target instanceof Directory) {
if (targetPath.toString() === "/" && !noPreserveRoot) if (targetPath.toString() === "/" && !noPreserveRoot)
throw new Error(`Cannot remove root directory.`); throw new IllegalArgumentError(`Cannot remove root directory.`);
if (!recursive) if (!recursive)
throw new Error(`'${targetPath}' is a directory.`); throw new IllegalArgumentError(`'${targetPath}' is a directory.`);
} }
parent.removeNode(targetPath.fileName); parent.removeNode(targetPath.fileName);
@ -393,7 +393,7 @@ export class Directory extends Node {
*/ */
getNode(name: string): Node { getNode(name: string): Node {
if (!this.hasNode(name)) 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]; return this._nodes[name];
} }

View File

@ -78,7 +78,7 @@ export function parseCssPixels(string: string | null): number {
return 0; return 0;
} else { } else {
if (!string.endsWith("px")) 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); return parseFloat(string);
} }

View File

@ -2,7 +2,7 @@ import * as Cookies from "js-cookie";
import {Commands} from "./Commands"; import {Commands} from "./Commands";
import {Environment} from "./Environment"; import {Environment} from "./Environment";
import {Directory, File, FileSystem, Node, Path} from "./FileSystem"; 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 {EscapeCharacters, InputHistory} from "./Terminal";
import {UserList} from "./UserList"; import {UserList} from "./UserList";
@ -62,16 +62,15 @@ export class Shell {
if (this.environment.get("user") === "") if (this.environment.get("user") === "")
return ""; return "";
return "" + return `${asciiHeaderHtml}
`${asciiHeaderHtml}
Student MSc Computer Science <span class="smallScreenOnly"> Student MSc Computer Science <span class="smallScreenOnly">
</span>@ <a href="https://www.tudelft.nl/en/">TU Delft</a>, the Netherlands </span>@ <a href="https://www.tudelft.nl/en/">TU Delft</a>, the Netherlands
<span class="wideScreenOnly">${(new Date()).toISOString()} <span class="wideScreenOnly">${(new Date()).toISOString()}
</span> </span>
Type "<a href="#" onclick="execute('help');">help</a>" for help. Type "<a href="#" onclick="execute('help');">help</a>" for help.
`.trimLines(); `.trimLines();
} }
/** /**
@ -80,24 +79,15 @@ export class Shell {
generatePrefix(): string { generatePrefix(): string {
const userName = this.environment.get("user"); const userName = this.environment.get("user");
if (userName === "") { if (userName === "") {
if (this.attemptUser === undefined) return this.attemptUser === undefined
return "login as: "; ? "login as: "
else : `Password for ${this.attemptUser}@fwdekker.com: `;
return `Password for ${this.attemptUser}@fwdekker.com: `;
} }
const cwd = new Path(this.environment.get("cwd")); const cwd = new Path(this.environment.get("cwd"));
const parts = cwd.ancestors.reverse(); const link = cwd.ancestors.reverse()
parts.push(cwd); .concat(cwd)
const link = parts .map(part => this.fileSystem.get(part)?.nameString(part.fileName + "/", part)).join("");
.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("");
return `${userName}@fwdekker.com <span class="prefixPath">${link}</span>&gt; `; return `${userName}@fwdekker.com <span class="prefixPath">${link}</span>&gt; `;
} }
@ -178,7 +168,7 @@ export class Shell {
const target = this.fileSystem.get(path); const target = this.fileSystem.get(path);
if (!(target instanceof File)) if (!(target instanceof File))
throw new Error("File unexpectedly disappeared since last check."); throw new IllegalStateError("File unexpectedly disappeared since last check.");
if (append) if (append)
target.contents += data; target.contents += data;
@ -427,7 +417,7 @@ export class InputParser {
switch (char) { switch (char) {
case "\\": case "\\":
if (i === input.length - 1) 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]; const nextChar = input[i + 1];
if (isInSingleQuotes || isInDoubleQuotes) if (isInSingleQuotes || isInDoubleQuotes)
@ -485,7 +475,7 @@ export class InputParser {
} }
if (isInSingleQuotes || isInDoubleQuotes) 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, ""]; return [token, ""];
} }
@ -544,7 +534,7 @@ export class InputParser {
const argsParts = arg.split(/=(.*)/, 2); const argsParts = arg.split(/=(.*)/, 2);
if (argsParts.length === 0 || argsParts.length > 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) if (argsParts[0].indexOf(' ') >= 0)
break; break;
@ -565,7 +555,7 @@ export class InputParser {
options[keys] = value; options[keys] = value;
} else { } else {
if (value !== null) 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) for (const key of keys)
options[key] = value; options[key] = value;