parent
cd17170657
commit
86b07555da
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fwdekker.com",
|
||||
"version": "0.39.2",
|
||||
"version": "0.39.3",
|
||||
"description": "The source code of [my personal website](https://fwdekker.com/).",
|
||||
"author": "Felix W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
|
@ -52,58 +52,71 @@ export class Commands {
|
|||
if (input.command === "")
|
||||
return ExitCode.OK;
|
||||
|
||||
const command = this.resolve(input.command);
|
||||
if (command === undefined) {
|
||||
let target = this.resolve(input.command);
|
||||
if (target === undefined) {
|
||||
streams.err.writeLine(`Unknown command '${input.command}'.`);
|
||||
return ExitCode.COMMAND_NOT_FOUND;
|
||||
}
|
||||
if (command instanceof Error) {
|
||||
streams.err.writeLine(`Could not parse command '${input.command}': ${command}.`);
|
||||
if (target instanceof Error) {
|
||||
streams.err.writeLine(`Could not parse command '${input.command}': ${target}.`);
|
||||
return ExitCode.COMMAND_NOT_FOUND;
|
||||
}
|
||||
if (command instanceof DocOnlyCommand) {
|
||||
if (target instanceof DocOnlyCommand) {
|
||||
streams.err.writeLine(`Could not execute doc-only command. Try 'help ${input.command}' instead.`);
|
||||
return ExitCode.COMMAND_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (command instanceof Script) {
|
||||
if (target instanceof Directory) {
|
||||
return this.execute(
|
||||
new InputArgs("/bin/cd", input.options, [input.command].concat(input.args), input.redirectTargets),
|
||||
streams
|
||||
);
|
||||
} else if (target instanceof Script) {
|
||||
const parser = InputParser.create(this.environment, this.fileSystem);
|
||||
return command.lines
|
||||
return target.lines
|
||||
.map(line => parser.parseCommands(line))
|
||||
.reduce((acc, input) => acc.concat(input))
|
||||
.reduce((acc, code) => acc !== 0 ? acc : this.execute(code, streams), 0);
|
||||
}
|
||||
} else {
|
||||
const validation = target.validator.validate(input);
|
||||
if (!validation[0]) {
|
||||
streams.err.writeLine(this.createUsageErrorOutput(input.command, target, validation[1]));
|
||||
return ExitCode.USAGE;
|
||||
}
|
||||
|
||||
const validation = command.validator.validate(input);
|
||||
if (!validation[0]) {
|
||||
streams.err.writeLine(this.createUsageErrorOutput(input.command, command, validation[1]));
|
||||
return ExitCode.USAGE;
|
||||
return target.fun.bind(this)(input, streams);
|
||||
}
|
||||
|
||||
return command.fun.bind(this)(input, streams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the `Command` with the given name and returns it.
|
||||
* Resolves a target from its name by looking through the file system.
|
||||
*
|
||||
* @param commandName the name of or path to the command to find
|
||||
* @return the command addressed by the given name or path, an 'Error' if the command could be found but could not
|
||||
* be parsed, or `undefined` if the command could not be found
|
||||
* If the target is a command, the command is parsed (but not yet invoked) and returned.
|
||||
*
|
||||
* Targets are resolved as follows. If the target name contains a slash, then interpret that path literally.
|
||||
* Otherwise, look for the target in /bin. If no such target exists or it is not a file, then interpret it as a
|
||||
* relative path.
|
||||
*
|
||||
* @param targetName the name of the target to be resolved
|
||||
* @return the `Command`, `Script`, or `Directory` addressed by the target, an `Error` if the target could be found
|
||||
* but could not be parsed, or `undefined` if the target could not be found
|
||||
*/
|
||||
resolve(commandName: string): Command | Script | Error | undefined {
|
||||
resolve(targetName: string): Command | Script | Directory | Error | undefined {
|
||||
const cwd = this.environment.get("cwd");
|
||||
|
||||
let script: Node | undefined;
|
||||
if (commandName.includes("/")) {
|
||||
script = this.fileSystem.get(Path.interpret(cwd, commandName));
|
||||
let target: Node | undefined;
|
||||
if (targetName.includes("/")) {
|
||||
target = this.fileSystem.get(Path.interpret(cwd, targetName));
|
||||
} else {
|
||||
script = this.fileSystem.get(Path.interpret(cwd, "/bin", commandName));
|
||||
}
|
||||
if (!(script instanceof File)) {
|
||||
return undefined;
|
||||
target = this.fileSystem.get(Path.interpret(cwd, "/bin", targetName));
|
||||
if (!(target instanceof File))
|
||||
target = this.fileSystem.get(Path.interpret(cwd, targetName));
|
||||
}
|
||||
|
||||
const code = script.open("read").read();
|
||||
if (!(target instanceof File))
|
||||
return target instanceof Directory ? target : undefined;
|
||||
|
||||
const code = target.open("read").read();
|
||||
try {
|
||||
if (code.startsWith("#!/bin/josh\n")) {
|
||||
return new Script(code.split("\n").slice(1));
|
||||
|
@ -111,7 +124,7 @@ export class Commands {
|
|||
return this.interpretBinary(code, this.environment, this.userList, this.fileSystem);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to interpret script '${commandName}'.`, code, e);
|
||||
console.error(`Failed to interpret script '${targetName}'.`, code, e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -610,7 +610,7 @@ export class Directory extends Node {
|
|||
* @param path the path to this node
|
||||
*/
|
||||
nameString(name: string, path: Path): string {
|
||||
return `<a href="#" class="dirLink" onclick="execute('cd ${path.toString(true)}; and ls -l')">${name}</a>`;
|
||||
return `<a href="#" class="dirLink" onclick="execute('${path.toString(true)}; and ls -l')">${name}</a>`;
|
||||
}
|
||||
|
||||
visit(path: string,
|
||||
|
|
|
@ -83,11 +83,64 @@ describe("commands", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("directories", () => {
|
||||
beforeEach(() => loadCommand("cd"));
|
||||
|
||||
|
||||
it("navigates to an absolute directory", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(execute("/dir")).to.equal(ExitCode.OK);
|
||||
expect(environment.get("cwd")).to.equal("/dir");
|
||||
});
|
||||
|
||||
it("navigates to a relative directory without a slash", () => {
|
||||
fileSystem.add(new Path("/dir1/dir2"), new Directory(), true);
|
||||
environment.set("cwd", "/dir1");
|
||||
|
||||
expect(execute("dir2")).to.equal(ExitCode.OK);
|
||||
expect(environment.get("cwd")).to.equal("/dir1/dir2");
|
||||
});
|
||||
|
||||
it("navigates to a relative directory with a slash", () => {
|
||||
fileSystem.add(new Path("/dir1/dir2"), new Directory(), true);
|
||||
environment.set("cwd", "dir1");
|
||||
|
||||
expect(execute("dir2/")).to.equal(ExitCode.OK);
|
||||
expect(environment.get("cwd")).to.equal("/dir1/dir2");
|
||||
});
|
||||
|
||||
it("navigates to a nested relative directory", () => {
|
||||
fileSystem.add(new Path("/dir1/dir2/dir3"), new Directory(), true);
|
||||
environment.set("cwd", "dir1");
|
||||
|
||||
expect(execute("dir2/dir3")).to.equal(ExitCode.OK);
|
||||
expect(environment.get("cwd")).to.equal("/dir1/dir2/dir3");
|
||||
});
|
||||
|
||||
describe("/bin", () => {
|
||||
it("cannot navigate to a directory in /bin by only typing its name if not currently in /bin", () => {
|
||||
fileSystem.add(new Path("/bin/dir"), new Directory(), true);
|
||||
|
||||
expect(execute("dir")).to.equal(ExitCode.COMMAND_NOT_FOUND);
|
||||
expect(environment.get("cwd")).to.equal("/");
|
||||
});
|
||||
|
||||
it("navigates to a directory in /bin by only typing its name if currently in /bin", () => {
|
||||
fileSystem.add(new Path("/bin/dir"), new Directory(), true);
|
||||
environment.set("cwd", "/bin");
|
||||
|
||||
expect(execute("dir")).to.equal(ExitCode.OK);
|
||||
expect(environment.get("cwd")).to.equal("/bin/dir");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("scripts", () => {
|
||||
it("executes an empty script", () => {
|
||||
fileSystem.add(new Path("/script"), new File("#!/bin/josh\n"), false);
|
||||
|
||||
expect(execute("/script")).to.equal(0);
|
||||
expect(execute("/script")).to.equal(ExitCode.OK);
|
||||
expect(readOut()).to.equal("");
|
||||
});
|
||||
|
||||
|
@ -96,7 +149,7 @@ describe("commands", () => {
|
|||
|
||||
fileSystem.add(new Path("/script"), new File("#!/bin/josh\necho though\necho only"), false);
|
||||
|
||||
expect(execute("/script")).to.equal(0);
|
||||
expect(execute("/script")).to.equal(ExitCode.OK);
|
||||
expect(readOut()).to.equal("though\nonly\n");
|
||||
});
|
||||
|
||||
|
@ -105,7 +158,7 @@ describe("commands", () => {
|
|||
|
||||
fileSystem.add(new Path("/script"), new File("#!/bin/josh\n echo rescue \n echo flour "), false);
|
||||
|
||||
expect(execute("/script")).to.equal(0);
|
||||
expect(execute("/script")).to.equal(ExitCode.OK);
|
||||
expect(readOut()).to.equal("rescue\nflour\n");
|
||||
});
|
||||
});
|
||||
|
@ -576,7 +629,7 @@ describe("commands", () => {
|
|||
|
||||
|
||||
it("does nothing if the previous command exited successfully", () => {
|
||||
environment.set("status", "0");
|
||||
environment.set("status", "" + ExitCode.OK);
|
||||
|
||||
expect(execute("or echo 'message'")).to.equal(ExitCode.OK);
|
||||
expect(readOut()).to.equal("");
|
||||
|
|
Loading…
Reference in New Issue