forked from tools/josh
1
0
Fork 0

Fix #50 and add error streams

This commit is contained in:
Florine W. Dekker 2019-11-09 23:52:46 +01:00
parent bdbf42e074
commit c7f2d6b0f6
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
6 changed files with 125 additions and 99 deletions

View File

@ -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;

View File

@ -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 {

View File

@ -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;
} }

View File

@ -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();

View File

@ -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;
/** /**

View File

@ -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.
*/ */