forked from tools/josh
1
0
Fork 0

Classify FileSystem

This commit is contained in:
Florine W. Dekker 2018-11-28 22:23:11 +01:00
parent ea80b978c7
commit f8fd158135
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
4 changed files with 273 additions and 273 deletions

View File

@ -1,14 +1,17 @@
class Commands { class Commands {
constructor() { constructor(terminal, fileSystem) {
this._terminal = terminal;
this._fileSystem = fileSystem;
this._list = { this._list = {
clear: { clear: {
fun: Commands.clear, fun: this.clear,
summary: `clear terminal output`, summary: `clear terminal output`,
usage: `clear`, usage: `clear`,
desc: `Clears all previous terminal output.`.trimLines() desc: `Clears all previous terminal output.`.trimLines()
}, },
cd: { cd: {
fun: Commands.cd, fun: this.cd,
summary: `change directory`, summary: `change directory`,
usage: `cd [DIRECTORY]`, usage: `cd [DIRECTORY]`,
desc: "" + desc: "" +
@ -44,25 +47,25 @@ class Commands {
If [DIRECTORY] is empty, the files and directories in the current working directory are shown.`.trimLines() If [DIRECTORY] is empty, the files and directories in the current working directory are shown.`.trimLines()
}, },
mkdir: { mkdir: {
fun: Commands.mkdir, fun: this.mkdir,
summary: `create directory`, summary: `create directory`,
usage: `mkdir [DIRECTORY]`, usage: `mkdir [DIRECTORY]`,
desc: `Creates a directory with name [DIRECTORY].`.trimLines() desc: `Creates a directory with name [DIRECTORY].`.trimLines()
}, },
pwd: { pwd: {
fun: Commands.pwd, fun: this.pwd,
summary: `print working directory`, summary: `print working directory`,
usage: `pwd`, usage: `pwd`,
desc: `Displays the current working directory.`.trimLines() desc: `Displays the current working directory.`.trimLines()
}, },
rm: { rm: {
fun: Commands.rm, fun: this.rm,
summary: `remove file`, summary: `remove file`,
usage: `rm [-f | --force] FILE`, usage: `rm [-f | --force] FILE`,
desc: `Removes FILE if it is a file.`.trimLines() desc: `Removes FILE if it is a file.`.trimLines()
}, },
rmdir: { rmdir: {
fun: Commands.rmdir, fun: this.rmdir,
summary: `remove directory`, summary: `remove directory`,
usage: `rmdir [-f | --force] DIR`, usage: `rmdir [-f | --force] DIR`,
desc: `Removes DIR if it is a directory.`.trimLines() desc: `Removes DIR if it is a directory.`.trimLines()
@ -85,12 +88,12 @@ class Commands {
} }
static cd(args) { cd(args) {
return fs.cd(args[1]); return this._fileSystem.cd(args[1]);
} }
static clear() { clear() {
Commands.clear(); this._terminal.clear();
return ``; return ``;
} }
@ -135,7 +138,7 @@ class Commands {
} }
ls(args) { ls(args) {
const files = fs.ls(args[1]); const files = this._fileSystem.ls(args[1]);
if (files === undefined) { if (files === undefined) {
return `The directory '${args[1]}' does not exist`; return `The directory '${args[1]}' does not exist`;
} }
@ -156,19 +159,19 @@ class Commands {
return dirList.concat(fileList).join(`\n`); return dirList.concat(fileList).join(`\n`);
} }
static mkdir(args) { mkdir(args) {
return fs.mkdir(args[1]); return this._fileSystem.mkdir(args[1]);
} }
static pwd() { pwd() {
return fs.pwd; return this._fileSystem.pwd;
} }
static rm(args) { rm(args) {
return fs.rm(args[1]); return this._fileSystem.rm(args[1]);
} }
static rmdir(args) { rmdir(args) {
let path; let path;
let force; let force;
if (args[1] === `-f` || args[1] === `--force`) { if (args[1] === `-f` || args[1] === `--force`) {
@ -179,9 +182,6 @@ class Commands {
force = false; force = false;
} }
return fs.rmdir(path, force); return this._fileSystem.rmdir(path, force);
} }
} }
const commands = new Commands();

468
js/fs.js
View File

@ -1,245 +1,241 @@
const fs = {}; class FileSystem {
constructor() {
this._root = {
personal: {
steam: `<a href="https://steamcommunity.com/id/Waflix">steam</a>`,
nukapedia: `<a href="http://fallout.wikia.com/wiki/User:FDekker">nukapedia</a>`
},
projects: {
minor: {
dice: `<a href="https://fwdekker.com/dice">dice_probabilities</a>`
},
randomness: `<a href="https://github.com/FWDekker/intellij-randomness">randomness</a>`,
schaapi: `<a href="http://cafejojo.org/schaapi">schaapi</a>`
},
social: {
github: `<a href="https://github.com/FWDekker/">github</a>`,
stackoverflow: `<a href="https://stackoverflow.com/u/3307872">stackoverflow</a>`,
linkedin: `<a href="https://www.linkedin.com/in/fwdekker/">linkedin</a>`
},
"resume.pdf": `<a href="https://fwdekker.com/resume.pdf">resume.pdf</a>`
};
this.pwd = `/`;
// Layout const visited = [];
fs.pwd = `/`; const queue = [this._root];
fs.root = {
personal: {
steam: `<a href="https://steamcommunity.com/id/Waflix">steam</a>`,
nukapedia: `<a href="http://fallout.wikia.com/wiki/User:FDekker">nukapedia</a>`
},
projects: {
minor: {
dice: `<a href="https://fwdekker.com/dice">dice_probabilities</a>`
},
randomness: `<a href="https://github.com/FWDekker/intellij-randomness">randomness</a>`,
schaapi: `<a href="http://cafejojo.org/schaapi">schaapi</a>`
},
social: {
github: `<a href="https://github.com/FWDekker/">github</a>`,
stackoverflow: `<a href="https://stackoverflow.com/u/3307872">stackoverflow</a>`,
linkedin: `<a href="https://www.linkedin.com/in/fwdekker/">linkedin</a>`
},
"resume.pdf": `<a href="https://fwdekker.com/resume.pdf">resume.pdf</a>`
};
fs.files = fs.root;
this._root[`.`] = this._root;
// Functions while (queue.length !== 0) {
fs._absolutePath = function (path) { const next = queue.pop();
if (path.startsWith(`/`)) { if (visited.indexOf(next) >= 0) {
return path;
} else {
return `${fs.pwd}/${path}`;
}
};
fs._filePath = function (path) {
return fs._normalisePath(path).split(`/`).slice(0, -1).slice(-1).join(`/`);
};
fs._getFile = function (path) {
const abPath = fs._normalisePath(path);
let file = fs.root;
abPath.split(`/`).forEach(part => {
if (part === ``) {
return;
}
if (file === undefined) {
return;
}
file = file[part];
});
return file;
};
fs._normalisePath = function (path) {
return fs._sanitisePath(fs._absolutePath(path));
};
fs._parentPath = function (path) {
return fs._normalisePath(path).split(`/`).slice(0, -1).join(`/`);
};
fs._sanitisePath = function (path) {
const selfRegex = /\/\.\//; // Match `./`
const upRegex = /(\/+)([^./]+)(\/+)(\.\.)(\/+)/; // Match `/directory/../
const doubleRegex = /\/{2,}/; // Match `///`
return path
.replaceAll(selfRegex, `/`)
.replaceAll(upRegex, `/`)
.replaceAll(doubleRegex, `/`)
.toString();
};
/**
* 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
*/
fs.isDirectory = function (file) {
return (file !== undefined && typeof file !== `string`);
};
/**
* 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
*/
fs.isFile = function (file) {
return (file !== undefined && typeof file === `string`);
};
/**
* 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
*/
fs.cd = function (path) {
if (path === undefined) {
return ``;
}
const file = fs._getFile(path);
if (file === undefined || !fs.isDirectory(file)) {
return `The directory '${path}' does not exist`;
}
fs.pwd = fs._normalisePath(path);
fs.files = file;
return ``;
};
/**
* 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
*/
fs.ls = function (path) {
path = (path || fs.pwd);
return fs._getFile(path);
};
/**
* 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
*/
fs.mkdir = function (path) {
const parentDirName = fs._parentPath(path);
const childDirName = fs._filePath(path);
const parentDir = fs._getFile(parentDirName);
if (!fs.isDirectory(parentDir)) {
return `The directory '${parentDirName}' does not exist`;
}
if (parentDir[childDirName] !== undefined) {
return `The directory '${childDirName}' already exists`;
}
parentDir[childDirName] = {};
parentDir[childDirName][`.`] = parentDir[childDirName];
parentDir[childDirName][`..`] = parentDir;
return ``;
};
/**
* Resets navigation in the file system.
*/
fs.reset = function () {
fs.pwd = `/`;
fs.files = fs.root;
};
/**
* Removes a file from the file system.
*
* @param path {string} the absolute or relative path to the file to be removed
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
*/
fs.rm = function (path) {
const dirName = fs._parentPath(path);
const fileName = fs._filePath(path);
const dir = fs._getFile(dirName);
if (!fs.isDirectory(dir)) {
return `The directory '${dirName}' does not exist`;
}
const file = dir[fileName];
if (!fs.isFile(file)) {
return `The file '${fileName}' does not exist`;
}
delete dir[fileName];
return ``;
};
/**
* 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
*/
fs.rmdir = function (path, force) {
force = (force || false);
const parentDirName = fs._parentPath(path);
const childDirName = fs._filePath(path);
const parentDir = fs._getFile(parentDirName);
if (!fs.isDirectory(parentDir)) {
return `The directory '${parentDirName}' does not exist`;
}
const childDir = parentDir[childDirName];
if (!fs.isDirectory(childDir)) {
return `The directory '${childDirName}' does not exist`;
}
if (!force && Object.keys(childDir).length > 2) {
return `The directory is not empty`;
}
delete parentDir[childDirName];
return ``;
};
// Init
(() => {
const visited = [];
const queue = [fs.files];
fs.files[`.`] = fs.files;
while (queue.length !== 0) {
const next = queue.pop();
if (visited.indexOf(next) >= 0) {
continue;
}
visited.push(next);
for (const key in next) {
if (key === `.` || key === `..`) {
continue; continue;
} }
next[key][`.`] = next[key]; visited.push(next);
next[key][`..`] = next; for (const key in next) {
queue.push(next[key]); if (key === `.` || key === `..` || FileSystem.isFile(next[key])) {
continue;
}
next[key][`.`] = next[key];
next[key][`..`] = next;
queue.push(next[key]);
}
} }
} }
})();
_absolutePath(path) {
if (path.startsWith(`/`)) {
return path;
} else {
return `${this.pwd}/${path}`;
}
}
_filePath(path) {
return this._normalisePath(path).split(`/`).slice(0, -1).slice(-1).join(`/`);
}
_getFile(path) {
path = this._normalisePath(path);
let file = this._root;
path.split(`/`).forEach(part => {
if (part === ``) {
return;
}
if (file === undefined) {
return;
}
file = file[part];
});
return file;
}
_normalisePath(path) {
return FileSystem._sanitisePath(this._absolutePath(path));
}
_parentPath(path) {
return this._normalisePath(path).split(`/`).slice(0, -1).join(`/`);
}
static _sanitisePath(path) {
const selfRegex = /\/\.\//; // Match `./`
const upRegex = /(\/+)([^./]+)(\/+)(\.\.)(\/+)/; // Match `/directory/../
const doubleRegex = /\/{2,}/; // Match `///`
return path
.replaceAll(selfRegex, `/`)
.replaceAll(upRegex, `/`)
.replaceAll(doubleRegex, `/`)
.toString();
}
/**
* 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) {
return (file !== undefined && typeof file !== `string`);
}
/**
* 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) {
return (file !== undefined && typeof file === `string`);
}
/**
* 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) {
return ``;
}
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;
return ``;
}
/**
* 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);
return this._getFile(path);
}
/**
* 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);
const childDirName = this._filePath(path);
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`;
}
parentDir[childDirName] = {};
parentDir[childDirName][`.`] = parentDir[childDirName];
parentDir[childDirName][`..`] = parentDir;
return ``;
}
/**
* Resets navigation in the file system.
*/
reset() {
this.pwd = `/`;
this.files = this._root;
}
/**
* Removes a file from the file system.
*
* @param path {string} the absolute or relative path to the file to be removed
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
*/
rm(path) {
const dirName = this._parentPath(path);
const fileName = this._filePath(path);
const dir = this._getFile(dirName);
if (!FileSystem.isDirectory(dir)) {
return `The directory '${dirName}' does not exist`;
}
const file = dir[fileName];
if (!FileSystem.isFile(file)) {
return `The file '${fileName}' does not exist`;
}
delete dir[fileName];
return ``;
}
/**
* 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);
const parentDirName = this._parentPath(path);
const childDirName = this._filePath(path);
const parentDir = this._getFile(parentDirName);
if (!FileSystem.isDirectory(parentDir)) {
return `The directory '${parentDirName}' does not exist`;
}
const childDir = parentDir[childDirName];
if (!FileSystem.isDirectory(childDir)) {
return `The directory '${childDirName}' does not exist`;
}
if (!force && Object.keys(childDir).length > 2) {
return `The directory is not empty`;
}
delete parentDir[childDirName];
return ``;
}
}

View File

@ -1,3 +1,11 @@
const asciiHeader = `&nbsp;________ _______ _ _
| ____\\ \\ / / __ \\ | | | |
| |__ \\ \\ /\\ / /| | | | ___| | _| | _____ _ __
| __| \\ \\/ \\/ / | | | |/ _ \\ |/ / |/ / _ \\ '__|
| | \\ /\\ / | |__| | __/ <| < __/ |
|_| \\/ \\/ |_____/ \\___|_|\\_\\_|\\_\\___|_| `;
String.prototype.replaceAll = function (regex, replacement) { String.prototype.replaceAll = function (regex, replacement) {
let string = this; let string = this;

View File

@ -1,11 +1,3 @@
const asciiHeader = `&nbsp;________ _______ _ _
| ____\\ \\ / / __ \\ | | | |
| |__ \\ \\ /\\ / /| | | | ___| | _| | _____ _ __
| __| \\ \\/ \\/ / | | | |/ _ \\ |/ / |/ / _ \\ '__|
| | \\ /\\ / | |__| | __/ <| < __/ |
|_| \\/ \\/ |_____/ \\___|_|\\_\\_|\\_\\___|_| `;
class InputHistory { class InputHistory {
constructor() { constructor() {
this._history = []; this._history = [];
@ -56,6 +48,9 @@ class Terminal {
this._prefixDiv = prefixDiv; this._prefixDiv = prefixDiv;
this._inputHistory = new InputHistory(); this._inputHistory = new InputHistory();
this._fs = new FileSystem();
this._commands = new Commands(this, this._fs);
this._terminal.addEventListener("click", this._onclick.bind(this)); this._terminal.addEventListener("click", this._onclick.bind(this));
this._terminal.addEventListener("keypress", this._onkeypress.bind(this)); this._terminal.addEventListener("keypress", this._onkeypress.bind(this));
this._input.addEventListener("keydown", this._onkeydown.bind(this)); this._input.addEventListener("keydown", this._onkeydown.bind(this));
@ -103,8 +98,8 @@ class Terminal {
`.trimLines(); `.trimLines();
} }
static generatePrefix() { generatePrefix() {
return `felix@fwdekker.com <span style="color: green;">${fs.pwd}</span>&gt; `; return `felix@fwdekker.com <span style="color: green;">${this._fs.pwd}</span>&gt; `;
} }
processInput(input) { processInput(input) {
@ -112,19 +107,19 @@ class Terminal {
this.inputText = ``; this.inputText = ``;
this.outputText += `${this.prefixText}${input}\n`; this.outputText += `${this.prefixText}${input}\n`;
const output = commands.parse(input.trim()); const output = this._commands.parse(input.trim());
if (output !== ``) { if (output !== ``) {
this.outputText += output + `\n`; this.outputText += output + `\n`;
} }
this.prefixText = Terminal.generatePrefix(); this.prefixText = this.generatePrefix();
} }
reset() { reset() {
fs.reset(); this._fs.reset();
this.outputText = Terminal.generateHeader(); this.outputText = Terminal.generateHeader();
this.prefixText = Terminal.generatePrefix(); this.prefixText = this.generatePrefix();
} }
@ -153,6 +148,7 @@ class Terminal {
} }
let terminal; let terminal;
addOnLoad(() => { addOnLoad(() => {