2018-11-28 22:23:11 +01:00
|
|
|
class FileSystem {
|
|
|
|
constructor() {
|
2018-11-28 22:41:59 +01:00
|
|
|
this.pwd = "/";
|
2018-11-30 15:23:03 +01:00
|
|
|
this._root = new Directory({
|
|
|
|
personal: new Directory({
|
|
|
|
steam: new LinkFile("https://steamcommunity.com/id/Waflix"),
|
|
|
|
nukapedia: new LinkFile("http://fallout.wikia.com/wiki/User:FDekker")
|
|
|
|
}),
|
|
|
|
projects: new Directory({
|
|
|
|
minor: new Directory({
|
|
|
|
dice: new LinkFile("https://fwdekker.com/dice")
|
|
|
|
}),
|
|
|
|
randomness: new LinkFile("https://github.com/FWDekker/intellij-randomness"),
|
|
|
|
schaapi: new LinkFile("http://cafejojo.org/schaapi")
|
|
|
|
}),
|
|
|
|
social: new Directory({
|
|
|
|
github: new LinkFile("https://github.com/FWDekker/"),
|
|
|
|
stackoverflow: new LinkFile("https://stackoverflow.com/u/3307872"),
|
|
|
|
linkedin: new LinkFile("https://www.linkedin.com/in/fwdekker/")
|
|
|
|
}),
|
|
|
|
"resume.pdf": new LinkFile("resume.pdf")
|
|
|
|
});
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
static _sanitisePath(path) {
|
|
|
|
const selfRegex = /\/\.\//; // Match "./"
|
|
|
|
const upRegex = /(\/+)([^./]+)(\/+)(\.\.)(\/+)/; // Match "/directory/../"
|
|
|
|
const doubleRegex = /\/{2,}/; // Match "///"
|
|
|
|
|
|
|
|
return `${path}/`
|
|
|
|
.replaceAll(selfRegex, "/")
|
|
|
|
.replaceAll(upRegex, "/")
|
|
|
|
.replaceAll(doubleRegex, "/")
|
|
|
|
.toString();
|
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
_absolutePath(path) {
|
2018-11-28 22:41:59 +01:00
|
|
|
if (path.startsWith("/")) {
|
2018-11-28 22:23:11 +01:00
|
|
|
return path;
|
|
|
|
} else {
|
|
|
|
return `${this.pwd}/${path}`;
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
_normalisePath(path) {
|
|
|
|
return FileSystem._sanitisePath(this._absolutePath(path));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-11-28 22:41:59 +01:00
|
|
|
_childPath(path) {
|
2018-11-29 10:16:31 +01:00
|
|
|
const childPath = this._normalisePath(path).split("/").slice(0, -1).slice(-1).join("/");
|
|
|
|
return (childPath === "")
|
|
|
|
? "."
|
|
|
|
: childPath;
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
_parentPath(path) {
|
|
|
|
const parentPath = this._normalisePath(path).split("/").slice(0, -2).join("/");
|
|
|
|
return (parentPath === "")
|
|
|
|
? "/"
|
|
|
|
: parentPath;
|
2018-11-29 09:37:42 +01:00
|
|
|
}
|
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
_getFile(path) {
|
|
|
|
path = this._normalisePath(path);
|
|
|
|
|
|
|
|
let file = this._root;
|
2018-11-28 22:41:59 +01:00
|
|
|
path.split("/").forEach(part => {
|
|
|
|
if (part === "") {
|
2018-11-28 22:23:11 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (file === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
2018-11-29 12:42:14 +01:00
|
|
|
if (FileSystem.isFile(file)) {
|
|
|
|
file = undefined;
|
2018-11-30 15:23:03 +01:00
|
|
|
return;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2018-11-29 12:42:14 +01:00
|
|
|
file = file.getNode(part);
|
2018-11-28 22:23:11 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
return file;
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
_executeForEach(inputs, fun) {
|
|
|
|
const outputs = [];
|
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
|
|
|
|
2018-11-29 13:01:55 +01:00
|
|
|
if (output !== "") {
|
|
|
|
outputs.push(output);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return outputs.join("\n");
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
|
|
|
|
/**
|
2018-11-29 12:42:14 +01:00
|
|
|
* Returns true iff {@code node} represents a directory.
|
2018-11-28 22:23:11 +01:00
|
|
|
*
|
2018-11-29 12:42:14 +01:00
|
|
|
* @param node {Object} a node from the file system
|
|
|
|
* @returns {boolean} true iff {@code node} represents a directory
|
2018-11-28 22:23:11 +01:00
|
|
|
*/
|
2018-11-29 12:42:14 +01:00
|
|
|
static isDirectory(node) {
|
|
|
|
return node instanceof Directory;
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
/**
|
2018-11-29 12:42:14 +01:00
|
|
|
* Returns true iff {@code node} represents a file.
|
2018-11-28 22:23:11 +01:00
|
|
|
*
|
2018-11-29 12:42:14 +01:00
|
|
|
* @param node {Object} an object from the file system
|
|
|
|
* @returns {boolean} true iff {@code node} represents a file
|
2018-11-28 22:23:11 +01:00
|
|
|
*/
|
2018-11-29 12:42:14 +01:00
|
|
|
static isFile(node) {
|
|
|
|
return node instanceof File;
|
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.
|
|
|
|
*
|
|
|
|
* @param path the absolute or relative path to change the current directory to
|
|
|
|
* @returns {string} an empty string if the change was successful, or an error message explaining what went wrong
|
|
|
|
*/
|
|
|
|
cd(path) {
|
|
|
|
if (path === undefined) {
|
2018-11-28 22:41:59 +01:00
|
|
|
return "";
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const file = this._getFile(path);
|
2018-11-29 12:42:14 +01:00
|
|
|
if (file === undefined) {
|
2018-11-28 22:23:11 +01:00
|
|
|
return `The directory '${path}' does not exist`;
|
|
|
|
}
|
2018-11-29 12:42:14 +01:00
|
|
|
if (!FileSystem.isDirectory(file)) {
|
|
|
|
return `'${path}' is not a directory`;
|
|
|
|
}
|
2018-11-28 22:23:11 +01:00
|
|
|
|
|
|
|
this.pwd = this._normalisePath(path);
|
|
|
|
this.files = file;
|
|
|
|
|
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.
|
|
|
|
*
|
|
|
|
* @param path the path to create a file at if it does not exist
|
|
|
|
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
|
|
|
*/
|
|
|
|
createFile(path) {
|
|
|
|
const parentNode = this._getFile(this._parentPath(path));
|
|
|
|
const childPath = this._childPath(path);
|
|
|
|
const childNode = this._getFile(childPath);
|
|
|
|
|
|
|
|
if (parentNode === undefined) {
|
|
|
|
return `The directory '' does not exist`;
|
|
|
|
}
|
|
|
|
if (childNode !== undefined) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
parentNode.addNode(childPath, new File(childPath));
|
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
|
|
|
|
*/
|
|
|
|
createFiles(paths) {
|
|
|
|
return this._executeForEach(paths, path => {
|
|
|
|
return this.createFile(path);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
*
|
|
|
|
* @param source {string} the absolute or relative path to the file or directory to copy
|
|
|
|
* @param destination {string} the absolute or relative path to the destination
|
|
|
|
* @returns {string} an empty string if the copy was successful, or a message explaining what went wrong
|
|
|
|
*/
|
|
|
|
cp(source, destination) {
|
|
|
|
const sourceChildName = this._childPath(source);
|
|
|
|
const sourceChildNode = this._getFile(source);
|
|
|
|
|
|
|
|
const destinationChildName = this._childPath(destination);
|
|
|
|
const destinationChildNode = this._getFile(destination);
|
|
|
|
const destinationParentPath = this._parentPath(destination);
|
|
|
|
const destinationParentNode = this._getFile(destinationParentPath);
|
|
|
|
|
|
|
|
if (sourceChildNode === undefined) {
|
|
|
|
return `The file '${source}' does not exist`;
|
|
|
|
}
|
|
|
|
if (!(sourceChildNode instanceof File)) {
|
|
|
|
return `Cannot copy directory.`;
|
|
|
|
}
|
|
|
|
if (destinationParentNode === undefined) {
|
|
|
|
return `The directory '${destinationParentPath}' does not exist`;
|
|
|
|
}
|
|
|
|
|
|
|
|
let targetNode;
|
|
|
|
let targetName;
|
|
|
|
if (destinationChildNode === undefined) {
|
|
|
|
targetNode = destinationParentNode;
|
|
|
|
targetName = destinationChildName;
|
|
|
|
} else {
|
|
|
|
targetNode = destinationChildNode;
|
|
|
|
targetName = sourceChildName;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (targetNode.getNode(targetName) !== undefined) {
|
|
|
|
return `The file '${targetName}' already exists`;
|
|
|
|
}
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
targetNode.addNode(targetName, sourceChildNode.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.
|
|
|
|
*
|
|
|
|
* @param path {string} the absolute or relative path to the directory to return
|
|
|
|
* @returns {Object} the directory at {@code path}, or the current directory if no path is given
|
|
|
|
*/
|
|
|
|
ls(path) {
|
|
|
|
path = (path || this.pwd);
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
const dir = this._getFile(path);
|
|
|
|
if (dir === undefined) {
|
2018-11-29 00:05:37 +01:00
|
|
|
return `The directory '${path}' does not exist`;
|
|
|
|
}
|
2018-11-30 15:23:03 +01:00
|
|
|
if (!FileSystem.isDirectory(dir)) {
|
2018-11-29 12:42:14 +01:00
|
|
|
return `'${path}' is not a directory`;
|
|
|
|
}
|
2018-11-29 00:05:37 +01:00
|
|
|
|
2018-11-29 12:42:14 +01:00
|
|
|
const dirList = ["./", "../"];
|
2018-11-29 00:05:37 +01:00
|
|
|
const fileList = [];
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
const nodes = dir.getNodes();
|
|
|
|
Object.keys(nodes)
|
|
|
|
.sortAlphabetically()
|
|
|
|
.forEach(name => {
|
|
|
|
const node = nodes[name];
|
|
|
|
|
|
|
|
if (FileSystem.isDirectory(node)) {
|
|
|
|
dirList.push(node.toString(name));
|
|
|
|
} else if (FileSystem.isFile(node)) {
|
|
|
|
fileList.push(node.toString(name));
|
|
|
|
} else {
|
|
|
|
throw `${name} is neither a file nor a directory!`;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
2018-11-30 15:23:03 +01:00
|
|
|
});
|
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.
|
|
|
|
*
|
|
|
|
* @param path {string} the absolute or relative path to the directory to create
|
|
|
|
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
|
|
|
*/
|
|
|
|
mkdir(path) {
|
|
|
|
const parentDirName = this._parentPath(path);
|
2018-11-28 22:41:59 +01:00
|
|
|
const childDirName = this._childPath(path);
|
2018-11-28 22:23:11 +01:00
|
|
|
|
|
|
|
const parentDir = this._getFile(parentDirName);
|
2018-11-29 12:42:14 +01:00
|
|
|
if (parentDir === undefined) {
|
2018-11-28 22:23:11 +01:00
|
|
|
return `The directory '${parentDirName}' does not exist`;
|
|
|
|
}
|
2018-11-29 12:42:14 +01:00
|
|
|
if (!FileSystem.isDirectory(parentDir)) {
|
|
|
|
return `'${parentDirName}' is not a directory`;
|
|
|
|
}
|
2018-11-28 22:23:11 +01:00
|
|
|
if (parentDir[childDirName] !== undefined) {
|
|
|
|
return `The directory '${childDirName}' already exists`;
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
parentDir.addNode(childDirName, 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
|
|
|
|
*/
|
2018-11-29 09:08:24 +01:00
|
|
|
mkdirs(paths) {
|
2018-11-29 09:37:42 +01:00
|
|
|
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-11-29 13:34:46 +01:00
|
|
|
* @param source {string} the absolute or relative path to the file or directory to move
|
|
|
|
* @param destination {string} the absolute or relative path to the destination
|
|
|
|
* @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
|
|
|
*/
|
|
|
|
mv(source, destination) {
|
2018-11-29 13:34:46 +01:00
|
|
|
const sourceChildName = this._childPath(source);
|
|
|
|
const sourceChildNode = this._getFile(source);
|
|
|
|
const sourceParentPath = this._parentPath(source);
|
|
|
|
const sourceParentNode = this._getFile(sourceParentPath);
|
2018-11-29 13:18:48 +01:00
|
|
|
|
2018-11-29 13:34:46 +01:00
|
|
|
const destinationChildName = this._childPath(destination);
|
|
|
|
const destinationChildNode = this._getFile(destination);
|
2018-11-29 13:18:48 +01:00
|
|
|
const destinationParentPath = this._parentPath(destination);
|
2018-11-29 13:34:46 +01:00
|
|
|
const destinationParentNode = this._getFile(destinationParentPath);
|
2018-11-29 13:18:48 +01:00
|
|
|
|
2018-11-29 13:34:46 +01:00
|
|
|
if (sourceChildNode === undefined) {
|
|
|
|
return `The file '${source}' does not exist`;
|
|
|
|
}
|
|
|
|
if (destinationParentNode === undefined) {
|
|
|
|
return `The directory '${destinationParentPath}' does not exist`;
|
2018-11-29 13:18:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-29 13:34:46 +01:00
|
|
|
let targetNode;
|
|
|
|
let targetName;
|
|
|
|
if (destinationChildNode === undefined) {
|
|
|
|
targetNode = destinationParentNode;
|
|
|
|
targetName = destinationChildName;
|
2018-11-29 13:18:48 +01:00
|
|
|
} else {
|
2018-11-29 13:34:46 +01:00
|
|
|
targetNode = destinationChildNode;
|
|
|
|
targetName = sourceChildName;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (targetNode.getNode(targetName) !== undefined) {
|
|
|
|
return `The file '${targetName}' already exists`;
|
2018-11-29 13:18:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-29 13:34:46 +01:00
|
|
|
sourceParentNode.removeNode(sourceChildNode);
|
|
|
|
targetNode.addNode(sourceChildNode);
|
|
|
|
sourceChildNode.name = targetName;
|
|
|
|
|
2018-11-29 13:18:48 +01:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
/**
|
|
|
|
* Resets navigation in the file system.
|
|
|
|
*/
|
|
|
|
reset() {
|
2018-11-28 22:41:59 +01:00
|
|
|
this.pwd = "/";
|
2018-11-28 22:23:11 +01:00
|
|
|
this.files = this._root;
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
/**
|
|
|
|
* Removes a file from the file system.
|
|
|
|
*
|
|
|
|
* @param path {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
|
|
|
|
*/
|
2018-11-29 13:50:36 +01:00
|
|
|
rm(path, force = false, recursive = false, noPreserveRoot = false) {
|
|
|
|
const absolutePath = this._normalisePath(path);
|
|
|
|
const parentPath = this._parentPath(path);
|
|
|
|
const childPath = this._childPath(path);
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2018-11-29 13:50:36 +01:00
|
|
|
const parentNode = this._getFile(parentPath);
|
|
|
|
if (parentNode === undefined) {
|
2018-11-29 09:37:42 +01:00
|
|
|
return force
|
|
|
|
? ""
|
2018-11-29 13:50:36 +01:00
|
|
|
: `The directory '${parentPath}' does not exist`;
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
2018-11-29 13:50:36 +01:00
|
|
|
if (!FileSystem.isDirectory(parentNode)) {
|
2018-11-29 12:42:14 +01:00
|
|
|
return force
|
|
|
|
? ""
|
2018-11-29 13:50:36 +01:00
|
|
|
: `'${parentPath}' is not a directory`;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 13:50:36 +01:00
|
|
|
const childNode = parentNode.getNode(childPath);
|
|
|
|
if (childNode === undefined) {
|
2018-11-29 12:42:14 +01:00
|
|
|
return force
|
|
|
|
? ""
|
2018-11-29 13:50:36 +01:00
|
|
|
: `The file '${childPath}' does not exist`;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 13:50:36 +01:00
|
|
|
if (recursive) {
|
|
|
|
if (absolutePath === "/") {
|
|
|
|
if (noPreserveRoot) {
|
2018-11-30 15:23:03 +01:00
|
|
|
this._root = new Directory();
|
2018-11-29 13:50:36 +01:00
|
|
|
} else {
|
|
|
|
return "'/' cannot be removed";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
parentNode.removeNode(childNode);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!(childNode instanceof File)) {
|
|
|
|
return force
|
|
|
|
? ""
|
|
|
|
: `'${childPath}' is not a file`;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
2018-11-29 13:50:36 +01:00
|
|
|
rms(paths, force = false, recursive = false, noPreserveRoot = false) {
|
2018-11-29 09:37:42 +01:00
|
|
|
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.
|
|
|
|
*
|
|
|
|
* @param path {string} the absolute or relative path to the directory to be removed
|
|
|
|
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
|
|
|
*/
|
2018-11-29 13:38:00 +01:00
|
|
|
rmdir(path) {
|
2018-11-29 00:21:35 +01:00
|
|
|
if (this._normalisePath(path) === "/") {
|
2018-11-29 13:38:00 +01:00
|
|
|
if (this._root.getNodeCount() > 0) {
|
2018-11-29 00:21:35 +01:00
|
|
|
return `The directory is not empty.`;
|
|
|
|
} else {
|
2018-11-29 12:42:14 +01:00
|
|
|
return "";
|
2018-11-29 00:21:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
const parentDirName = this._parentPath(path);
|
2018-11-28 22:41:59 +01:00
|
|
|
const childDirName = this._childPath(path);
|
2018-11-28 22:23:11 +01:00
|
|
|
|
|
|
|
const parentDir = this._getFile(parentDirName);
|
2018-11-29 12:42:14 +01:00
|
|
|
if (parentDir === undefined) {
|
2018-11-29 13:38:00 +01:00
|
|
|
return `The directory '${parentDirName}' does not exist`;
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
2018-11-29 12:42:14 +01:00
|
|
|
if (!FileSystem.isDirectory(parentDir)) {
|
2018-11-29 13:38:00 +01:00
|
|
|
return `'${parentDirName}' is not a directory`;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
2018-11-28 22:23:11 +01:00
|
|
|
|
2018-11-29 12:42:14 +01:00
|
|
|
const childDir = parentDir.getNode(childDirName);
|
|
|
|
if (childDir === undefined) {
|
2018-11-29 13:38:00 +01:00
|
|
|
return `The directory '${childDirName}' does not exist`;
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
2018-11-29 12:42:14 +01:00
|
|
|
if (!FileSystem.isDirectory(childDir)) {
|
2018-11-29 13:38:00 +01:00
|
|
|
return `'${childDirName}' is not a directory`;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
2018-11-29 13:38:00 +01:00
|
|
|
if (childDir.getNodeCount() > 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
|
|
|
|
*/
|
2018-11-29 13:38:00 +01:00
|
|
|
rmdirs(paths) {
|
2018-11-29 09:37:42 +01:00
|
|
|
return this._executeForEach(paths, path => {
|
2018-11-29 13:38:00 +01:00
|
|
|
return this.rmdir(path);
|
2018-11-29 09:37:42 +01:00
|
|
|
});
|
2018-11-29 09:08:24 +01:00
|
|
|
}
|
2018-11-28 22:23:11 +01:00
|
|
|
}
|
2018-11-29 00:05:37 +01:00
|
|
|
|
|
|
|
|
2018-11-29 12:42:14 +01:00
|
|
|
class Node {
|
|
|
|
copy() {
|
|
|
|
throw "Cannot execute abstract method!";
|
|
|
|
}
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
toString(name) {
|
2018-11-29 12:42:14 +01:00
|
|
|
throw "Cannot execute abstract method!";
|
|
|
|
}
|
|
|
|
|
|
|
|
visit(fun, pre, post) {
|
|
|
|
throw "Cannot execute abstract method!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Directory extends Node {
|
2018-11-30 15:23:03 +01:00
|
|
|
constructor(nodes = {}) {
|
|
|
|
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
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
Object.keys(this._nodes).forEach(name => {
|
|
|
|
this._nodes[name]._parent = this
|
2018-11-29 12:42:14 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getNodes() {
|
2018-11-30 15:23:03 +01:00
|
|
|
return Object.assign({}, this._nodes);
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
getNodeCount() {
|
2018-11-30 15:23:03 +01:00
|
|
|
return Object.keys(this._nodes).length;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
getNode(name) {
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
addNode(name, node) {
|
2018-11-29 13:34:46 +01:00
|
|
|
if (node instanceof Directory) {
|
|
|
|
node._parent = this;
|
|
|
|
}
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
this._nodes[name] = node;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
removeNode(nodeOrName) {
|
|
|
|
if (nodeOrName instanceof Node) {
|
|
|
|
const name = Object.keys(this._nodes).find(key => this._nodes[key] === nodeOrName);
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
copy() {
|
2018-11-30 15:23:03 +01:00
|
|
|
const copy = new Directory(Object.assign({}, this._nodes));
|
|
|
|
copy._parent = undefined;
|
|
|
|
return copy;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
toString(name) {
|
|
|
|
return `${name}/`;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
visit(fun, pre = emptyFunction, post = emptyFunction) {
|
|
|
|
pre(this);
|
|
|
|
|
|
|
|
fun(this);
|
2018-11-30 15:23:03 +01:00
|
|
|
Object.keys(this._nodes).forEach(name => {
|
|
|
|
this._nodes[name].visit(fun, pre, post);
|
|
|
|
});
|
2018-11-29 12:42:14 +01:00
|
|
|
|
|
|
|
post(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class File extends Node {
|
2018-11-30 15:23:03 +01:00
|
|
|
constructor() {
|
|
|
|
super();
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
copy() {
|
2018-11-30 15:23:03 +01:00
|
|
|
return new File();
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
toString(name) {
|
|
|
|
return name;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
visit(fun, pre = emptyFunction, post = emptyFunction) {
|
|
|
|
pre(this);
|
|
|
|
fun(this);
|
|
|
|
post(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class LinkFile extends File {
|
2018-11-30 15:23:03 +01:00
|
|
|
constructor(url) {
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
copy() {
|
2018-11-30 15:23:03 +01:00
|
|
|
return new LinkFile(this.url);
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
|
2018-11-30 15:23:03 +01:00
|
|
|
toString(name) {
|
|
|
|
return `<a href="${this.url}">${name}</a>`;
|
2018-11-29 12:42:14 +01:00
|
|
|
}
|
|
|
|
}
|