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

355 lines
10 KiB
JavaScript
Raw Normal View History

2018-11-28 22:23:11 +01:00
class FileSystem {
constructor() {
this._root = {
personal: {
2018-11-29 00:05:37 +01:00
steam: {
type: "link",
link: "https://steamcommunity.com/id/Waflix"
},
nukapedia: {
type: "link",
link: "http://fallout.wikia.com/wiki/User:FDekker"
}
2018-11-28 22:23:11 +01:00
},
projects: {
minor: {
2018-11-29 00:05:37 +01:00
dice: {
type: "link",
link: "https://fwdekker.com/dice"
}
},
randomness: {
type: "link",
link: "https://github.com/FWDekker/intellij-randomness"
2018-11-28 22:23:11 +01:00
},
2018-11-29 00:05:37 +01:00
schaapi: {
type: "link",
link: "http://cafejojo.org/schaapi"
}
2018-11-28 22:23:11 +01:00
},
social: {
2018-11-29 00:05:37 +01:00
github: {
type: "link",
link: "https://github.com/FWDekker/"
},
stackoverflow: {
type: "link",
link: "https://stackoverflow.com/u/3307872"
},
linkedin: {
type: "link",
link: "https://www.linkedin.com/in/fwdekker/"
}
2018-11-28 22:23:11 +01:00
},
2018-11-29 00:05:37 +01:00
"resume.pdf": {
type: "link",
link: "https://fwdekker.com/resume.pdf"
}
2018-11-28 22:23:11 +01:00
};
2018-11-28 22:41:59 +01:00
this.pwd = "/";
2018-11-28 22:23:11 +01:00
const visited = [];
const queue = [this._root];
2018-11-28 22:41:59 +01:00
this._root["."] = this._root;
this._root[".."] = this._root;
2018-11-28 22:23:11 +01:00
while (queue.length !== 0) {
const next = queue.pop();
if (visited.indexOf(next) >= 0) {
continue;
}
2018-11-28 19:51:48 +01:00
2018-11-28 22:23:11 +01:00
visited.push(next);
for (const key in next) {
2018-11-28 22:41:59 +01:00
if (key === "." || key === ".." || FileSystem.isFile(next[key])) {
2018-11-28 22:23:11 +01:00
continue;
}
2018-11-28 19:51:48 +01:00
2018-11-28 22:41:59 +01:00
next[key]["."] = next[key];
next[key][".."] = next;
2018-11-28 22:23:11 +01:00
queue.push(next[key]);
}
2018-11-28 19:51:48 +01:00
}
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;
}
file = file[part];
});
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 file} represents a directory.
*
* @param file {Object} an object from the file system
* @returns {boolean} true iff {@code file} represents a directory
*/
static isDirectory(file) {
2018-11-29 00:05:37 +01:00
return (file !== undefined && typeof file.type !== "string");
2018-11-28 19:51:48 +01:00
}
2018-11-28 22:23:11 +01:00
/**
* Returns true iff {@code file} represents a file.
*
* @param file {Object} an object from the file system
* @returns {boolean} true iff {@code file} represents a file
*/
static isFile(file) {
2018-11-29 00:05:37 +01:00
return (file !== undefined && typeof file.type === "string");
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 || !FileSystem.isDirectory(file)) {
return `The directory '${path}' does not exist`;
}
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);
2018-11-29 00:05:37 +01:00
const files = this._getFile(path);
if (files === undefined) {
return `The directory '${path}' does not exist`;
}
const dirList = [];
const fileList = [];
Object.keys(files).sort().forEach(fileName => {
const file = files[fileName];
if (FileSystem.isFile(file)) {
fileList.push(fileToString(fileName, file));
} else if (FileSystem.isDirectory(file)) {
dirList.push(`${fileName}/`);
} else {
throw `${fileName} is neither a file nor a directory!`;
}
});
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 (!FileSystem.isDirectory(parentDir)) {
return `The directory '${parentDirName}' does not exist`;
}
if (parentDir[childDirName] !== undefined) {
return `The directory '${childDirName}' already exists`;
}
2018-11-28 19:51:48 +01:00
2018-11-28 22:23:11 +01:00
parentDir[childDirName] = {};
2018-11-28 22:41:59 +01:00
parentDir[childDirName]["."] = parentDir[childDirName];
parentDir[childDirName][".."] = parentDir;
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 (!FileSystem.isDirectory(dir)) {
2018-11-29 09:37:42 +01:00
return force
? ""
: `The directory '${dirName}' does not exist`;
2018-11-28 22:23:11 +01:00
}
2018-11-28 19:51:48 +01:00
2018-11-28 22:23:11 +01:00
const file = dir[fileName];
if (!FileSystem.isFile(file)) {
2018-11-29 09:37:42 +01:00
return force
? ""
: `The file '${fileName}' does not exist`;
2018-11-28 19:51:48 +01:00
}
2018-11-28 22:23:11 +01:00
delete dir[fileName];
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 => {
this.rm(path, force);
});
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 && Object.keys(this._root).length > 1) {
return `The directory is not empty.`;
} else {
this._root = {};
this._root["."] = this._root;
return ``;
}
}
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 (!FileSystem.isDirectory(parentDir)) {
2018-11-29 09:37:42 +01:00
return force
? ""
: `The directory '${parentDirName}' does not exist`;
2018-11-28 19:51:48 +01:00
}
2018-11-28 22:23:11 +01:00
const childDir = parentDir[childDirName];
if (!FileSystem.isDirectory(childDir)) {
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 (!force && Object.keys(childDir).length > 2) {
return `The directory is not empty`;
}
delete parentDir[childDirName];
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
2018-11-29 00:21:35 +01:00
const fileToString = function (fileName, file) {
2018-11-29 00:05:37 +01:00
switch (file.type) {
case "link":
return `<a href="${file.link}">${fileName}</a>`;
default:
return fileName;
}
};