-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshell.js
More file actions
176 lines (163 loc) · 5.19 KB
/
shell.js
File metadata and controls
176 lines (163 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
const terminal = document.getElementById("terminal");
const loginInput = document.getElementById("login");
let username = "";
let cwd = ["/", "home", "user"];
let loggedIn = false;
let installedPackages = {};
let fs = {
"/": {
type: "dir",
contents: {
"bin": {}, "etc": {}, "usr": {}, "home": {
"user": {}
}
}
}
};
function formatPrompt() {
const path = "/" + cwd.slice(2).join("/") || "";
return `${username}@freebsd:${path || "~"}% `;
}
function resolvePath(path) {
if (!path) return cwd;
if (path.startsWith("/")) return path.split("/").filter(Boolean);
const base = [...cwd];
const parts = path.split("/");
for (const part of parts) {
if (part === "..") base.pop();
else if (part !== ".") base.push(part);
}
return base;
}
function getDirAndName(pathArray) {
const name = pathArray.pop();
const parent = pathArray.reduce((dir, key) => dir.contents?.[key], fs["/"]);
return [parent, name];
}
function getCurrentDir() {
return cwd.reduce((dir, key) => dir.contents?.[key], fs["/"]);
}
function print(text = "") {
terminal.innerHTML += text + "\n";
}
function inputLine() {
const line = document.createElement("div");
line.innerHTML = `<span>${formatPrompt()}</span><span id="input-line"><input id="shell-input" autocomplete="off" /></span>`;
terminal.appendChild(line);
const shellInput = document.getElementById("shell-input");
shellInput.focus();
shellInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const command = shellInput.value;
line.innerHTML = `<span>${formatPrompt()}${command}</span>`;
handleCommand(command.trim());
}
});
}
function login(usernameInput) {
username = usernameInput;
cwd = ["/", "home", "user"];
terminal.innerHTML += `Welcome to FreeBSD!\n\n`;
loggedIn = true;
inputLine();
}
loginInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const name = loginInput.value.trim();
terminal.innerHTML = terminal.innerHTML.replace('<span id="login-prompt"><input id="login" autofocus autocomplete="off" /></span>', name);
login(name);
}
});
function handleCommand(cmdLine) {
const [cmd, ...args] = cmdLine.split(/\s+/);
const out = [];
const dir = getCurrentDir();
switch (cmd) {
case "pwd":
out.push("/" + cwd.slice(1).join("/"));
break;
case "ls":
const target = args[0] ? resolvePath(args[0]) : cwd;
let d = fs["/"];
for (const part of target) d = d.contents?.[part];
if (!d || d.type === "file") out.push("ls: Not a directory");
else out.push(Object.keys(d.contents).join(" "));
break;
case "cd":
const newPath = resolvePath(args[0] || "/");
let test = fs["/"];
for (const part of newPath) test = test.contents?.[part];
if (!test || test.type === "file") out.push("cd: No such directory");
else cwd = ["/", ...newPath];
break;
case "mkdir":
if (!args[0]) out.push("mkdir: missing operand");
else {
const pathArr = resolvePath(args[0]);
const [parent, name] = getDirAndName([...pathArr]);
if (!parent || !parent.contents) out.push("mkdir: cannot create directory");
else parent.contents[name] = { type: "dir", contents: {} };
}
break;
case "touch":
if (!args[0]) out.push("touch: missing file operand");
else {
const pathArr = resolvePath(args[0]);
const [parent, name] = getDirAndName([...pathArr]);
if (!parent || !parent.contents) out.push("touch: cannot create file");
else parent.contents[name] = { type: "file", content: "" };
}
break;
case "rm":
if (!args[0]) out.push("rm: missing operand");
else {
const pathArr = resolvePath(args[0]);
const [parent, name] = getDirAndName([...pathArr]);
if (!parent || !parent.contents || !parent.contents[name]) {
out.push("rm: cannot remove: No such file or directory");
} else {
delete parent.contents[name];
}
}
break;
case "cat":
if (!args[0]) out.push("cat: missing file operand");
else {
const pathArr = resolvePath(args[0]);
const file = pathArr.reduce((dir, key) => dir.contents?.[key], fs["/"]);
if (!file || file.type !== "file") out.push("cat: file not found or not a file");
else out.push(file.content || "");
}
break;
case "pkg":
if (args[0] === "install" && args[1] === "nano") {
installedPackages["nano"] = true;
out.push("nano installed successfully.");
} else {
out.push("pkg: unknown command or package");
}
break;
case "nano":
if (!installedPackages["nano"]) {
out.push("nano: command not found (hint: try 'pkg install nano')");
} else {
out.push("nano (simulated): [editor opens]");
}
break;
case "echo":
out.push(args.join(" "));
break;
case "clear":
terminal.innerHTML = "";
return;
case "help":
out.push("Supported: ls, cd, mkdir, touch, rm, cat, echo, pkg install nano, nano, clear");
break;
case "":
break;
default:
out.push(`${cmd}: command not found`);
}
out.forEach(print);
inputLine();
}