forked from tools/josh
1
0
Fork 0
josh/js/fs.js

447 lines
12 KiB
JavaScript
Raw Normal View History

2018-11-28 22:23:11 +01:00
class FileSystem {
constructor() {
2018-11-28 22:41:59 +01:00
this.pwd = "/";
this._root = new Directory("", undefined, [
new Directory("personal", undefined, [
new LinkFile("steam", "https://steamcommunity.com/id/Waflix"),
new LinkFile("nukapedia", "http://fallout.wikia.com/wiki/User:FDekker")
]),
new Directory("projects", undefined, [
new Directory("minor", undefined, [
new LinkFile("dice", "https://fwdekker.com/dice")
]),
new LinkFile("randomness", "https://github.com/FWDekker/intellij-randomness"),
new LinkFile("schaapi", "http://cafejojo.org/schaapi")
]),
new Directory("social", undefined, [
new LinkFile("github", "https://github.com/FWDekker/"),
new LinkFile("stackoverflow", "https://stackoverflow.com/u/3307872"),
new LinkFile("linkedin", "https://www.linkedin.com/in/fwdekker/")
]),
new LinkFile("resume.pdf", "resume.pdf")
]);
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-28 22:41:59 +01:00
_childPath(path) {
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 09:37:42 +01:00
_executeForEach(inputs, fun) {
const outputs = [];
inputs.forEach(input => {
const output = fun(input);
if (output !== "") {
outputs.push(output);
}
});
return outputs.join("\n");
}
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;
}
if (FileSystem.isFile(file)) {
file = undefined;
}
2018-11-28 22:23:11 +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
_normalisePath(path) {
return FileSystem._sanitisePath(this._absolutePath(path));
2018-11-28 19:51:48 +01:00
}
2018-11-28 22:23:11 +01:00
_parentPath(path) {
const parentPath = this._normalisePath(path).split("/").slice(0, -2).join("/");
return (parentPath === "")
? "/"
: parentPath;
2018-11-28 19:51:48 +01:00
}
2018-11-28 22:23:11 +01:00
static _sanitisePath(path) {
2018-11-28 22:41:59 +01:00
const selfRegex = /\/\.\//; // Match "./"
const upRegex = /(\/+)([^./]+)(\/+)(\.\.)(\/+)/; // Match "/directory/../"
const doubleRegex = /\/{2,}/; // Match "///"
2018-11-28 22:23:11 +01:00
2018-11-28 23:22:17 +01:00
return `${path}/`
2018-11-28 22:41:59 +01:00
.replaceAll(selfRegex, "/")
.replaceAll(upRegex, "/")
.replaceAll(doubleRegex, "/")
2018-11-28 22:23:11 +01:00
.toString();
2018-11-28 19:51:48 +01:00
}
2018-11-28 22:23:11 +01:00
/**
* Returns true iff {@code node} represents a directory.
2018-11-28 22:23:11 +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
*/
static isDirectory(node) {
return node instanceof Directory;
2018-11-28 19:51:48 +01:00
}
2018-11-28 22:23:11 +01:00
/**
* Returns true iff {@code node} represents a file.
2018-11-28 22:23:11 +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
*/
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);
if (file === undefined) {
2018-11-28 22:23:11 +01:00
return `The directory '${path}' does not exist`;
}
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
/**
* 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);
const node = this._getFile(path);
if (node === undefined) {
2018-11-29 00:05:37 +01:00
return `The directory '${path}' does not exist`;
}
if (!FileSystem.isDirectory(node)) {
return `'${path}' is not a directory`;
}
2018-11-29 00:05:37 +01:00
const dirList = ["./", "../"];
2018-11-29 00:05:37 +01:00
const fileList = [];
node.getNodes()
.sortAlphabetically(node => node.name)
.forEach(node => {
if (FileSystem.isDirectory(node)) {
dirList.push(node.toString());
} else if (FileSystem.isFile(node)) {
fileList.push(node.toString());
} else {
throw `${node.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.
*
* @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);
if (parentDir === undefined) {
2018-11-28 22:23:11 +01:00
return `The directory '${parentDirName}' does not exist`;
}
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
parentDir.addNode(new Directory(childDirName, parentDir, []));
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
mkdirs(paths) {
2018-11-29 09:37:42 +01:00
return this._executeForEach(paths, this.mkdir.bind(this));
}
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-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 09:37:42 +01:00
rm(path, force) {
2018-11-28 22:23:11 +01:00
const dirName = this._parentPath(path);
2018-11-28 22:41:59 +01:00
const fileName = this._childPath(path);
2018-11-28 22:23:11 +01:00
const dir = this._getFile(dirName);
if (dir === undefined) {
2018-11-29 09:37:42 +01:00
return force
? ""
: `The directory '${dirName}' does not exist`;
2018-11-28 22:23:11 +01:00
}
if (!FileSystem.isDirectory(dir)) {
return force
? ""
: `'${dirName}' is not a directory`;
}
2018-11-28 19:51:48 +01:00
const file = dir.getNode(fileName);
if (file === undefined) {
2018-11-29 09:37:42 +01:00
return force
? ""
: `The file '${fileName}' does not exist`;
2018-11-28 19:51:48 +01:00
}
if (!FileSystem.isFile(file)) {
return force
? ""
: `'${fileName}' is not a file`;
}
2018-11-28 19:51:48 +01:00
dir.removeNode(file);
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 09:37:42 +01:00
rms(paths, force) {
return this._executeForEach(paths, path => {
return this.rm(path, force);
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
* @param force {boolean} true iff the directory should be removed regardless of whether it is empty
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
*/
rmdir(path, force) {
force = (force || false);
2018-11-29 00:21:35 +01:00
if (this._normalisePath(path) === "/") {
if (!force && this._root.getNodeCount() > 0) {
2018-11-29 00:21:35 +01:00
return `The directory is not empty.`;
} else {
this._root = new Directory("/", undefined, []);
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);
if (parentDir === undefined) {
2018-11-29 09:37:42 +01:00
return force
? ""
: `The directory '${parentDirName}' does not exist`;
2018-11-28 19:51:48 +01:00
}
if (!FileSystem.isDirectory(parentDir)) {
return force
? ""
: `'${parentDirName}' is not a directory`;
}
2018-11-28 22:23:11 +01:00
const childDir = parentDir.getNode(childDirName);
if (childDir === undefined) {
2018-11-29 09:37:42 +01:00
return force
? ""
: `The directory '${childDirName}' does not exist`;
2018-11-28 22:23:11 +01:00
}
if (!FileSystem.isDirectory(childDir)) {
return force
? ""
: `'${childDirName}' is not a directory`;
}
if (!force && childDir.getNodeCount() > 0) {
2018-11-28 22:23:11 +01:00
return `The directory is not empty`;
}
parentDir.removeNode(childDir);
2018-11-28 22:41:59 +01:00
return "";
2018-11-28 19:51:48 +01:00
}
rmdirs(paths, force) {
2018-11-29 09:37:42 +01:00
return this._executeForEach(paths, path => {
return this.rmdir(path, force);
});
}
2018-11-28 22:23:11 +01:00
}
2018-11-29 00:05:37 +01:00
class Node {
constructor(name) {
this.name = name;
}
copy() {
throw "Cannot execute abstract method!";
}
toString() {
throw "Cannot execute abstract method!";
}
visit(fun, pre, post) {
throw "Cannot execute abstract method!";
}
}
class Directory extends Node {
constructor(name, parent, nodes) {
super(name);
this._parent = (parent || this);
this._nodes = (nodes || []);
this._nodes.forEach(node => {
node._parent = this;
});
}
getNodes() {
return this._nodes.slice();
}
getNodeCount() {
return this._nodes.length;
}
getNode(name) {
switch (name) {
case ".":
return this;
case "..":
return this._parent;
default:
return this._nodes.find(it => it.name === name);
}
}
addNode(node) {
this._nodes.push(node);
}
removeNode(node) {
const index = this._nodes.indexOf(node);
if (index >= 0) {
this._nodes.splice(index, 1);
return true;
} else {
return false;
}
}
copy() {
return new Directory(this.name, this._parent, this._nodes);
}
toString() {
return `${this.name}/`;
}
visit(fun, pre = emptyFunction, post = emptyFunction) {
pre(this);
fun(this);
this._nodes.forEach(node => node.visit(fun, pre, post));
post(this);
}
}
class File extends Node {
constructor(name) {
super(name);
}
copy() {
return new File(this.name);
}
toString() {
return name;
}
visit(fun, pre = emptyFunction, post = emptyFunction) {
pre(this);
fun(this);
post(this);
}
}
class LinkFile extends File {
constructor(name, url) {
super(name);
this.url = url;
2018-11-29 00:05:37 +01:00
}
copy() {
return new LinkFile(this.name, this.url);
}
toString() {
return `<a href="${this.url}">${this.name}</a>`;
}
}