forked from tools/josh
parent
ee7b7b3663
commit
6c10b714cd
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fwdekker.com",
|
||||
"version": "1.13.0",
|
||||
"version": "1.14.0",
|
||||
"description": "The source code of [my personal website](https://fwdekker.com/).",
|
||||
"author": "Felix W. Dekker",
|
||||
"repository": {
|
||||
|
|
|
@ -62,6 +62,9 @@ export class FileSystem {
|
|||
* or if there already exists a node at the given location
|
||||
*/
|
||||
add(target: Path, node: Node, createParents: boolean): void {
|
||||
if (target.isDirectory && !(node instanceof Directory))
|
||||
throw new IllegalArgumentError(`Cannot add non-directory at '${target}/'.`);
|
||||
|
||||
if (!this.has(target.parent)) {
|
||||
if (createParents)
|
||||
this.add(target.parent, new Directory(), true);
|
||||
|
@ -72,10 +75,10 @@ export class FileSystem {
|
|||
const parent = this.get(target.parent);
|
||||
if (!(parent instanceof Directory))
|
||||
throw new IllegalArgumentError(`'${target.parent}' is not a directory.`);
|
||||
if (parent.hasNode(target.fileName))
|
||||
if (parent.has(target.fileName))
|
||||
throw new IllegalArgumentError(`A file or directory already exists at '${target}'.`);
|
||||
|
||||
parent.addNode(target.fileName, node);
|
||||
parent.add(target.fileName, node);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,10 +113,14 @@ export class FileSystem {
|
|||
return this.root;
|
||||
|
||||
const parent = this.get(target.parent);
|
||||
if (!(parent instanceof Directory) || !parent.hasNode(target.fileName))
|
||||
if (!(parent instanceof Directory))
|
||||
return undefined;
|
||||
|
||||
return parent.getNode(target.fileName);
|
||||
const node = parent.get(target.fileName);
|
||||
if (target.isDirectory && !(node instanceof Directory))
|
||||
return undefined;
|
||||
|
||||
return parent.get(target.fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,14 +129,7 @@ export class FileSystem {
|
|||
* @param target the path to check for node presence
|
||||
*/
|
||||
has(target: Path): boolean {
|
||||
if (target.toString() === "/")
|
||||
return true;
|
||||
|
||||
const parent = this.get(target.parent);
|
||||
if (!(parent instanceof Directory))
|
||||
return false;
|
||||
|
||||
return parent.hasNode(target.fileName);
|
||||
return this.get(target) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,14 +161,18 @@ export class FileSystem {
|
|||
*/
|
||||
open(target: Path, mode: FileMode): FileStream {
|
||||
if (!this.has(target.parent))
|
||||
throw new IllegalArgumentError(`open: Directory '${target.parent}' does not exist.`);
|
||||
throw new IllegalArgumentError(`Directory '${target.parent}' does not exist.`);
|
||||
|
||||
if (!this.has(target))
|
||||
this.add(target, new File(), false);
|
||||
if (!this.has(target)) {
|
||||
if (mode === "append" || mode === "write")
|
||||
this.add(target, new File(), false);
|
||||
else
|
||||
throw new IllegalArgumentError(`File '${target}' does not exist.`);
|
||||
}
|
||||
|
||||
const targetNode = this.get(target);
|
||||
if (!(targetNode instanceof File))
|
||||
throw new IllegalArgumentError(`open: Cannot open stream to directory '${target}'.`);
|
||||
throw new IllegalArgumentError(`Cannot open directory '${target}'.`);
|
||||
|
||||
return targetNode.open(mode);
|
||||
}
|
||||
|
@ -178,14 +182,18 @@ export class FileSystem {
|
|||
*
|
||||
* If the node in question does not exist, the function will return successfully.
|
||||
*
|
||||
* @param targetPath the path to the node to be removed
|
||||
* @param target the path to the node to be removed
|
||||
*/
|
||||
remove(targetPath: Path): void {
|
||||
const parent = this.get(targetPath.parent);
|
||||
remove(target: Path): void {
|
||||
const parent = this.get(target.parent);
|
||||
if (!(parent instanceof Directory))
|
||||
return;
|
||||
|
||||
parent.removeNode(targetPath.fileName);
|
||||
const node = this.get(target);
|
||||
if (target.isDirectory && !(node instanceof Directory))
|
||||
return;
|
||||
|
||||
parent.remove(target.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,12 +214,16 @@ export class Path {
|
|||
* The name of the node described by this path.
|
||||
*/
|
||||
readonly fileName: string;
|
||||
/**
|
||||
* `true` if and only if the path necessarily points to a directory.
|
||||
*/
|
||||
readonly isDirectory: boolean;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new path.
|
||||
*
|
||||
* @param paths a string that describes the path
|
||||
* @param paths a set of strings that describe the path
|
||||
*/
|
||||
constructor(...paths: string[]) {
|
||||
const path = `/${paths.join("/")}/`;
|
||||
|
@ -240,6 +252,7 @@ export class Path {
|
|||
this.path = "/" + parts.join("/");
|
||||
this._parent = parts.slice(0, -1).join("/");
|
||||
this.fileName = parts.slice(-1).join("");
|
||||
this.isDirectory = paths[paths.length - 1].endsWith("/");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -447,15 +460,11 @@ export class Directory extends Node {
|
|||
|
||||
|
||||
/**
|
||||
* Returns the node with the given name.
|
||||
* Returns the node with the given name, or `undefined` if there is no such node.
|
||||
*
|
||||
* @param name the name of the node to return
|
||||
* @throws when there is no node with the given name in this directory
|
||||
*/
|
||||
getNode(name: string): Node {
|
||||
if (!this.hasNode(name))
|
||||
throw new IllegalArgumentError(`Directory does not have a node with name '${name}'.`);
|
||||
|
||||
get(name: string): Node | undefined {
|
||||
return this._nodes[name];
|
||||
}
|
||||
|
||||
|
@ -464,7 +473,7 @@ export class Directory extends Node {
|
|||
*
|
||||
* @param name the name to check
|
||||
*/
|
||||
hasNode(name: string): boolean {
|
||||
has(name: string): boolean {
|
||||
return this._nodes.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
|
@ -474,7 +483,7 @@ export class Directory extends Node {
|
|||
* @param name the name of the node in this directory
|
||||
* @param node the node to add to this directory
|
||||
*/
|
||||
addNode(name: string, node: Node): void {
|
||||
add(name: string, node: Node): void {
|
||||
if (new Path(`/${name}`).toString() === "/" || name.indexOf("/") >= 0)
|
||||
throw new IllegalArgumentError(`Cannot add node with name '${name}'.`);
|
||||
|
||||
|
@ -487,9 +496,9 @@ export class Directory extends Node {
|
|||
* @param name the name of the node to remove
|
||||
* @throws if the given node is not contained in this directory
|
||||
*/
|
||||
removeNode(name: string): void {
|
||||
remove(name: string): void {
|
||||
if (name === "" || name === ".") {
|
||||
Object.keys(this._nodes).forEach(node => this.removeNode(node));
|
||||
Object.keys(this._nodes).forEach(node => this.remove(node));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,103 +20,103 @@ describe("directory", () => {
|
|||
});
|
||||
|
||||
it("has the given nodes", () => {
|
||||
expect(new Directory({node: new File()}).hasNode("node")).to.be.true;
|
||||
expect(new Directory({node: new File()}).has("node")).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("getNode", () => {
|
||||
describe("get", () => {
|
||||
it("throws an exception if he node does not exist", () => {
|
||||
expect(() => directory.getNode("error")).to.throw();
|
||||
expect(directory.get("error")).to.be.undefined;
|
||||
});
|
||||
|
||||
it("does not contain itself", () => {
|
||||
expect(() => directory.getNode(".")).to.throw();
|
||||
expect(directory.get(".")).to.be.undefined;
|
||||
});
|
||||
|
||||
it("returns the desired node", () => {
|
||||
const file = new File();
|
||||
directory.addNode("file", file);
|
||||
directory.add("file", file);
|
||||
|
||||
expect(directory.getNode("file")).to.equal(file);
|
||||
expect(directory.get("file")).to.equal(file);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasNode", () => {
|
||||
describe("has", () => {
|
||||
it("returns false if the node does not exist", () => {
|
||||
expect(directory.hasNode("error")).to.be.false;
|
||||
expect(directory.has("error")).to.be.false;
|
||||
});
|
||||
|
||||
it("returns true if the node exists", () => {
|
||||
directory.addNode("file", new File());
|
||||
directory.add("file", new File());
|
||||
|
||||
expect(directory.hasNode("file")).to.be.true;
|
||||
expect(directory.has("file")).to.be.true;
|
||||
});
|
||||
|
||||
it("returns false if the node is the reflexive node", () => {
|
||||
expect(directory.hasNode(".")).to.be.false;
|
||||
expect(directory.has(".")).to.be.false;
|
||||
});
|
||||
|
||||
it("returns false if the node is the parent node", () => {
|
||||
expect(directory.hasNode("..")).to.be.false;
|
||||
expect(directory.has("..")).to.be.false;
|
||||
});
|
||||
|
||||
it("returns false if the node refers to itself", () => {
|
||||
expect(directory.hasNode("")).to.be.false;
|
||||
expect(directory.has("")).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("addNode", () => {
|
||||
describe("add", () => {
|
||||
it("adds the given node", () => {
|
||||
directory.addNode("file", new File());
|
||||
directory.add("file", new File());
|
||||
|
||||
expect(directory.hasNode("file")).to.be.true;
|
||||
expect(directory.has("file")).to.be.true;
|
||||
});
|
||||
|
||||
it("overwrites an existing node", () => {
|
||||
directory.addNode("file", new File());
|
||||
directory.addNode("file", new Directory());
|
||||
directory.add("file", new File());
|
||||
directory.add("file", new Directory());
|
||||
|
||||
expect(directory.getNode("file")).to.be.instanceOf(Directory);
|
||||
expect(directory.get("file")).to.be.instanceOf(Directory);
|
||||
});
|
||||
|
||||
it("refuses to add a node at the reflexive path", () => {
|
||||
expect(() => directory.addNode(".", new File())).to.throw();
|
||||
expect(() => directory.add(".", new File())).to.throw();
|
||||
});
|
||||
|
||||
it("refuses to add a node at the parent path", () => {
|
||||
expect(() => directory.addNode("..", new File())).to.throw();
|
||||
expect(() => directory.add("..", new File())).to.throw();
|
||||
});
|
||||
|
||||
it("refuses to add a node that refers to the directory", () => {
|
||||
expect(() => directory.addNode("", new File())).to.throw();
|
||||
expect(() => directory.add("", new File())).to.throw();
|
||||
});
|
||||
|
||||
it("refuses to add a node with a name containing a slash", () => {
|
||||
expect(() => directory.addNode("a/b", new File())).to.throw();
|
||||
expect(() => directory.add("a/b", new File())).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeNode", () => {
|
||||
describe("remove", () => {
|
||||
it("removes the desired node", () => {
|
||||
directory.addNode("file", new File());
|
||||
directory.add("file", new File());
|
||||
|
||||
directory.removeNode("file");
|
||||
directory.remove("file");
|
||||
|
||||
expect(directory.nodeCount).to.equal(0);
|
||||
});
|
||||
|
||||
it("empties the directory if the name is empty", () => {
|
||||
directory.addNode("file", new File());
|
||||
directory.add("file", new File());
|
||||
|
||||
directory.removeNode("");
|
||||
directory.remove("");
|
||||
|
||||
expect(directory.nodeCount).to.equal(0);
|
||||
});
|
||||
|
||||
it("empties the directory if the name is reflexive", () => {
|
||||
directory.addNode("file", new File());
|
||||
directory.add("file", new File());
|
||||
|
||||
directory.removeNode(".");
|
||||
directory.remove(".");
|
||||
|
||||
expect(directory.nodeCount).to.equal(0);
|
||||
});
|
||||
|
@ -130,13 +130,13 @@ describe("directory", () => {
|
|||
});
|
||||
const copy = directory.copy();
|
||||
|
||||
(<File>directory.getNode("file")).open("write").write("changed");
|
||||
expect((<File>copy.getNode("file")).open("read").read()).to.equal("contents");
|
||||
(<File>directory.get("file")).open("write").write("changed");
|
||||
expect((<File>copy.get("file")).open("read").read()).to.equal("contents");
|
||||
|
||||
(<Directory>directory.getNode("dir")).addNode("file2", new File());
|
||||
expect((<Directory>copy.getNode("dir")).nodeCount).to.equal(0);
|
||||
(<Directory>directory.get("dir")).add("file2", new File());
|
||||
expect((<Directory>copy.get("dir")).nodeCount).to.equal(0);
|
||||
|
||||
directory.removeNode("file");
|
||||
directory.remove("file");
|
||||
expect(copy.nodeCount).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -62,6 +62,10 @@ describe("file system", () => {
|
|||
|
||||
expect(() => fileSystem.add(new Path("/file1"), new File(), false)).to.throw();
|
||||
});
|
||||
|
||||
it("fails if a file is added at a directory path", () => {
|
||||
expect(() => fileSystem.add(new Path("/dir/"), new File(), false)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("copy", () => {
|
||||
|
@ -132,6 +136,42 @@ describe("file system", () => {
|
|||
expect(fileSystem.get(new Path("/"))).to.equal(fileSystem.root);
|
||||
});
|
||||
|
||||
it("returns the node at the given path", () => {
|
||||
const file = new File();
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
fileSystem.add(new Path("/dir/file"), file, false);
|
||||
|
||||
expect(fileSystem.get(new Path("/dir/file"))).to.equal(file);
|
||||
});
|
||||
|
||||
it("returns the file at the given file path", () => {
|
||||
const file = new File();
|
||||
fileSystem.add(new Path("/file"), file, false);
|
||||
|
||||
expect(fileSystem.get(new Path("/file"))).to.equal(file);
|
||||
});
|
||||
|
||||
it("returns the directory at the given file path", () => {
|
||||
const directory = new Directory();
|
||||
fileSystem.add(new Path("/dir"), directory, false);
|
||||
|
||||
expect(fileSystem.get(new Path("/dir"))).to.equal(directory);
|
||||
});
|
||||
|
||||
it("returns undefined for a directory path even though a file exists with the same name", () => {
|
||||
const file = new File();
|
||||
fileSystem.add(new Path("/file"), file, false);
|
||||
|
||||
expect(fileSystem.get(new Path("/file/"))).to.be.undefined;
|
||||
});
|
||||
|
||||
it("returns the directory at the given directory path", () => {
|
||||
const directory = new Directory();
|
||||
fileSystem.add(new Path("/dir"), directory, false);
|
||||
|
||||
expect(fileSystem.get(new Path("/dir/"))).to.equal(directory);
|
||||
});
|
||||
|
||||
it("returns undefined if the parent is not a directory", () => {
|
||||
fileSystem.add(new Path("/file1"), new File(), false);
|
||||
|
||||
|
@ -143,14 +183,6 @@ describe("file system", () => {
|
|||
|
||||
expect(fileSystem.get(new Path("/dir/file"))).to.be.undefined;
|
||||
});
|
||||
|
||||
it("returns the node at the given path", () => {
|
||||
const file = new File();
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
fileSystem.add(new Path("/dir/file"), file, false);
|
||||
|
||||
expect(fileSystem.get(new Path("/dir/file"))).to.equal(file);
|
||||
});
|
||||
});
|
||||
|
||||
describe("has", () => {
|
||||
|
@ -158,6 +190,30 @@ describe("file system", () => {
|
|||
expect(fileSystem.has(new Path("/"))).to.be.true;
|
||||
});
|
||||
|
||||
it("returns true if a file exists at the given file path", () => {
|
||||
fileSystem.add(new Path("/dir"), new File(), false);
|
||||
|
||||
expect(fileSystem.has(new Path("/dir"))).to.be.true;
|
||||
});
|
||||
|
||||
it("returns true if a directory exists at the given file path", () =>{
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(fileSystem.has(new Path("/dir"))).to.be.true;
|
||||
});
|
||||
|
||||
it("returns false if a file with the same name exists but a directory path is given", () => {
|
||||
fileSystem.add(new Path("/dir"), new File(), false);
|
||||
|
||||
expect(fileSystem.has(new Path("/dir/"))).to.be.false;
|
||||
});
|
||||
|
||||
it("returns true if a directory exists at the given directory path", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(fileSystem.has(new Path("/dir/"))).to.be.true;
|
||||
});
|
||||
|
||||
it("returns false if the node does not exist", () => {
|
||||
expect(fileSystem.has(new Path("/error"))).to.be.false;
|
||||
});
|
||||
|
@ -242,8 +298,12 @@ describe("file system", () => {
|
|||
expect(() => fileSystem.open(new Path("/dir"), "read")).to.throw();
|
||||
});
|
||||
|
||||
it("creates the target if it does not exist yet", () => {
|
||||
fileSystem.open(new Path("/file"), "read");
|
||||
it("throws an error in read mode if a directory path is given", () => {
|
||||
expect(() => fileSystem.open(new Path("/dir/"), "read")).to.throw();
|
||||
});
|
||||
|
||||
it("creates the target in write mode if it does not exist yet", () => {
|
||||
fileSystem.open(new Path("/file"), "write");
|
||||
|
||||
expect(fileSystem.has(new Path("/file"))).to.be.true;
|
||||
});
|
||||
|
@ -258,6 +318,7 @@ describe("file system", () => {
|
|||
describe("remove", () => {
|
||||
it("removes a file", () => {
|
||||
fileSystem.add(new Path("/file"), new File(), false);
|
||||
|
||||
fileSystem.remove(new Path("/file"));
|
||||
|
||||
expect(fileSystem.has(new Path("/file"))).to.be.false;
|
||||
|
@ -282,5 +343,13 @@ describe("file system", () => {
|
|||
|
||||
expect(fileSystem.has(new Path("/dir"))).to.be.false;
|
||||
});
|
||||
|
||||
it("does not remove a file at a directory path", () => {
|
||||
fileSystem.add(new Path("/file"), new File(), false);
|
||||
|
||||
fileSystem.remove(new Path("/file/"));
|
||||
|
||||
expect(fileSystem.has(new Path("/file"))).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,6 +48,10 @@ describe("paths", () => {
|
|||
});
|
||||
|
||||
describe("parts", () => {
|
||||
it("throws an error if no parts are given", () => {
|
||||
expect(() => new Path()).to.throw();
|
||||
});
|
||||
|
||||
it("concatenates multiple parts", () => {
|
||||
expect(new Path("/dir1", "/dir2", "/file").toString()).to.equal("/dir1/dir2/file");
|
||||
});
|
||||
|
@ -94,6 +98,24 @@ describe("paths", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("directory", () => {
|
||||
it("is a directory path if the last part ends with a slash", () => {
|
||||
expect(new Path("/dir/").isDirectory).to.be.true;
|
||||
});
|
||||
|
||||
it("is a directory if the last part ends with a slash", () => {
|
||||
expect(new Path("/dir1", "dir2/").isDirectory).to.be.true;
|
||||
});
|
||||
|
||||
it("is not a directory path if the last part does not end with a slash", () => {
|
||||
expect(new Path("/dir").isDirectory).to.be.false;
|
||||
});
|
||||
|
||||
it("is not a directory if only the first part ends with a slash", () => {
|
||||
expect(new Path("/dir/", "file").isDirectory).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("parent", () => {
|
||||
it("returns root as the parent of root", () => {
|
||||
expect(new Path("/").parent.toString()).to.equal(new Path("/").toString());
|
||||
|
|
Loading…
Reference in New Issue