forked from tools/josh
Perform simple conversion to TypeScript
This commit is contained in:
parent
d805d0fbe5
commit
c6b59daf42
|
@ -0,0 +1,2 @@
|
|||
# Output
|
||||
build/*
|
|
@ -27,9 +27,10 @@
|
|||
</div>
|
||||
|
||||
|
||||
<script src="js/shared.js"></script>
|
||||
<script src="js/terminal.js"></script>
|
||||
<script src="js/commands.js"></script>
|
||||
<script src="js/fs.js"></script>
|
||||
<script type="module" src="build/extensions.js"></script>
|
||||
<script type="module" src="build/shared.js"></script>
|
||||
<script type="module" src="build/terminal.js"></script>
|
||||
<script type="module" src="build/commands.js"></script>
|
||||
<script type="module" src="build/fs.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
class Commands {
|
||||
import "./extensions.js"
|
||||
import {FileSystem, UrlFile} from "./fs.js"
|
||||
import {terminal} from "./terminal.js";
|
||||
|
||||
|
||||
export class Commands {
|
||||
private _terminal: any;
|
||||
private _fs: any;
|
||||
private _list: any;
|
||||
|
||||
|
||||
constructor(terminal, fileSystem) {
|
||||
this._terminal = terminal;
|
||||
this._fs = fileSystem;
|
||||
|
@ -278,6 +288,11 @@ class Commands {
|
|||
}
|
||||
|
||||
class InputArgs {
|
||||
private _command: any;
|
||||
private _options: any;
|
||||
private _args: any;
|
||||
|
||||
|
||||
constructor(input) {
|
||||
const inputParts = (input.match(/("[^"]+"|[^"\s]+)/g) || [])
|
||||
.map(it => it.replace(/^"/, "").replace(/"$/, ""));
|
||||
|
@ -342,7 +357,7 @@ class InputArgs {
|
|||
return this._command;
|
||||
}
|
||||
|
||||
getOption(key, def) {
|
||||
getOption(key, def = undefined) {
|
||||
return (def === undefined)
|
||||
? this._options[key]
|
||||
: this._options[key] || def;
|
|
@ -0,0 +1,7 @@
|
|||
interface String {
|
||||
trimLines(): string;
|
||||
}
|
||||
|
||||
interface Array<T> {
|
||||
sortAlphabetically(transform: (element: T) => string);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
interface String {
|
||||
trimLines(): string;
|
||||
|
||||
replaceAll(regex: RegExp, replacement: string): string;
|
||||
}
|
||||
|
||||
String.prototype.trimLines = function (): string {
|
||||
return this.split("\n").map(it => it.trim()).join("\n");
|
||||
};
|
||||
|
||||
String.prototype.replaceAll = function (regex, replacement) {
|
||||
let string = this;
|
||||
|
||||
while (regex.test(string))
|
||||
string = string.replace(regex, replacement);
|
||||
|
||||
return "" + string;
|
||||
};
|
||||
|
||||
|
||||
interface Array<T> {
|
||||
sortAlphabetically(transform: (element: T) => string);
|
||||
}
|
||||
|
||||
Array.prototype.sortAlphabetically = function (transform = (x) => x) {
|
||||
return this.sort((a, b) => {
|
||||
const aName = transform(a).toLowerCase();
|
||||
const bName = transform(b).toLowerCase();
|
||||
|
||||
if (aName < bName)
|
||||
return -1;
|
||||
else if (aName > bName)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
});
|
||||
};
|
|
@ -1,4 +1,13 @@
|
|||
class FileSystem {
|
||||
import {emptyFunction} from "./shared.js";
|
||||
import {relToAbs} from "./terminal.js";
|
||||
|
||||
|
||||
export class FileSystem {
|
||||
pwd: string;
|
||||
private _root: Directory;
|
||||
private files: Directory;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.pwd = "/";
|
||||
this._root = new Directory({
|
||||
|
@ -123,7 +132,7 @@ class FileSystem {
|
|||
if (tailNode !== undefined)
|
||||
return "";
|
||||
|
||||
headNode.addNode(path.tail, new File(path.tail));
|
||||
headNode.addNode(path.tail, new File());
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -203,7 +212,7 @@ class FileSystem {
|
|||
|
||||
const nodes = dir.getNodes();
|
||||
Object.keys(nodes)
|
||||
.sortAlphabetically()
|
||||
.sortAlphabetically((x) => x)
|
||||
.forEach(name => {
|
||||
const node = nodes[name];
|
||||
|
||||
|
@ -225,7 +234,7 @@ class FileSystem {
|
|||
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
||||
*/
|
||||
mkdir(pathString) {
|
||||
const path = new Path(pathString);
|
||||
const path = new Path(pathString, undefined);
|
||||
|
||||
const headNode = this._getFile(path.head);
|
||||
if (headNode === undefined)
|
||||
|
@ -261,11 +270,11 @@ class FileSystem {
|
|||
* @returns {string} an empty string if the move was successful, or a message explaining what went wrong
|
||||
*/
|
||||
mv(sourceString, destinationString) {
|
||||
const sourcePath = new Path(sourceString);
|
||||
const sourcePath = new Path(sourceString, undefined);
|
||||
const sourceHeadNode = this._getFile(sourcePath.head);
|
||||
const sourceTailNode = this._getFile(sourcePath.path);
|
||||
|
||||
const destinationPath = new Path(destinationString);
|
||||
const destinationPath = new Path(destinationString, undefined);
|
||||
const destinationHeadNode = this._getFile(destinationPath.head);
|
||||
const destinationTailNode = this._getFile(destinationPath.path);
|
||||
|
||||
|
@ -288,7 +297,7 @@ class FileSystem {
|
|||
return `The file '${targetName}' already exists`;
|
||||
|
||||
sourceHeadNode.removeNode(sourceTailNode);
|
||||
targetNode.addNode(sourceTailNode);
|
||||
targetNode.addNode(sourceTailNode, undefined);
|
||||
sourceTailNode.name = targetName;
|
||||
|
||||
return "";
|
||||
|
@ -312,7 +321,7 @@ class FileSystem {
|
|||
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
||||
*/
|
||||
rm(pathString, force = false, recursive = false, noPreserveRoot = false) {
|
||||
const path = new Path(pathString);
|
||||
const path = new Path(pathString, undefined);
|
||||
|
||||
const parentNode = this._getFile(path.head);
|
||||
if (parentNode === undefined)
|
||||
|
@ -371,7 +380,7 @@ class FileSystem {
|
|||
* @returns {string} an empty string if the removal was successful, or a message explaining what went wrong
|
||||
*/
|
||||
rmdir(pathString) {
|
||||
const path = new Path(pathString);
|
||||
const path = new Path(pathString, undefined);
|
||||
|
||||
if (path.path === "/") {
|
||||
if (this._root.getNodeCount() > 0)
|
||||
|
@ -412,7 +421,13 @@ class FileSystem {
|
|||
}
|
||||
|
||||
|
||||
class Path {
|
||||
export class Path {
|
||||
private _path: string;
|
||||
private _parts: string[];
|
||||
private _head: string;
|
||||
private _tail: string;
|
||||
|
||||
|
||||
constructor(currentPath, relativePath) {
|
||||
let path;
|
||||
if (relativePath === undefined)
|
||||
|
@ -453,7 +468,7 @@ class Path {
|
|||
}
|
||||
}
|
||||
|
||||
class Node {
|
||||
export class Node {
|
||||
copy() {
|
||||
throw "Cannot execute abstract method!";
|
||||
}
|
||||
|
@ -467,7 +482,12 @@ class Node {
|
|||
}
|
||||
}
|
||||
|
||||
class Directory extends Node {
|
||||
export class Directory extends Node {
|
||||
private _parent: this;
|
||||
private _nodes: {};
|
||||
name: string;
|
||||
|
||||
|
||||
constructor(nodes = {}) {
|
||||
super();
|
||||
|
||||
|
@ -527,7 +547,7 @@ class Directory extends Node {
|
|||
return `<a href="#" class="dirLink" onclick="run('cd ${relToAbs(name)}/');run('ls');">${name}/</a>`;
|
||||
}
|
||||
|
||||
visit(fun, pre = emptyFunction, post = emptyFunction) {
|
||||
visit(fun, pre: (dir: Directory) => void = emptyFunction, post: (dir: Directory) => void = emptyFunction) {
|
||||
pre(this);
|
||||
|
||||
fun(this);
|
||||
|
@ -539,7 +559,7 @@ class Directory extends Node {
|
|||
}
|
||||
}
|
||||
|
||||
class File extends Node {
|
||||
export class File extends Node {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
@ -553,14 +573,17 @@ class File extends Node {
|
|||
return name;
|
||||
}
|
||||
|
||||
visit(fun, pre = emptyFunction, post = emptyFunction) {
|
||||
visit(fun, pre: (dir: File) => void = emptyFunction, post: (dir: File) => void = emptyFunction) {
|
||||
pre(this);
|
||||
fun(this);
|
||||
post(this);
|
||||
}
|
||||
}
|
||||
|
||||
class UrlFile extends File {
|
||||
export class UrlFile extends File {
|
||||
url: any;
|
||||
|
||||
|
||||
constructor(url) {
|
||||
super();
|
||||
|
66
js/shared.js
66
js/shared.js
|
@ -1,66 +0,0 @@
|
|||
const asciiHeader = ` ________ _______ _ _
|
||||
| ____\\ \\ / / __ \\ | | | |
|
||||
| |__ \\ \\ /\\ / /| | | | ___| | _| | _____ _ __
|
||||
| __| \\ \\/ \\/ / | | | |/ _ \\ |/ / |/ / _ \\ '__|
|
||||
| | \\ /\\ / | |__| | __/ <| < __/ |
|
||||
|_| \\/ \\/ |_____/ \\___|_|\\_\\_|\\_\\___|_| `;
|
||||
|
||||
const asciiHeaderHtml = `<span class="wideScreenOnly">${asciiHeader}</span><span class="smallScreenOnly"><b><u>FWDekker</u></b></span>`;
|
||||
|
||||
const emptyFunction = () => {};
|
||||
|
||||
const identityFunction = (x) => x;
|
||||
|
||||
|
||||
Array.prototype.sortAlphabetically = function(transform = identityFunction) {
|
||||
return this.sort((a, b) => {
|
||||
const aName = transform(a).toLowerCase();
|
||||
const bName = transform(b).toLowerCase();
|
||||
|
||||
if (aName < bName)
|
||||
return -1;
|
||||
else if (aName > bName)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
String.prototype.replaceAll = function (regex, replacement) {
|
||||
let string = this;
|
||||
|
||||
while (regex.test(string))
|
||||
string = string.replace(regex, replacement);
|
||||
|
||||
return "" + string;
|
||||
};
|
||||
|
||||
String.prototype.trimLines = function () {
|
||||
return this.split("\n").map(it => it.trim()).join("\n");
|
||||
};
|
||||
|
||||
|
||||
function addOnLoad(fun) {
|
||||
const oldOnLoad = window.onload || (() => {
|
||||
});
|
||||
|
||||
window.onload = (() => {
|
||||
oldOnLoad();
|
||||
fun();
|
||||
});
|
||||
}
|
||||
|
||||
function moveCaretToEndOf(element) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
range.collapse(false);
|
||||
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
function q(query) {
|
||||
return document.querySelector(query);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
export const asciiHeader = ` ________ _______ _ _
|
||||
| ____\\ \\ / / __ \\ | | | |
|
||||
| |__ \\ \\ /\\ / /| | | | ___| | _| | _____ _ __
|
||||
| __| \\ \\/ \\/ / | | | |/ _ \\ |/ / |/ / _ \\ '__|
|
||||
| | \\ /\\ / | |__| | __/ <| < __/ |
|
||||
|_| \\/ \\/ |_____/ \\___|_|\\_\\_|\\_\\___|_| `;
|
||||
|
||||
export const asciiHeaderHtml = `<span class="wideScreenOnly">${asciiHeader}</span><span class="smallScreenOnly"><b><u>FWDekker</u></b></span>`;
|
||||
|
||||
export const emptyFunction = () => {};
|
||||
|
||||
|
||||
export function addOnLoad(fun: () => void) {
|
||||
const oldOnLoad = window.onload || (() => {
|
||||
});
|
||||
|
||||
window.onload = (() => {
|
||||
// @ts-ignore TODO Find out how to resolve this
|
||||
oldOnLoad();
|
||||
fun();
|
||||
});
|
||||
}
|
||||
|
||||
export function moveCaretToEndOf(element: Node) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
range.collapse(false);
|
||||
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
export function q(query: string) {
|
||||
return document.querySelector(query);
|
||||
}
|
|
@ -1,4 +1,20 @@
|
|||
class Terminal {
|
||||
import {addOnLoad, asciiHeaderHtml, moveCaretToEndOf, q} from "./shared.js";
|
||||
import {FileSystem} from "./fs.js";
|
||||
import {Commands} from "./commands.js";
|
||||
|
||||
|
||||
export class Terminal {
|
||||
private _terminal: any;
|
||||
private _input: any;
|
||||
private _output: any;
|
||||
private _prefixDiv: any;
|
||||
private _user: string;
|
||||
private _loggedIn: boolean;
|
||||
private _inputHistory: InputHistory;
|
||||
private _fs:FileSystem;
|
||||
private _commands: Commands;
|
||||
|
||||
|
||||
constructor(terminal, input, output, prefixDiv) {
|
||||
this._terminal = terminal;
|
||||
this._input = input;
|
||||
|
@ -172,15 +188,20 @@ class Terminal {
|
|||
}
|
||||
|
||||
class InputHistory {
|
||||
private _history: string[];
|
||||
private _index: number;
|
||||
|
||||
|
||||
constructor() {
|
||||
this._history = [];
|
||||
this._index = -1;
|
||||
}
|
||||
|
||||
|
||||
addEntry(entry) {
|
||||
addEntry(entry: string) {
|
||||
if (entry.trim() !== "")
|
||||
this._history.unshift(entry);
|
||||
|
||||
this._index = -1;
|
||||
}
|
||||
|
||||
|
@ -214,7 +235,7 @@ class InputHistory {
|
|||
}
|
||||
|
||||
|
||||
let terminal;
|
||||
export let terminal;
|
||||
|
||||
addOnLoad(() => {
|
||||
terminal = new Terminal(
|
||||
|
@ -227,11 +248,10 @@ addOnLoad(() => {
|
|||
terminal.processInput("ls");
|
||||
});
|
||||
|
||||
|
||||
function run(command) {
|
||||
export function run(command: string) {
|
||||
terminal.processInput(command);
|
||||
}
|
||||
|
||||
function relToAbs(filename) {
|
||||
export function relToAbs(filename) {
|
||||
return terminal._fs.pwd + filename;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"target": "es2019",
|
||||
"sourceMap": true,
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue