forked from tools/josh
Fix #50 and add error streams
This commit is contained in:
parent
bdbf42e074
commit
c7f2d6b0f6
|
@ -6,7 +6,7 @@ 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";
|
||||||
import {OutputStream} from "./Stream";
|
import {StreamSet} from "./Stream";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -227,9 +227,9 @@ export class Commands {
|
||||||
* Parses and executes the given input string and returns the exit code of that command.
|
* Parses and executes the given input string and returns the exit code of that command.
|
||||||
*
|
*
|
||||||
* @param input the input string to parse and execute
|
* @param input the input string to parse and execute
|
||||||
* @param output the stream to write output to
|
* @param streams the streams to interact with
|
||||||
*/
|
*/
|
||||||
execute(input: InputArgs, output: OutputStream): number {
|
execute(input: InputArgs, streams: StreamSet): number {
|
||||||
if (input.command === "factory-reset") {
|
if (input.command === "factory-reset") {
|
||||||
Persistence.reset();
|
Persistence.reset();
|
||||||
location.reload();
|
location.reload();
|
||||||
|
@ -239,18 +239,18 @@ export class Commands {
|
||||||
if (input.command === "")
|
if (input.command === "")
|
||||||
return 0;
|
return 0;
|
||||||
if (!this.commands.hasOwnProperty(input.command)) {
|
if (!this.commands.hasOwnProperty(input.command)) {
|
||||||
output.writeLine(`Unknown command '${input.command}'`);
|
streams.err.writeLine(`Unknown command '${input.command}'`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = this.commands[input.command];
|
const command = this.commands[input.command];
|
||||||
const validation = command.validator.validate(input);
|
const validation = command.validator.validate(input);
|
||||||
if (!validation[0]) {
|
if (!validation[0]) {
|
||||||
output.writeLine(this.createUsageErrorOutput(input.command, validation[1]));
|
streams.err.writeLine(this.createUsageErrorOutput(input.command, validation[1]));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return command.fun.bind(this)(input, output);
|
return command.fun.bind(this)(input, streams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -271,28 +271,31 @@ export class Commands {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private cat(input: InputArgs, output: OutputStream): number {
|
private cat(input: InputArgs, streams: StreamSet): number {
|
||||||
return input.args
|
return input.args
|
||||||
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
||||||
.map(path => {
|
.map(path => {
|
||||||
if (!this.fileSystem.has(path)) {
|
if (!this.fileSystem.has(path)) {
|
||||||
output.writeLine(`cat: ${path}: No such file`);
|
streams.err.writeLine(`cat: ${path}: No such file`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = this.fileSystem.get(path);
|
const node = this.fileSystem.get(path);
|
||||||
if (!(node instanceof File)) {
|
if (!(node instanceof File)) {
|
||||||
output.writeLine(`cat: ${path}: No such file`);
|
streams.err.writeLine(`cat: ${path}: No such file`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.writeLine(node.contents);
|
if (node.contents.endsWith("\n"))
|
||||||
|
streams.out.write(node.contents);
|
||||||
|
else
|
||||||
|
streams.out.writeLine(node.contents);
|
||||||
return 0;
|
return 0;
|
||||||
})
|
})
|
||||||
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private cd(input: InputArgs, output: OutputStream): number {
|
private cd(input: InputArgs, streams: StreamSet): number {
|
||||||
if (input.args[0] === "") {
|
if (input.args[0] === "") {
|
||||||
this.environment.set("cwd", this.environment.get("home"));
|
this.environment.set("cwd", this.environment.get("home"));
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -300,7 +303,7 @@ export class Commands {
|
||||||
|
|
||||||
const path = Path.interpret(this.environment.get("cwd"), input.args[0]);
|
const path = Path.interpret(this.environment.get("cwd"), input.args[0]);
|
||||||
if (!this.fileSystem.has(path)) {
|
if (!this.fileSystem.has(path)) {
|
||||||
output.writeLine(`The directory '${path}' does not exist.`);
|
streams.err.writeLine(`The directory '${path}' does not exist.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +311,7 @@ export class Commands {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private cp(input: InputArgs, output: OutputStream): number {
|
private cp(input: InputArgs, streams: StreamSet): number {
|
||||||
try {
|
try {
|
||||||
return this.moveCopyMappings(input)
|
return this.moveCopyMappings(input)
|
||||||
.map(([source, destination]) => {
|
.map(([source, destination]) => {
|
||||||
|
@ -316,29 +319,29 @@ export class Commands {
|
||||||
this.fileSystem.copy(source, destination, input.hasAnyOption(["r", "R", "recursive"]));
|
this.fileSystem.copy(source, destination, input.hasAnyOption(["r", "R", "recursive"]));
|
||||||
return 0;
|
return 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
output.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
output.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private clear(_: InputArgs, output: OutputStream): number {
|
private clear(_: InputArgs, streams: StreamSet): number {
|
||||||
output.write(EscapeCharacters.Escape + EscapeCharacters.Clear);
|
streams.err.write(EscapeCharacters.Escape + EscapeCharacters.Clear);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private echo(input: InputArgs, output: OutputStream): number {
|
private echo(input: InputArgs, streams: StreamSet): number {
|
||||||
const message = input.args.join(" ").replace("hunter2", "*******");
|
const message = input.args.join(" ").replace("hunter2", "*******");
|
||||||
|
|
||||||
if (input.hasOption("n"))
|
if (input.hasOption("n"))
|
||||||
output.write(message);
|
streams.out.write(message);
|
||||||
else
|
else
|
||||||
output.writeLine(message);
|
streams.out.writeLine(message);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -348,24 +351,24 @@ export class Commands {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private help(input: InputArgs, output: OutputStream): number {
|
private help(input: InputArgs, streams: StreamSet): number {
|
||||||
const commandNames = Object.keys(this.commands);
|
const commandNames = Object.keys(this.commands);
|
||||||
|
|
||||||
if (input.args.length > 0) {
|
if (input.args.length > 0) {
|
||||||
return input.args
|
return input.args
|
||||||
.map((it, i) => {
|
.map((it, i) => {
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
output.write("\n\n");
|
streams.out.write("\n\n");
|
||||||
|
|
||||||
if (!this.commands.hasOwnProperty(it)) {
|
if (!this.commands.hasOwnProperty(it)) {
|
||||||
output.writeLine(`Unknown command ${it}.`);
|
streams.out.writeLine(`Unknown command ${it}.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandName = it.toLowerCase();
|
const commandName = it.toLowerCase();
|
||||||
const command = this.commands[commandName];
|
const command = this.commands[commandName];
|
||||||
|
|
||||||
output.writeLine(
|
streams.out.writeLine(
|
||||||
`<b>Name</b>
|
`<b>Name</b>
|
||||||
${commandName}
|
${commandName}
|
||||||
|
|
||||||
|
@ -390,7 +393,7 @@ 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}`);
|
||||||
|
|
||||||
output.writeLine(
|
streams.out.writeLine(
|
||||||
`The source code of this website is \\\
|
`The source code of this website is \\\
|
||||||
<a href="https://git.fwdekker.com/FWDekker/fwdekker.com">available on git</a>.
|
<a href="https://git.fwdekker.com/FWDekker/fwdekker.com">available on git</a>.
|
||||||
|
|
||||||
|
@ -403,20 +406,20 @@ export class Commands {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ls(input: InputArgs, output: OutputStream): number {
|
private ls(input: InputArgs, streams: StreamSet): number {
|
||||||
return (input.args.length === 0 ? [""] : input.args)
|
return (input.args.length === 0 ? [""] : input.args)
|
||||||
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
||||||
.map((path, i) => {
|
.map((path, i) => {
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
output.write("\n");
|
streams.out.write("\n");
|
||||||
|
|
||||||
const node = this.fileSystem.get(path);
|
const node = this.fileSystem.get(path);
|
||||||
if (node === undefined) {
|
if (node === undefined) {
|
||||||
output.writeLine(`The directory '${path}' does not exist.`);
|
streams.err.writeLine(`The directory '${path}' does not exist.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (!(node instanceof Directory)) {
|
if (!(node instanceof Directory)) {
|
||||||
output.writeLine(`'${path}' is not a directory.`);
|
streams.err.writeLine(`'${path}' is not a directory.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,27 +446,27 @@ export class Commands {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (input.args.length > 1)
|
if (input.args.length > 1)
|
||||||
output.writeLine(`<b>${path}</b>`);
|
streams.out.writeLine(`<b>${path}</b>`);
|
||||||
output.writeLine(dirList.concat(fileList).join("\n"));
|
streams.out.writeLine(dirList.concat(fileList).join("\n"));
|
||||||
return 0;
|
return 0;
|
||||||
})
|
})
|
||||||
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private man(input: InputArgs, output: OutputStream): number {
|
private man(input: InputArgs, streams: StreamSet): number {
|
||||||
if (input.args.length === 0) {
|
if (input.args.length === 0) {
|
||||||
output.writeLine("What manual page do you want?");
|
streams.out.writeLine("What manual page do you want?");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (Object.keys(this.commands).includes(input.args[0])) {
|
if (!Object.keys(this.commands).includes(input.args[0])) {
|
||||||
output.writeLine(`No manual entry for ${input.args[0]}`);
|
streams.err.writeLine(`No manual entry for '${input.args[0]}'.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.help(input, output);
|
return this.help(input, streams);
|
||||||
}
|
}
|
||||||
|
|
||||||
private mkdir(input: InputArgs, output: OutputStream): number {
|
private mkdir(input: InputArgs, streams: StreamSet): number {
|
||||||
return input.args
|
return input.args
|
||||||
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
||||||
.map(path => {
|
.map(path => {
|
||||||
|
@ -471,14 +474,14 @@ export class Commands {
|
||||||
this.fileSystem.add(path, new Directory(), input.hasOption("p"));
|
this.fileSystem.add(path, new Directory(), input.hasOption("p"));
|
||||||
return 0;
|
return 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
output.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private mv(input: InputArgs, output: OutputStream): number {
|
private mv(input: InputArgs, streams: StreamSet): number {
|
||||||
try {
|
try {
|
||||||
return this.moveCopyMappings(input)
|
return this.moveCopyMappings(input)
|
||||||
.map(([source, destination]) => {
|
.map(([source, destination]) => {
|
||||||
|
@ -486,28 +489,28 @@ export class Commands {
|
||||||
this.fileSystem.move(source, destination);
|
this.fileSystem.move(source, destination);
|
||||||
return 0;
|
return 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
output.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
output.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private open(input: InputArgs, output: OutputStream): number {
|
private open(input: InputArgs, streams: StreamSet): number {
|
||||||
const path = Path.interpret(this.environment.get("cwd"), input.args[0]);
|
const path = Path.interpret(this.environment.get("cwd"), input.args[0]);
|
||||||
const target = input.hasAnyOption(["b", "blank"]) ? "_blank" : "_self";
|
const target = input.hasAnyOption(["b", "blank"]) ? "_blank" : "_self";
|
||||||
|
|
||||||
const node = this.fileSystem.get(path);
|
const node = this.fileSystem.get(path);
|
||||||
if (node === undefined) {
|
if (node === undefined) {
|
||||||
output.writeLine(`The file '${path}' does not exist`);
|
streams.err.writeLine(`The file '${path}' does not exist`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (!(node instanceof File)) {
|
if (!(node instanceof File)) {
|
||||||
output.writeLine(`'${path}' is not a file`);
|
streams.err.writeLine(`'${path}' is not a file`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,17 +518,17 @@ export class Commands {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private poweroff(_: InputArgs, output: OutputStream): number {
|
private poweroff(_: InputArgs, streams: StreamSet): number {
|
||||||
const userName = this.environment.get("user");
|
const userName = this.environment.get("user");
|
||||||
if (userName === "") {
|
if (userName === "") {
|
||||||
output.writeLine("Cannot execute `poweroff` while not logged in.");
|
streams.err.writeLine("Cannot execute `poweroff` while not logged in.");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Persistence.setPoweroff(true);
|
Persistence.setPoweroff(true);
|
||||||
setTimeout(() => location.reload(), 2000);
|
setTimeout(() => location.reload(), 2000);
|
||||||
|
|
||||||
output.writeLine(
|
streams.out.writeLine(
|
||||||
`Shutdown NOW!
|
`Shutdown NOW!
|
||||||
|
|
||||||
*** FINAL System shutdown message from ${userName}@fwdekker.com ***
|
*** FINAL System shutdown message from ${userName}@fwdekker.com ***
|
||||||
|
@ -538,12 +541,12 @@ export class Commands {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private pwd(_: InputArgs, output: OutputStream): number {
|
private pwd(_: InputArgs, streams: StreamSet): number {
|
||||||
output.writeLine(this.environment.get("cwd") ?? "");
|
streams.out.writeLine(this.environment.get("cwd") ?? "");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private rm(input: InputArgs, output: OutputStream): number {
|
private rm(input: InputArgs, streams: StreamSet): number {
|
||||||
return input.args
|
return input.args
|
||||||
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
||||||
.map(path => {
|
.map(path => {
|
||||||
|
@ -553,16 +556,16 @@ export class Commands {
|
||||||
if (input.hasAnyOption(["f", "force"]))
|
if (input.hasAnyOption(["f", "force"]))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
output.writeLine(`The file '${path}' does not exist.`);
|
streams.err.writeLine(`The file '${path}' does not exist.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (target instanceof Directory) {
|
if (target instanceof Directory) {
|
||||||
if (!input.hasAnyOption(["r", "R", "recursive"])) {
|
if (!input.hasAnyOption(["r", "R", "recursive"])) {
|
||||||
output.writeLine(`'${path}' is a directory.`);
|
streams.err.writeLine(`'${path}' is a directory.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (path.toString() === "/" && !input.hasOption("no-preserve-root")) {
|
if (path.toString() === "/" && !input.hasOption("no-preserve-root")) {
|
||||||
output.writeLine("Cannot remove root directory.");
|
streams.err.writeLine("Cannot remove root directory.");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -570,57 +573,57 @@ export class Commands {
|
||||||
this.fileSystem.remove(path);
|
this.fileSystem.remove(path);
|
||||||
return 0;
|
return 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
output.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private rmdir(input: InputArgs, output: OutputStream): number {
|
private rmdir(input: InputArgs, streams: StreamSet): number {
|
||||||
return input.args
|
return input.args
|
||||||
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
||||||
.map(path => {
|
.map(path => {
|
||||||
try {
|
try {
|
||||||
const target = this.fileSystem.get(path);
|
const target = this.fileSystem.get(path);
|
||||||
if (target === undefined) {
|
if (target === undefined) {
|
||||||
output.writeLine(`'${path}' does not exist.`);
|
streams.err.writeLine(`'${path}' does not exist.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (!(target instanceof Directory)) {
|
if (!(target instanceof Directory)) {
|
||||||
output.writeLine(`'${path}' is not a directory.`);
|
streams.err.writeLine(`'${path}' is not a directory.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (target.nodeCount !== 0) {
|
if (target.nodeCount !== 0) {
|
||||||
output.writeLine(`'${path}' is not empty.`);
|
streams.err.writeLine(`'${path}' is not empty.`);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fileSystem.remove(path);
|
this.fileSystem.remove(path);
|
||||||
return 0;
|
return 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
output.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private set(input: InputArgs, output: OutputStream): number {
|
private set(input: InputArgs, streams: StreamSet): number {
|
||||||
try {
|
try {
|
||||||
if (input.args.length === 1)
|
if (input.args.length === 1)
|
||||||
this.environment.safeDelete(input.args[0]);
|
this.environment.safeDelete(input.args[0]);
|
||||||
else
|
else
|
||||||
this.environment.safeSet(input.args[0], input.args[1]);
|
this.environment.safeSet(input.args[0], input.args[1]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
output.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private touch(input: InputArgs, output: OutputStream): number {
|
private touch(input: InputArgs, streams: StreamSet): number {
|
||||||
return input.args
|
return input.args
|
||||||
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
.map(arg => Path.interpret(this.environment.get("cwd"), arg))
|
||||||
.map(path => {
|
.map(path => {
|
||||||
|
@ -628,21 +631,21 @@ export class Commands {
|
||||||
this.fileSystem.add(path, new File(), false);
|
this.fileSystem.add(path, new File(), false);
|
||||||
return 0;
|
return 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
output.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
.reduce((acc, exitCode) => exitCode === 0 ? acc : exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private whoami(_: InputArgs, output: OutputStream): number {
|
private whoami(_: InputArgs, streams: StreamSet): number {
|
||||||
const user = this.userSession.get(this.environment.get("user"));
|
const user = this.userSession.get(this.environment.get("user"));
|
||||||
if (user === undefined) {
|
if (user === undefined) {
|
||||||
output.writeLine("Cannot execute `whoami` while not logged in.");
|
streams.err.writeLine("Cannot execute `whoami` while not logged in.");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.writeLine(user.description);
|
streams.out.writeLine(user.description);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,7 +693,7 @@ class Command {
|
||||||
/**
|
/**
|
||||||
* The function to execute with the command is executed.
|
* The function to execute with the command is executed.
|
||||||
*/
|
*/
|
||||||
readonly fun: (args: InputArgs, output: OutputStream) => number;
|
readonly fun: (args: InputArgs, streams: StreamSet) => number;
|
||||||
/**
|
/**
|
||||||
* A short summary of what the command does.
|
* A short summary of what the command does.
|
||||||
*/
|
*/
|
||||||
|
@ -718,7 +721,7 @@ class Command {
|
||||||
* @param desc a longer description of what the command does and how its parameters work
|
* @param desc a longer description of what the command does and how its parameters work
|
||||||
* @param validator a function that validates input for this command
|
* @param validator a function that validates input for this command
|
||||||
*/
|
*/
|
||||||
constructor(fun: (args: InputArgs, output: OutputStream) => number, summary: string, usage: string, desc: string,
|
constructor(fun: (args: InputArgs, streams: StreamSet) => number, summary: string, usage: string, desc: string,
|
||||||
validator: InputValidator) {
|
validator: InputValidator) {
|
||||||
this.fun = fun;
|
this.fun = fun;
|
||||||
this.summary = summary;
|
this.summary = summary;
|
||||||
|
|
|
@ -169,10 +169,7 @@ export class FileSystem {
|
||||||
|
|
||||||
return new class implements OutputStream {
|
return new class implements OutputStream {
|
||||||
write(string: string): void {
|
write(string: string): void {
|
||||||
if (options === "write")
|
targetNode.contents += string;
|
||||||
targetNode.contents = string;
|
|
||||||
else
|
|
||||||
targetNode.contents += string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeLine(string: string): void {
|
writeLine(string: string): void {
|
||||||
|
|
|
@ -54,10 +54,10 @@ export class Persistence {
|
||||||
|
|
||||||
let environment: Environment;
|
let environment: Environment;
|
||||||
try {
|
try {
|
||||||
environment = new Environment(["cwd", "home", "user"], JSON.parse(environmentString));
|
environment = new Environment(["cwd", "home", "user", "status"], JSON.parse(environmentString));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Failed to set environment from cookie.");
|
console.warn("Failed to set environment from cookie.");
|
||||||
environment = new Environment(["cwd", "home", "user"]);
|
environment = new Environment(["cwd", "home", "user", "status"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check user in environment
|
// Check user in environment
|
||||||
|
@ -75,6 +75,9 @@ export class Persistence {
|
||||||
if (!environment.has("cwd"))
|
if (!environment.has("cwd"))
|
||||||
environment.set("cwd", environment.get("home"));
|
environment.set("cwd", environment.get("home"));
|
||||||
|
|
||||||
|
// Set status
|
||||||
|
environment.set("status", "0");
|
||||||
|
|
||||||
return environment;
|
return environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import {Commands} from "./Commands";
|
import {Commands} from "./Commands";
|
||||||
import {Environment} from "./Environment";
|
import {Environment} from "./Environment";
|
||||||
import {Directory, File, FileSystem, Path} from "./FileSystem";
|
import {Directory, FileSystem, Path} from "./FileSystem";
|
||||||
import {InputParser} from "./InputParser";
|
import {InputParser} from "./InputParser";
|
||||||
import {Persistence} from "./Persistence";
|
import {Persistence} from "./Persistence";
|
||||||
import {asciiHeaderHtml, IllegalStateError, stripHtmlTags} from "./Shared";
|
import {asciiHeaderHtml, stripHtmlTags} from "./Shared";
|
||||||
import {EscapeCharacters, InputHistory} from "./Terminal";
|
import {EscapeCharacters, InputHistory} from "./Terminal";
|
||||||
import {UserList} from "./UserList";
|
import {UserList} from "./UserList";
|
||||||
import {OutputStream} from "./Stream";
|
import {StreamSet} from "./Stream";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,51 +115,52 @@ export class Shell {
|
||||||
* Processes a user's input and returns the associated exit code.
|
* Processes a user's input and returns the associated exit code.
|
||||||
*
|
*
|
||||||
* @param inputString the input to process
|
* @param inputString the input to process
|
||||||
* @param outputStream the standard output stream
|
* @param streams the standard streams
|
||||||
*/
|
*/
|
||||||
execute(inputString: string, outputStream: OutputStream): number {
|
execute(inputString: string, streams: StreamSet): number {
|
||||||
if (this.environment.get("user") === "") {
|
if (this.environment.get("user") === "") {
|
||||||
if (this.attemptUser === undefined) {
|
if (this.attemptUser === undefined) {
|
||||||
this.attemptUser = inputString.trim() ?? undefined; // Set to undefined if empty string
|
streams.out.write(EscapeCharacters.Escape + EscapeCharacters.HideInput);
|
||||||
|
|
||||||
this.saveState();
|
this.attemptUser = inputString.trim() ?? undefined; // Leave at undefined if empty string
|
||||||
outputStream.write(EscapeCharacters.Escape + EscapeCharacters.HideInput);
|
|
||||||
} else {
|
} else {
|
||||||
const attemptUser = this.userList.get(this.attemptUser);
|
streams.out.write(EscapeCharacters.Escape + EscapeCharacters.ShowInput);
|
||||||
|
|
||||||
let resultString: string;
|
const attemptUser = this.userList.get(this.attemptUser);
|
||||||
if (attemptUser !== undefined && attemptUser.password === inputString) {
|
if (attemptUser !== undefined && attemptUser.password === inputString) {
|
||||||
this.environment.set("user", attemptUser.name);
|
this.environment.set("user", attemptUser.name);
|
||||||
this.environment.set("home", attemptUser.home);
|
this.environment.set("home", attemptUser.home);
|
||||||
this.environment.set("cwd", attemptUser.home);
|
this.environment.set("cwd", attemptUser.home);
|
||||||
resultString = this.generateHeader();
|
this.environment.set("status", "0");
|
||||||
|
streams.out.writeLine(this.generateHeader());
|
||||||
} else {
|
} else {
|
||||||
resultString = "Access denied\n";
|
streams.out.writeLine("Access denied");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.attemptUser = undefined;
|
this.attemptUser = undefined;
|
||||||
this.saveState();
|
|
||||||
outputStream.write(EscapeCharacters.Escape + EscapeCharacters.ShowInput + resultString);
|
|
||||||
}
|
}
|
||||||
|
this.saveState();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inputHistory.addEntry(inputString.trim());
|
this.inputHistory.addEntry(inputString.trim());
|
||||||
|
|
||||||
const parser = InputParser.create(this.environment, this.fileSystem);
|
|
||||||
let input;
|
let input;
|
||||||
try {
|
try {
|
||||||
input = parser.parse(stripHtmlTags(inputString));
|
input = InputParser.create(this.environment, this.fileSystem).parse(stripHtmlTags(inputString));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
outputStream.writeLine(error.message);
|
streams.err.writeLine(error.message);
|
||||||
|
this.environment.set("status", "-1");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.redirectTarget[0] !== "default") {
|
if (input.redirectTarget[0] !== "default") {
|
||||||
const target = Path.interpret(this.environment.get("cwd"), input.redirectTarget[1]);
|
const target = Path.interpret(this.environment.get("cwd"), input.redirectTarget[1]);
|
||||||
outputStream = this.fileSystem.open(target, input.redirectTarget[0]);
|
streams.out = this.fileSystem.open(target, input.redirectTarget[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = this.commands.execute(input, outputStream);
|
const output = this.commands.execute(input, streams);
|
||||||
|
this.environment.set("status", "" + output);
|
||||||
|
|
||||||
if (this.environment.get("user") === "") {
|
if (this.environment.get("user") === "") {
|
||||||
this.inputHistory.clear();
|
this.inputHistory.clear();
|
||||||
|
|
|
@ -41,15 +41,15 @@ export class StreamSet {
|
||||||
/**
|
/**
|
||||||
* The input stream.
|
* The input stream.
|
||||||
*/
|
*/
|
||||||
readonly ins: InputStream;
|
ins: InputStream;
|
||||||
/**
|
/**
|
||||||
* The output stream.
|
* The output stream.
|
||||||
*/
|
*/
|
||||||
readonly out: OutputStream;
|
out: OutputStream;
|
||||||
/**
|
/**
|
||||||
* The error output stream.
|
* The error output stream.
|
||||||
*/
|
*/
|
||||||
readonly err: OutputStream;
|
err: OutputStream;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {moveCaretToEndOf, parseCssPixels} from "./Shared";
|
import {IllegalStateError, moveCaretToEndOf, parseCssPixels} from "./Shared";
|
||||||
import {Shell} from "./Shell";
|
import {Shell} from "./Shell";
|
||||||
import {OutputStream} from "./Stream";
|
import {InputStream, OutputStream, StreamSet} from "./Stream";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,13 +200,35 @@ export class Terminal {
|
||||||
this.inputText = "";
|
this.inputText = "";
|
||||||
this.outputText += `${this.prefixText}${this.isInputHidden ? "" : input.trim()}\n`;
|
this.outputText += `${this.prefixText}${this.isInputHidden ? "" : input.trim()}\n`;
|
||||||
|
|
||||||
this.shell.execute(input, this.getOutputStream());
|
this.shell.execute(input, this.getStreams());
|
||||||
|
|
||||||
this.prefixText = this.shell.generatePrefix();
|
this.prefixText = this.shell.generatePrefix();
|
||||||
this.scroll = 0;
|
this.scroll = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the terminal's default set of streams.
|
||||||
|
*/
|
||||||
|
private getStreams(): StreamSet {
|
||||||
|
return new StreamSet(this.getInputStream(), this.getOutputStream(), this.getOutputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an input stream that reads from the terminal's input.
|
||||||
|
*/
|
||||||
|
private getInputStream(): InputStream {
|
||||||
|
return new class implements InputStream {
|
||||||
|
read(count: number | undefined): string {
|
||||||
|
throw new IllegalStateError("Default input stream has not been implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
readLine(): string {
|
||||||
|
throw new IllegalStateError("Default input stream has not been implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an output stream that writes to the terminal's output.
|
* Returns an output stream that writes to the terminal's output.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue