2019-10-20 23:55:04 +02:00
|
|
|
import {emptyFunction} from "./shared.js";
|
|
|
|
|
|
|
|
|
|
|
|
export class FileSystem {
|
2019-10-21 03:13:14 +02:00
|
|
|
private _pwd: string;
|
2019-10-21 02:25:42 +02:00
|
|
|
private root: Directory;
|
2019-10-20 23:55:04 +02:00
|
|
|
private files: Directory;
|
|
|
|
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
constructor() {
|
2019-10-21 03:13:14 +02:00
|
|
|
this._pwd = "/";
|
2019-10-21 02:25:42 +02:00
|
|
|
this.root = new Directory({
|
2018-11-30 15:23:03 +01:00
|
|
|
personal: new Directory({
|
2018-11-30 17:11:56 +01:00
|
|
|
steam: new UrlFile("https://steamcommunity.com/id/Waflix"),
|
2019-07-08 19:14:12 +02:00
|
|
|
nukapedia: new UrlFile("http://fallout.wikia.com/wiki/User:FDekker"),
|
|
|
|
blog: new UrlFile("https://blog.fwdekker.com/"),
|
2018-11-30 15:23:03 +01:00
|
|
|
}),
|
|
|
|
projects: new Directory({
|
2018-11-30 17:11:56 +01:00
|
|
|
randomness: new UrlFile("https://github.com/FWDekker/intellij-randomness"),
|
2019-07-08 19:14:12 +02:00
|
|
|
schaapi: new UrlFile("http://cafejojo.org/schaapi"),
|
|
|
|
gitea: new UrlFile("https://git.fwdekker.com/explore/"),
|
2019-09-03 14:53:48 +02:00
|
|
|
github: new UrlFile("https://github.com/FWDekker/"),
|
2018-11-30 15:23:03 +01:00
|
|
|
}),
|
|
|
|
social: new Directory({
|
2018-11-30 17:11:56 +01:00
|
|
|
github: new UrlFile("https://github.com/FWDekker/"),
|
|
|
|
stackoverflow: new UrlFile("https://stackoverflow.com/u/3307872"),
|
|
|
|
linkedin: new UrlFile("https://www.linkedin.com/in/fwdekker/")
|
2018-11-30 15:23:03 +01:00
|
|
|
}),
|
2019-06-10 15:39:56 +02:00
|
|
|
"resume.pdf": new UrlFile("https://static.fwdekker.com/misc/resume.pdf")
|
2018-11-30 15:23:03 +01:00
|
|
|
});
|
2019-10-21 17:07:16 +02:00
|
|
|
this.files = this.root;
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-21 03:13:14 +02:00
|
|
|
get pwd(): string {
|
|
|
|
return this._pwd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
getNode(pathString: string): Node {
|
2019-10-21 03:13:14 +02:00
|
|
|
const path = new Path(this._pwd, pathString);
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
let node: Node = this.root;
|
2018-12-06 10:29:14 +01:00
|
|
|
path.parts.forEach(part => {
|
2019-10-21 02:25:42 +02:00
|
|
|
if (part === "" || node === undefined || node instanceof File)
|
2018-11-28 22:23:11 +01:00
|
|
|
return;
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
if (node instanceof Directory)
|
|
|
|
node = node.getNode(part);
|
|
|
|
else
|
|
|
|
throw "Node must be file or directory.";
|
2018-11-28 22:23:11 +01:00
|
|
|
});
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resets navigation in the file system.
|
|
|
|
*/
|
2019-10-26 13:09:34 +02:00
|
|
|
reset(): void {
|
2019-10-21 03:13:14 +02:00
|
|
|
this._pwd = "/";
|
2019-10-21 02:25:42 +02:00
|
|
|
this.files = this.root;
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2019-10-21 17:07:16 +02:00
|
|
|
private executeForEach(inputs: string[], fun: (_: string) => string): string {
|
|
|
|
const outputs: string[] = [];
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
inputs.forEach(input => {
|
|
|
|
const output = fun(input);
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2019-06-10 15:31:46 +02:00
|
|
|
if (output !== "")
|
2018-11-29 13:01:55 +01:00
|
|
|
outputs.push(output);
|
|
|
|
});
|
|
|
|
|
|
|
|
return outputs.join("\n");
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the current directory to {@code path}, if it exists.
|
|
|
|
*
|
2018-12-06 10:29:14 +01:00
|
|
|
* @param pathString the absolute or relative path to change the current directory to
|
2018-11-28 22:23:11 +01:00
|
|
|
* @returns {string} an empty string if the change was successful, or an error message explaining what went wrong
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
cd(pathString: string): string {
|
|
|
|
if (pathString === undefined)
|
2018-11-28 22:41:59 +01:00
|
|
|
return "";
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2019-10-21 03:13:14 +02:00
|
|
|
const path = new Path(this._pwd, pathString);
|
2018-12-06 10:29:14 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
const node = this.getNode(path.path);
|
|
|
|
if (node === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The directory '${path.path}' does not exist`;
|
2019-10-21 02:25:42 +02:00
|
|
|
if (!(node instanceof Directory))
|
|
|
|
return `'${path.path}' is not a directory.`;
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2019-10-21 03:13:14 +02:00
|
|
|
this._pwd = path.path;
|
2019-10-21 02:25:42 +02:00
|
|
|
this.files = node;
|
2018-11-28 22:41:59 +01:00
|
|
|
return "";
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2018-11-29 20:56:27 +01:00
|
|
|
/**
|
|
|
|
* Creates an empty file at {@code path} if it does not exist.
|
|
|
|
*
|
2019-10-21 02:25:42 +02:00
|
|
|
* @param pathString the path to create a file at if it does not exist
|
2018-11-29 20:56:27 +01:00
|
|
|
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
private createFile(pathString: string): string {
|
2019-10-21 03:13:14 +02:00
|
|
|
const path = new Path(this._pwd, pathString);
|
2018-11-29 20:56:27 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
const headNode = this.getNode(path.head);
|
2019-06-10 15:31:46 +02:00
|
|
|
if (headNode === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The directory '${path.head}' does not exist`;
|
2019-10-21 02:25:42 +02:00
|
|
|
if (!(headNode instanceof Directory))
|
2018-12-06 10:29:14 +01:00
|
|
|
return `${path.head} is not a directory`;
|
2018-12-03 16:47:14 +01:00
|
|
|
|
2018-12-06 10:29:14 +01:00
|
|
|
const tailNode = headNode.getNode(path.tail);
|
2019-06-10 15:31:46 +02:00
|
|
|
if (tailNode !== undefined)
|
2019-10-21 02:25:42 +02:00
|
|
|
return ""; // File already exists
|
2018-11-29 20:56:27 +01:00
|
|
|
|
2019-10-20 23:55:04 +02:00
|
|
|
headNode.addNode(path.tail, new File());
|
2018-11-29 20:56:27 +01:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls {@link createFile} on all elements in {@code paths}.
|
|
|
|
*
|
|
|
|
* @param paths {string[]} the absolute or relative paths to the files to be created
|
|
|
|
* @returns {string} the warnings generated during creation of the files
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
createFiles(paths: string[]): string {
|
|
|
|
return this.executeForEach(paths, path => this.createFile(path));
|
2018-11-29 20:56:27 +01:00
|
|
|
}
|
|
|
|
|
2018-11-29 13:34:46 +01:00
|
|
|
/**
|
|
|
|
* Copies {@code source} to {@code destination}.
|
|
|
|
*
|
|
|
|
* If the destination does not exist, the source will be copied to that exact location. If the destination exists
|
|
|
|
* and is a directory, the source will be copied into the directory. If the destination exists but is not a
|
|
|
|
* directory, the copy will fail.
|
|
|
|
*
|
2018-12-06 10:29:14 +01:00
|
|
|
* @param sourceString {string} the absolute or relative path to the file or directory to copy
|
|
|
|
* @param destinationString {string} the absolute or relative path to the destination
|
2018-11-29 13:34:46 +01:00
|
|
|
* @returns {string} an empty string if the copy was successful, or a message explaining what went wrong
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
cp(sourceString: string, destinationString: string): string {
|
2019-10-21 03:13:14 +02:00
|
|
|
const sourcePath = new Path(this._pwd, sourceString);
|
2019-10-21 02:25:42 +02:00
|
|
|
const sourceTailNode = this.getNode(sourcePath.path);
|
2018-11-29 13:34:46 +01:00
|
|
|
|
2019-10-21 03:13:14 +02:00
|
|
|
const destinationPath = new Path(this._pwd, destinationString);
|
2019-10-21 02:25:42 +02:00
|
|
|
const destinationHeadNode = this.getNode(destinationPath.head);
|
|
|
|
const destinationTailNode = this.getNode(destinationPath.path);
|
2018-11-29 13:34:46 +01:00
|
|
|
|
2019-06-10 15:31:46 +02:00
|
|
|
if (sourceTailNode === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The file '${sourcePath.path}' does not exist`;
|
2019-06-10 15:31:46 +02:00
|
|
|
if (!(sourceTailNode instanceof File))
|
2018-11-29 13:34:46 +01:00
|
|
|
return `Cannot copy directory.`;
|
2019-06-10 15:31:46 +02:00
|
|
|
if (destinationHeadNode === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The directory '${destinationPath.head}' does not exist`;
|
2018-11-29 13:34:46 +01:00
|
|
|
|
|
|
|
let targetNode;
|
|
|
|
let targetName;
|
2018-12-06 10:29:14 +01:00
|
|
|
if (destinationTailNode === undefined) {
|
2019-10-21 17:07:16 +02:00
|
|
|
if (!(destinationHeadNode instanceof Directory))
|
|
|
|
return `The path '${destinationPath.head}' does not point to a directory`;
|
|
|
|
|
2018-12-06 10:29:14 +01:00
|
|
|
targetNode = destinationHeadNode;
|
|
|
|
targetName = destinationPath.tail;
|
2018-11-29 13:34:46 +01:00
|
|
|
} else {
|
2019-10-21 17:07:16 +02:00
|
|
|
if (!(destinationTailNode instanceof Directory))
|
|
|
|
return `The path '${destinationPath.tail}' does not point to a directory`;
|
|
|
|
|
2018-12-06 10:29:14 +01:00
|
|
|
targetNode = destinationTailNode;
|
|
|
|
targetName = sourcePath.tail;
|
2018-11-29 13:34:46 +01:00
|
|
|
}
|
|
|
|
|
2019-06-10 15:31:46 +02:00
|
|
|
if (targetNode.getNode(targetName) !== undefined)
|
2018-11-29 13:34:46 +01:00
|
|
|
return `The file '${targetName}' already exists`;
|
|
|
|
|
2018-12-06 10:29:14 +01:00
|
|
|
targetNode.addNode(targetName, sourceTailNode.copy());
|
2018-11-29 13:34:46 +01:00
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
/**
|
|
|
|
* Returns the directory at {@code path}, or the current directory if no path is given.
|
|
|
|
*
|
2018-12-06 10:29:14 +01:00
|
|
|
* @param pathString {string} the absolute or relative path to the directory to return
|
2018-11-28 22:23:11 +01:00
|
|
|
* @returns {Object} the directory at {@code path}, or the current directory if no path is given
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
ls(pathString: string): string {
|
2019-10-21 03:13:14 +02:00
|
|
|
const path = new Path(this._pwd, pathString);
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
const node = this.getNode(path.path);
|
|
|
|
if (node === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The directory '${path.path}' does not exist`;
|
2019-10-21 02:25:42 +02:00
|
|
|
if (!(node instanceof Directory))
|
2018-12-06 10:29:14 +01:00
|
|
|
return `'${path.path}' is not a directory`;
|
2018-11-29 00:05:37 +01:00
|
|
|
|
2019-04-08 22:42:11 +02:00
|
|
|
const dirList = [new Directory({}).nameString("."), new Directory({}).nameString("..")];
|
2019-10-21 17:07:16 +02:00
|
|
|
const fileList: string[] = [];
|
2018-11-29 00:05:37 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
const nodes = node.nodes;
|
2018-11-30 15:23:03 +01:00
|
|
|
Object.keys(nodes)
|
2019-10-20 23:55:04 +02:00
|
|
|
.sortAlphabetically((x) => x)
|
2018-11-30 15:23:03 +01:00
|
|
|
.forEach(name => {
|
|
|
|
const node = nodes[name];
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
if (node instanceof Directory)
|
2018-12-04 12:54:04 +01:00
|
|
|
dirList.push(node.nameString(name));
|
2019-10-21 02:25:42 +02:00
|
|
|
else if (node instanceof File)
|
2018-12-04 12:54:04 +01:00
|
|
|
fileList.push(node.nameString(name));
|
2019-06-10 15:31:46 +02:00
|
|
|
else
|
2018-11-30 15:23:03 +01:00
|
|
|
throw `${name} is neither a file nor a directory!`;
|
|
|
|
});
|
2018-11-29 00:05:37 +01:00
|
|
|
|
|
|
|
return dirList.concat(fileList).join("\n");
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
/**
|
|
|
|
* Creates an empty directory in the file system.
|
|
|
|
*
|
2018-12-06 10:29:14 +01:00
|
|
|
* @param pathString {string} the absolute or relative path to the directory to create
|
2018-11-28 22:23:11 +01:00
|
|
|
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
private mkdir(pathString: string): string {
|
2019-10-21 03:13:14 +02:00
|
|
|
const path = new Path(pathString);
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
const headNode = this.getNode(path.head);
|
2019-06-10 15:31:46 +02:00
|
|
|
if (headNode === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The directory '${path.head}' does not exist`;
|
2019-10-21 02:25:42 +02:00
|
|
|
if (!(headNode instanceof Directory))
|
2018-12-06 10:29:14 +01:00
|
|
|
return `'${path.head}' is not a directory`;
|
2019-06-10 15:31:46 +02:00
|
|
|
if (headNode.getNode(path.tail))
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The directory '${path.tail}' already exists`;
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-12-06 10:29:14 +01:00
|
|
|
headNode.addNode(path.tail, new Directory());
|
2018-11-28 22:41:59 +01:00
|
|
|
return "";
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
/**
|
|
|
|
* Calls {@link mkdir} on all elements in {@code paths}.
|
|
|
|
*
|
|
|
|
* @param paths {string[]} the absolute or relative paths to the directories to create
|
|
|
|
* @returns {string} the warnings generated during creation of the directories
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
mkdirs(paths: string[]): string {
|
|
|
|
return this.executeForEach(paths, this.mkdir.bind(this));
|
2018-11-29 09:08:24 +01:00
|
|
|
}
|
|
|
|
|
2018-11-29 13:18:48 +01:00
|
|
|
/**
|
|
|
|
* Moves {@code source} to {@code destination}.
|
|
|
|
*
|
|
|
|
* If the destination does not exist, the source will be moved to that exact location. If the destination exists and
|
|
|
|
* is a directory, the source will be moved into the directory. If the destination exists but is not a directory,
|
|
|
|
* the move will fail.
|
|
|
|
*
|
2018-12-06 10:29:14 +01:00
|
|
|
* @param sourceString {string} the absolute or relative path to the file or directory to move
|
|
|
|
* @param destinationString {string} the absolute or relative path to the destination
|
2018-11-29 13:34:46 +01:00
|
|
|
* @returns {string} an empty string if the move was successful, or a message explaining what went wrong
|
2018-11-29 13:18:48 +01:00
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
mv(sourceString: string, destinationString: string): string {
|
2019-10-21 03:13:14 +02:00
|
|
|
const sourcePath = new Path(sourceString);
|
2019-10-21 02:25:42 +02:00
|
|
|
const sourceHeadNode = this.getNode(sourcePath.head);
|
|
|
|
const sourceTailNode = this.getNode(sourcePath.path);
|
2018-12-06 10:29:14 +01:00
|
|
|
|
2019-10-21 03:13:14 +02:00
|
|
|
const destinationPath = new Path(destinationString);
|
2019-10-21 02:25:42 +02:00
|
|
|
const destinationHeadNode = this.getNode(destinationPath.head);
|
|
|
|
const destinationTailNode = this.getNode(destinationPath.path);
|
2018-12-06 10:29:14 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
if (!(sourceHeadNode instanceof Directory))
|
|
|
|
return `The path '${sourcePath.head}' does not point to a directory`;
|
2019-06-10 15:31:46 +02:00
|
|
|
if (sourceTailNode === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The file '${sourcePath.path}' does not exist`;
|
2019-06-10 15:31:46 +02:00
|
|
|
if (destinationHeadNode === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The directory '${destinationPath.head}' does not exist`;
|
2018-11-29 13:18:48 +01:00
|
|
|
|
2018-11-29 13:34:46 +01:00
|
|
|
let targetNode;
|
|
|
|
let targetName;
|
2018-12-06 10:29:14 +01:00
|
|
|
if (destinationTailNode === undefined) {
|
2019-10-21 17:07:16 +02:00
|
|
|
if (!(destinationHeadNode instanceof Directory))
|
|
|
|
return `The path '${destinationPath.head}' does not point to a directory`;
|
|
|
|
|
2018-12-06 10:29:14 +01:00
|
|
|
targetNode = destinationHeadNode;
|
|
|
|
targetName = destinationPath.tail;
|
2018-11-29 13:18:48 +01:00
|
|
|
} else {
|
2019-10-21 17:07:16 +02:00
|
|
|
if (!(destinationTailNode instanceof Directory))
|
|
|
|
return `The path '${destinationPath.tail}' does not point to a directory`;
|
|
|
|
|
2018-12-06 10:29:14 +01:00
|
|
|
targetNode = destinationTailNode;
|
|
|
|
targetName = sourcePath.tail;
|
2018-11-29 13:34:46 +01:00
|
|
|
}
|
|
|
|
|
2019-06-10 15:31:46 +02:00
|
|
|
if (targetNode.getNode(targetName) !== undefined)
|
2018-11-29 13:34:46 +01:00
|
|
|
return `The file '${targetName}' already exists`;
|
2018-11-29 13:18:48 +01:00
|
|
|
|
2018-12-06 10:29:14 +01:00
|
|
|
sourceHeadNode.removeNode(sourceTailNode);
|
2019-10-21 02:25:42 +02:00
|
|
|
targetNode.addNode(targetName, sourceTailNode);
|
2018-11-29 13:34:46 +01:00
|
|
|
|
2018-11-29 13:18:48 +01:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
/**
|
|
|
|
* Removes a file from the file system.
|
|
|
|
*
|
2018-12-06 10:29:14 +01:00
|
|
|
* @param pathString {string} the absolute or relative path to the file to be removed
|
2018-11-29 09:37:42 +01:00
|
|
|
* @param force {boolean} true if no warnings should be given if removal is unsuccessful
|
2018-11-29 13:50:36 +01:00
|
|
|
* @param recursive {boolean} true if files and directories should be removed recursively
|
|
|
|
* @param noPreserveRoot {boolean} false if the root directory should not be removed
|
2018-11-28 22:23:11 +01:00
|
|
|
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
private rm(pathString: string, force: boolean = false, recursive: boolean = false, noPreserveRoot: boolean = false): string {
|
2019-10-21 03:13:14 +02:00
|
|
|
const path = new Path(pathString);
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
const parentNode = this.getNode(path.head);
|
2019-06-10 15:31:46 +02:00
|
|
|
if (parentNode === undefined)
|
2018-11-29 09:37:42 +01:00
|
|
|
return force
|
|
|
|
? ""
|
2018-12-06 10:29:14 +01:00
|
|
|
: `The directory '${path.head}' does not exist`;
|
2019-10-21 02:25:42 +02:00
|
|
|
if (!(parentNode instanceof Directory))
|
2018-11-29 12:42:14 +01:00
|
|
|
return force
|
|
|
|
? ""
|
2018-12-06 10:29:14 +01:00
|
|
|
: `'${path.head}' is not a directory`;
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
const childNode = this.getNode(path.path);
|
2019-06-10 15:31:46 +02:00
|
|
|
if (childNode === undefined)
|
2018-11-29 12:42:14 +01:00
|
|
|
return force
|
|
|
|
? ""
|
2018-12-06 10:29:14 +01:00
|
|
|
: `The file '${path.path}' does not exist`;
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 13:50:36 +01:00
|
|
|
if (recursive) {
|
2019-06-10 15:31:46 +02:00
|
|
|
if (path.path === "/")
|
|
|
|
if (noPreserveRoot)
|
2019-10-21 02:25:42 +02:00
|
|
|
this.root = new Directory();
|
2019-06-10 15:31:46 +02:00
|
|
|
else
|
2018-11-29 13:50:36 +01:00
|
|
|
return "'/' cannot be removed";
|
2019-06-10 15:31:46 +02:00
|
|
|
else
|
2018-11-29 13:50:36 +01:00
|
|
|
parentNode.removeNode(childNode);
|
|
|
|
} else {
|
2019-06-10 15:31:46 +02:00
|
|
|
if (!(childNode instanceof File))
|
2018-11-29 13:50:36 +01:00
|
|
|
return force
|
|
|
|
? ""
|
2018-12-06 10:29:14 +01:00
|
|
|
: `'${path.tail}' is not a file`;
|
2018-11-29 13:50:36 +01:00
|
|
|
|
|
|
|
parentNode.removeNode(childNode);
|
|
|
|
}
|
2018-11-28 22:41:59 +01:00
|
|
|
return "";
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
/**
|
|
|
|
* Calls {@link rm} on all elements in {@code paths}.
|
|
|
|
*
|
|
|
|
* @param paths {string} the absolute or relative paths to the files to be removed
|
|
|
|
* @param force {boolean} true if no warnings should be given if removal is unsuccessful
|
2018-11-29 13:50:36 +01:00
|
|
|
* @param recursive {boolean} true if files and directories should be removed recursively
|
|
|
|
* @param noPreserveRoot {boolean} false if the root directory should not be removed
|
2018-11-29 13:01:55 +01:00
|
|
|
* @returns {string} the warnings generated during removal of the directories
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
rms(paths: string[], force: boolean = false, recursive: boolean = false, noPreserveRoot: boolean = false): string {
|
|
|
|
return this.executeForEach(paths, path => {
|
2018-11-29 13:50:36 +01:00
|
|
|
return this.rm(path, force, recursive, noPreserveRoot);
|
2018-11-29 09:37:42 +01:00
|
|
|
});
|
2018-11-29 09:20:23 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
/**
|
|
|
|
* Removes a directory from the file system.
|
|
|
|
*
|
2018-12-06 10:29:14 +01:00
|
|
|
* @param pathString {string} the absolute or relative path to the directory to be removed
|
2018-11-28 22:23:11 +01:00
|
|
|
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
private rmdir(pathString: string): string {
|
2019-10-21 03:13:14 +02:00
|
|
|
const path = new Path(pathString);
|
2018-12-06 10:29:14 +01:00
|
|
|
|
|
|
|
if (path.path === "/") {
|
2019-10-21 02:25:42 +02:00
|
|
|
if (this.root.nodeCount > 0)
|
2018-11-29 00:21:35 +01:00
|
|
|
return `The directory is not empty.`;
|
2019-06-10 15:31:46 +02:00
|
|
|
else
|
2018-11-29 12:42:14 +01:00
|
|
|
return "";
|
2018-11-29 00:21:35 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
const parentDir = this.getNode(path.head);
|
2019-06-10 15:31:46 +02:00
|
|
|
if (parentDir === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The directory '${path.head}' does not exist`;
|
2019-10-21 02:25:42 +02:00
|
|
|
if (!(parentDir instanceof Directory))
|
2018-12-06 10:29:14 +01:00
|
|
|
return `'${path.head}' is not a directory`;
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2018-12-06 10:29:14 +01:00
|
|
|
const childDir = parentDir.getNode(path.tail);
|
2019-06-10 15:31:46 +02:00
|
|
|
if (childDir === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
return `The directory '${path.tail}' does not exist`;
|
2019-10-21 02:25:42 +02:00
|
|
|
if (!(childDir instanceof Directory))
|
2018-12-06 10:29:14 +01:00
|
|
|
return `'${path.tail}' is not a directory`;
|
2019-10-21 02:25:42 +02:00
|
|
|
if (childDir.nodeCount > 0)
|
2018-11-28 22:23:11 +01:00
|
|
|
return `The directory is not empty`;
|
|
|
|
|
2018-11-29 12:42:14 +01:00
|
|
|
parentDir.removeNode(childDir);
|
2018-11-28 22:41:59 +01:00
|
|
|
return "";
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
2018-11-29 09:08:24 +01:00
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
/**
|
|
|
|
* Calls {@link rmdir} on all elements in {@code paths}.
|
|
|
|
*
|
|
|
|
* @param paths {string[]} the absolute or relative paths to the directories to be removed
|
|
|
|
* @returns {string} the warnings generated during removal of the directories
|
|
|
|
*/
|
2019-10-21 02:25:42 +02:00
|
|
|
rmdirs(paths: string[]): string {
|
|
|
|
return this.executeForEach(paths, path => this.rmdir(path));
|
2018-11-29 09:08:24 +01:00
|
|
|
}
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
2018-11-29 00:05:37 +01:00
|
|
|
|
|
|
|
|
2019-10-20 23:55:04 +02:00
|
|
|
export class Path {
|
2019-10-21 02:25:42 +02:00
|
|
|
private readonly _parts: string[];
|
|
|
|
readonly path: string;
|
|
|
|
readonly head: string;
|
|
|
|
readonly tail: string;
|
2019-10-20 23:55:04 +02:00
|
|
|
|
|
|
|
|
2019-10-21 17:07:16 +02:00
|
|
|
constructor(currentPath: string, relativePath: string | undefined = undefined) {
|
2018-12-06 10:29:14 +01:00
|
|
|
let path;
|
2019-06-10 15:31:46 +02:00
|
|
|
if (relativePath === undefined)
|
2018-12-06 10:29:14 +01:00
|
|
|
path = currentPath;
|
2019-06-10 15:31:46 +02:00
|
|
|
else if (relativePath.startsWith("/"))
|
2018-12-06 10:29:14 +01:00
|
|
|
path = relativePath;
|
2019-06-10 15:31:46 +02:00
|
|
|
else
|
2018-12-06 10:29:14 +01:00
|
|
|
path = `${currentPath}/${relativePath}`;
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
this.path = `${path}/`
|
2018-12-06 10:29:14 +01:00
|
|
|
.replaceAll(/\/\.\//, "/")
|
|
|
|
.replaceAll(/(\/+)([^./]+)(\/+)(\.\.)(\/+)/, "/")
|
|
|
|
.replaceAll(/\/{2,}/, "/")
|
2019-03-20 21:11:16 +01:00
|
|
|
.replace(/^\/?\.?\.\/$/, "/")
|
2018-12-06 10:29:14 +01:00
|
|
|
.toString();
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
this._parts = this.path.split("/");
|
|
|
|
this.head = this.parts.slice(0, -2).join("/");
|
|
|
|
this.tail = this.parts.slice(0, -1).slice(-1).join("/");
|
2018-12-06 10:29:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
get parts(): string[] {
|
2018-12-06 10:29:14 +01:00
|
|
|
return this._parts.slice();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
export abstract class Node {
|
|
|
|
abstract copy(): Node;
|
2018-11-29 12:42:14 +01:00
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
abstract nameString(name: string): string;
|
2018-11-29 12:42:14 +01:00
|
|
|
|
2019-10-21 17:07:16 +02:00
|
|
|
abstract visit(fun: (node: Node) => void, pre: (node: Node) => void, post: (node: Node) => void): void;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2019-10-20 23:55:04 +02:00
|
|
|
export class Directory extends Node {
|
2019-10-21 17:07:16 +02:00
|
|
|
private readonly _nodes: { [key: string]: Node };
|
2019-10-21 02:25:42 +02:00
|
|
|
// noinspection TypeScriptFieldCanBeMadeReadonly: False positive
|
|
|
|
private _parent: Directory;
|
2019-10-20 23:55:04 +02:00
|
|
|
|
|
|
|
|
2019-10-21 17:07:16 +02:00
|
|
|
constructor(nodes: { [key: string]: Node } = {}) {
|
2018-11-30 15:23:03 +01:00
|
|
|
super();
|
2018-11-29 12:42:14 +01:00
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
this._parent = this;
|
|
|
|
this._nodes = nodes;
|
2018-11-29 12:42:14 +01:00
|
|
|
|
2019-10-21 17:07:16 +02:00
|
|
|
Object.values(this._nodes)
|
|
|
|
.forEach(node => {
|
|
|
|
if (node instanceof Directory) node._parent = this;
|
|
|
|
});
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-21 17:07:16 +02:00
|
|
|
get nodes(): { [key: string]: Node } {
|
2018-11-30 15:23:03 +01:00
|
|
|
return Object.assign({}, this._nodes);
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
get nodeCount(): number {
|
2018-11-30 15:23:03 +01:00
|
|
|
return Object.keys(this._nodes).length;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
get parent(): Directory {
|
|
|
|
return this._parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getNode(name: string): Node {
|
2018-11-29 12:42:14 +01:00
|
|
|
switch (name) {
|
|
|
|
case ".":
|
|
|
|
return this;
|
|
|
|
case "..":
|
|
|
|
return this._parent;
|
|
|
|
default:
|
2018-11-30 15:23:03 +01:00
|
|
|
return this._nodes[name];
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
addNode(name: string, node: Node) {
|
|
|
|
if (node instanceof Directory)
|
2018-11-29 13:34:46 +01:00
|
|
|
node._parent = this;
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
this._nodes[name] = node;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
removeNode(nodeOrName: Node | string) {
|
2018-11-30 15:23:03 +01:00
|
|
|
if (nodeOrName instanceof Node) {
|
|
|
|
const name = Object.keys(this._nodes).find(key => this._nodes[key] === nodeOrName);
|
2019-10-21 17:07:16 +02:00
|
|
|
if (name === undefined)
|
|
|
|
throw `Could not remove node '${nodeOrName}'.`;
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
delete this._nodes[name];
|
2018-11-29 12:42:14 +01:00
|
|
|
} else {
|
2018-11-30 15:23:03 +01:00
|
|
|
delete this._nodes[name];
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
copy(): Directory {
|
2019-10-21 17:07:16 +02:00
|
|
|
return new Directory(this.nodes);
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
nameString(name: string): string {
|
|
|
|
// @ts-ignore: Defined in `terminal.ts`
|
2019-04-08 22:42:11 +02:00
|
|
|
return `<a href="#" class="dirLink" onclick="run('cd ${relToAbs(name)}/');run('ls');">${name}/</a>`;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
visit(fun: (node: Node) => void,
|
|
|
|
pre: (node: Node) => void = emptyFunction,
|
|
|
|
post: (node: Node) => void = emptyFunction) {
|
2018-11-29 12:42:14 +01:00
|
|
|
pre(this);
|
|
|
|
|
|
|
|
fun(this);
|
2019-10-21 02:25:42 +02:00
|
|
|
Object.keys(this._nodes).forEach(name => this._nodes[name].visit(fun, pre, post));
|
2018-11-29 12:42:14 +01:00
|
|
|
|
|
|
|
post(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-20 23:55:04 +02:00
|
|
|
export class File extends Node {
|
2018-11-30 15:23:03 +01:00
|
|
|
constructor() {
|
|
|
|
super();
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
copy(): File {
|
2018-11-30 15:23:03 +01:00
|
|
|
return new File();
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
nameString(name: string): string {
|
2018-11-30 15:23:03 +01:00
|
|
|
return name;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
visit(fun: (node: Node) => void,
|
|
|
|
pre: (node: Node) => void = emptyFunction,
|
|
|
|
post: (node: Node) => void = emptyFunction) {
|
2018-11-29 12:42:14 +01:00
|
|
|
pre(this);
|
|
|
|
fun(this);
|
|
|
|
post(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-20 23:55:04 +02:00
|
|
|
export class UrlFile extends File {
|
2019-10-21 02:25:42 +02:00
|
|
|
readonly url: string;
|
2019-10-20 23:55:04 +02:00
|
|
|
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
constructor(url: string) {
|
2018-11-30 15:23:03 +01:00
|
|
|
super();
|
2018-11-29 12:42:14 +01:00
|
|
|
|
|
|
|
this.url = url;
|
2018-11-29 00:05:37 +01:00
|
|
|
}
|
2018-11-29 12:42:14 +01:00
|
|
|
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
copy(): UrlFile {
|
2018-11-30 17:11:56 +01:00
|
|
|
return new UrlFile(this.url);
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:25:42 +02:00
|
|
|
nameString(name: string): string {
|
2019-04-14 00:36:26 +02:00
|
|
|
return `<a href="${this.url}" class="fileLink">${name}</a>`;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
}
|