summaryrefslogtreecommitdiff
path: root/MistyCore/shell.js
diff options
context:
space:
mode:
Diffstat (limited to 'MistyCore/shell.js')
-rw-r--r--MistyCore/shell.js709
1 files changed, 709 insertions, 0 deletions
diff --git a/MistyCore/shell.js b/MistyCore/shell.js
new file mode 100644
index 0000000..7dc3c86
--- /dev/null
+++ b/MistyCore/shell.js
@@ -0,0 +1,709 @@
+// noinspection JSIncompatibleTypesComparison
+
+const fs = require('fs');
+const os = require('os');
+const chalk = require('chalk');
+const YAML = require('yaml');
+const path = require('path');
+
+global.systemRoot = path.resolve(__dirname + "/..");
+console.clear();
+require('./banner')();
+
+global.daemons = fs.readdirSync(systemRoot + "/LaunchDaemons").map(i => i.substring(0, i.length - 4));
+global.log = require('./log');
+log("Shell", "Starting up shell...");
+global.internalCommands = fs.readdirSync(__dirname + "/../Components");
+global.internalCommandsMetadata = internalCommands.map((i) => {
+ return YAML.parse(fs.readFileSync(__dirname + "/../Components/" + i + "/metadata.yml").toString());
+});
+
+global.commands = [...internalCommands];
+global.commandsMetadata = [...internalCommandsMetadata];
+
+let stdin = process.stdin;
+global.currentInput = "";
+global.inputPosition = 0;
+stdin.setRawMode(true);
+stdin.setEncoding('utf8');
+
+function prompt() {
+ return chalk.cyan(os.hostname() + ":") + chalk.blue(process.cwd() + "> ");
+}
+
+function promptNoColor() {
+ return os.hostname() + ":" + process.cwd() + "> ";
+}
+
+function main() {
+ global.currentInput = "";
+ global.inputPosition = 0;
+
+ stdin.resume();
+ updateInput();
+}
+
+stdin.on('data', function (key) {
+ if (key === '\u0003') {
+ main();
+ return;
+ }
+
+ if (key === '\u0004') {
+ process.exit();
+ return;
+ }
+
+ if (key === "\r" || key === "\n") {
+ replaceWithAliasIfPossible(true, () => {
+ if (key === "\r") {
+ process.stdout.write("\n");
+ } else {
+ process.stdout.write("\r");
+ }
+
+ stdin.pause();
+ let parts = currentInput.split(" ");
+ let command = parts[0];
+
+ if (commands.includes(command)) {
+ if (internalCommands.includes(command)) {
+ let metadata = commandsMetadata[commands.indexOf(command)];
+ parts[0] = __dirname + "/../Components/" + command + "/index.js";
+ let arguments = {
+ _finals: []
+ };
+ let willAddParameters = true;
+
+ for (let index in parts) {
+ if (index > 0) {
+ let part = parts[index];
+ let originalPart = part;
+
+ if (originalPart === "--") {
+ willAddParameters = false;
+ continue;
+ }
+
+ if (part.startsWith("--") && willAddParameters) part = part.substring(2);
+ if (part.startsWith("-") && willAddParameters) part = part.substring(1);
+
+ if (part.includes("=") && willAddParameters) {
+ let value = part.substring(part.split("=")[0].length + 1);
+
+ if (!isNaN(parseFloat(value))) {
+ value = parseFloat(value);
+ } else if (!isNaN(parseInt(value))) {
+ value = parseInt(value);
+ }
+
+ arguments[part.split("=")[0].toLowerCase()] = value;
+ } else {
+ if (originalPart.startsWith("-") && willAddParameters) {
+ arguments[part.toLowerCase()] = true;
+ } else {
+ if (part.trim() !== "") arguments["_finals"].push(part);
+ }
+ }
+ }
+ }
+
+ log("Shell", "Invoking command: " + command);
+
+ try {
+ if (metadata.internal) {
+ require(__dirname + "/../Components/" + command + "/index.js")(arguments);
+ } else {
+ stdin.setRawMode(false);
+ require('child_process').execFileSync(process.argv[0], [ parts[0], JSON.stringify(arguments) ], { stdio: "inherit" });
+ stdin.setRawMode(true);
+ }
+ } catch (e) {
+ stdin.setRawMode(true);
+ log("Shell", "Error while running: " + currentInput + "\n" + e.stack);
+ console.log("\n" + chalk.bgRed.white("<!>") + " " + chalk.red("Error while running command") + "\n");
+ main();
+ return;
+ }
+ }
+ } else {
+ log("Shell", "Attempted to run command " + command + " but it does not exist.");
+ console.log(chalk.bgRed.white("<!>") + " " + chalk.red("Command not found: ") + command);
+ }
+
+ main();
+ });
+ } else if (key === "\b" || key === "\x7F") {
+ if (global.currentInput !== "" && inputPosition > 0) {
+ global.currentInput = global.currentInput.substring(0, inputPosition - 1) + global.currentInput.substring(inputPosition);
+ inputPosition--;
+ updateInput();
+ }
+ } else if (key === " ") {
+ replaceWithAliasIfPossible(false);
+ } else if (key.startsWith("\x1B") || key === "\t") {
+ if (key === "\x1B[D") { // Left
+ cursorLeft();
+ } else if (key === "\x1B[C" || key === "\t") { // Right
+ cursorRight();
+ } else if (key === "\x1B[B") { // Down
+ } else if (key === "\x1B[A") { // Up
+ }
+ } else {
+ global.currentInput = global.currentInput.substring(0, inputPosition) + key + global.currentInput.substring(inputPosition);
+ inputPosition++;
+ updateInput();
+ }
+});
+
+function replaceWithAliasIfPossible(isEnter, callback) {
+ let text = currentInput;
+ if (text.trim() === "") return "";
+
+ let parts = text.split(" ");
+ let command = parts[0];
+
+ if (!commands.includes(command)) {
+ let inputLowerCase = command.toLowerCase();
+ let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0];
+
+ if (!match) {
+ let alias = null;
+
+ for (let selectedCommand of commands) {
+ let metadata = commandsMetadata[commands.indexOf(selectedCommand)];
+ if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(command.toLowerCase())) {
+ alias = selectedCommand;
+ }
+ }
+
+ if (alias) {
+ parts[0] = alias;
+ global.currentInput = parts.join(" ") + " ";
+ global.inputPosition = currentInput.length;
+ global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition);
+ global.currentInput = global.currentInput.trim() + " ";
+ global.inputPosition = currentInput.length;
+ if (isEnter) {
+ process.stdout.clearLine(0, () => {
+ process.stdout.cursorTo(0, () => {
+ process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => {
+ if (callback) callback();
+ });
+ });
+ });
+ } else {
+ updateInput();
+ }
+ return;
+ }
+ }
+
+ let alias = null;
+
+ for (let selectedCommand of commands) {
+ if (selectedCommand.toLowerCase() === command.toLowerCase()) {
+ alias = selectedCommand;
+ }
+ }
+
+ if (alias) {
+ parts[0] = alias;
+ global.currentInput = parts.join(" ") + " ";
+ global.inputPosition = currentInput.length;
+ global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition);
+ global.currentInput = global.currentInput.trim() + " ";
+ global.inputPosition = currentInput.length;
+ if (isEnter) {
+ process.stdout.clearLine(0, () => {
+ process.stdout.cursorTo(0, () => {
+ process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => {
+ if (callback) callback();
+ });
+ });
+ });
+ } else {
+ updateInput();
+ }
+ return;
+ }
+ } else if (commands.includes(command)) {
+ let metadata = commandsMetadata[commands.indexOf(command)];
+
+ let arguments = {
+ _finals: []
+ };
+ let willAddParameters = true;
+
+ for (let index in parts) {
+ if (index > 0) {
+ let part = parts[index];
+ let originalPart = part;
+
+ if (originalPart === "--") {
+ willAddParameters = false;
+ continue;
+ }
+
+ if (part.startsWith("--") && willAddParameters) part = part.substring(2);
+ if (part.startsWith("-") && willAddParameters) part = part.substring(1);
+
+ if (part.includes("=") && willAddParameters) {
+ let value = part.substring(part.split("=")[0].length + 1);
+
+ if (!isNaN(parseFloat(value))) {
+ value = parseFloat(value);
+ } else if (!isNaN(parseInt(value))) {
+ value = parseInt(value);
+ }
+
+ arguments[part.split("=")[0].toLowerCase()] = value;
+ } else {
+ if (originalPart.startsWith("-") && willAddParameters) {
+ arguments[part.toLowerCase()] = true;
+ } else {
+ if (part.trim() !== "") arguments["_finals"].push(part);
+ }
+ }
+ }
+ }
+
+ if (arguments["_finals"].length > 0) {
+ let lastFinal = arguments["_finals"][arguments["_finals"].length - 1];
+
+ if (metadata && metadata['manual']['final'] && metadata['manual']['final']['command']) {
+ command = lastFinal;
+ let inputLowerCase = command.toLowerCase();
+ let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0];
+
+ if (!match) {
+ let alias = null;
+
+ for (let selectedCommand of commands) {
+ let metadata = commandsMetadata[commands.indexOf(selectedCommand)];
+ if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(command.toLowerCase())) {
+ alias = selectedCommand;
+ }
+ }
+
+ if (alias) {
+ parts[parts.indexOf(lastFinal)] = alias;
+ global.currentInput = parts.join(" ") + " ";
+ global.inputPosition = currentInput.length;
+ global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition);
+ global.currentInput = global.currentInput.trim() + " ";
+ global.inputPosition = currentInput.length;
+ if (isEnter) {
+ process.stdout.clearLine(0, () => {
+ process.stdout.cursorTo(0, () => {
+ process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => {
+ if (callback) callback();
+ });
+ });
+ });
+ } else {
+ updateInput();
+ }
+ return;
+ }
+ }
+
+ let alias = null;
+
+ for (let selectedCommand of commands) {
+ if (selectedCommand.toLowerCase() === command.toLowerCase()) {
+ alias = selectedCommand;
+ }
+ }
+
+ if (alias) {
+ parts[parts.indexOf(lastFinal)] = alias;
+ global.currentInput = parts.join(" ") + " ";
+ global.inputPosition = currentInput.length;
+ global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition);
+ global.currentInput = global.currentInput.trim() + " ";
+ global.inputPosition = currentInput.length;
+ if (isEnter) {
+ process.stdout.clearLine(0, () => {
+ process.stdout.cursorTo(0, () => {
+ process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => {
+ if (callback) callback();
+ });
+ });
+ });
+ } else {
+ updateInput();
+ }
+ return;
+ }
+ } else if (metadata && metadata['manual']['final'] && metadata['manual']['final']['daemon']) {
+ command = lastFinal;
+
+ let alias = null;
+
+ for (let selectedCommand of commands) {
+ if (selectedCommand.toLowerCase() === command.toLowerCase()) {
+ alias = selectedCommand;
+ }
+ }
+
+ if (alias) {
+ parts[parts.indexOf(lastFinal)] = alias;
+ global.currentInput = parts.join(" ") + " ";
+ global.inputPosition = currentInput.length;
+ global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition);
+ global.currentInput = global.currentInput.trim() + " ";
+ global.inputPosition = currentInput.length;
+ if (isEnter) {
+ process.stdout.clearLine(0, () => {
+ process.stdout.cursorTo(0, () => {
+ process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => {
+ if (callback) callback();
+ });
+ });
+ });
+ } else {
+ updateInput();
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ if (!isEnter) {
+ global.currentInput = global.currentInput.substring(0, inputPosition) + " " + global.currentInput.substring(inputPosition);
+ inputPosition++;
+ updateInput();
+ } else {
+ process.stdout.clearLine(0, () => {
+ process.stdout.cursorTo(0, () => {
+ process.stdout.write(prompt() + syntaxHighlighting(currentInput), () => {
+ if (callback) callback();
+ });
+ });
+ });
+ }
+}
+
+function cursorLeft() {
+ if (inputPosition > 0) {
+ inputPosition--;
+ }
+
+ updateInput();
+}
+
+function cursorRight() {
+ if (inputPosition < currentInput.length) {
+ inputPosition++;
+ } else {
+ autoCompleteIfPossible();
+ }
+
+ updateInput();
+}
+
+function syntaxHighlighting(text) {
+ let parts = text.split(" ");
+ let coloredParts = [];
+ let willColorParameters = true;
+ let cmdMetadata = null;
+
+ for (let _index in parts) {
+ let index = parseInt(_index);
+ let part = parts[index];
+
+ if (index === 0) {
+ cmdMetadata = commandsMetadata[commands.indexOf(part)];
+
+ if (commands.includes(part)) {
+ coloredParts.push(chalk.green(part));
+ } else {
+ if (parts.length === 1) {
+ let alias = null;
+
+ for (let selectedCommand of commands) {
+ let metadata = commandsMetadata[commands.indexOf(selectedCommand)];
+ if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(part.toLowerCase())) {
+ alias = selectedCommand;
+ }
+ }
+
+ if (alias) {
+ coloredParts.push(chalk.green.bgGray(part));
+ } else {
+ coloredParts.push(chalk.yellow(part));
+ }
+ } else {
+ coloredParts.push(chalk.red(part));
+ }
+ }
+ } else if (part === "--" && willColorParameters) {
+ coloredParts.push(chalk.cyan(part));
+ willColorParameters = false;
+ } else if (part.startsWith("-") && willColorParameters) {
+ if (part.includes("=")) {
+ coloredParts.push(chalk.magentaBright.underline(part.split("=")[0]) + chalk.magenta.bgGray("=") + chalk.magenta(part.substring(part.split("=")[0].length + 1)));
+ } else {
+ coloredParts.push(chalk.magenta(part));
+ }
+ } else if (cmdMetadata && cmdMetadata['manual']['final'] && cmdMetadata['manual']['final']['command']) {
+ if (commands.map(i => i.toLowerCase()).includes(part.toLowerCase())) {
+ coloredParts.push(chalk.yellow(part));
+ } else {
+ let alias = null;
+
+ for (let selectedCommand of commands) {
+ let metadata = commandsMetadata[commands.indexOf(selectedCommand)];
+ if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(part.toLowerCase())) {
+ alias = selectedCommand;
+ }
+ }
+
+ if (alias) {
+ coloredParts.push(chalk.yellow(part));
+ } else {
+ coloredParts.push(chalk.red(part));
+ }
+ }
+ } else if (cmdMetadata && cmdMetadata['manual']['final'] && cmdMetadata['manual']['final']['daemon']) {
+ if (daemons.map(i => i.toLowerCase()).includes(part.toLowerCase())) {
+ coloredParts.push(chalk.yellow(part));
+ } else {
+ coloredParts.push(chalk.red(part));
+ }
+ } else {
+ coloredParts.push(part);
+ }
+ }
+
+ return coloredParts.join(" ");
+}
+
+function autoComplete(text) {
+ if (text.trim() === "") return "";
+
+ let parts = text.split(" ");
+ let command = parts[0];
+
+ if (commands.includes(command)) {
+ if (parts.filter(i => i.trim() !== "").length > 1) {
+ let metadata = commandsMetadata[commands.indexOf(command)];
+
+ let arguments = {
+ _finals: []
+ };
+ let willAddParameters = true;
+
+ for (let index in parts) {
+ if (index > 0) {
+ let part = parts[index];
+ let originalPart = part;
+
+ if (originalPart === "--") {
+ willAddParameters = false;
+ continue;
+ }
+
+ if (part.startsWith("--") && willAddParameters) part = part.substring(2);
+ if (part.startsWith("-") && willAddParameters) part = part.substring(1);
+
+ if (part.includes("=") && willAddParameters) {
+ let value = part.substring(part.split("=")[0].length + 1);
+
+ if (!isNaN(parseFloat(value))) {
+ value = parseFloat(value);
+ } else if (!isNaN(parseInt(value))) {
+ value = parseInt(value);
+ }
+
+ arguments[part.split("=")[0].toLowerCase()] = value;
+ } else {
+ if (originalPart.startsWith("-") && willAddParameters) {
+ arguments[part.toLowerCase()] = true;
+ } else {
+ if (part.trim() !== "") arguments["_finals"].push(part);
+ }
+ }
+ }
+ }
+
+ if (arguments["_finals"].length > 0) {
+ let lastFinal = arguments["_finals"][arguments["_finals"].length - 1];
+
+ if (metadata && metadata['manual']['final'] && metadata['manual']['final']['command']) {
+ command = lastFinal;
+
+ if (commands.includes(command)) {
+ let metadata = commandsMetadata[commands.indexOf(command)];
+ return " " + chalk.gray("- " + (metadata.description ?? "<no description>"));
+ } else {
+ let inputLowerCase = command.toLowerCase();
+ let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0];
+
+ if (match) {
+ return chalk.gray(match.substring(command.length));
+ } else {
+ let alias = null;
+
+ for (let selectedCommand of commands) {
+ let metadata = commandsMetadata[commands.indexOf(selectedCommand)];
+ if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(command.toLowerCase())) {
+ alias = selectedCommand;
+ }
+ }
+
+ if (alias) {
+ let metadata = commandsMetadata[commands.indexOf(alias)];
+ return " " + chalk.gray(" (-> " + alias + ") - " + (metadata.description ?? "<no description>"));
+ } else {
+ return "";
+ }
+ }
+ }
+ } else if (metadata && metadata['manual']['final'] && metadata['manual']['final']['daemon']) {
+ command = lastFinal;
+
+ if (daemons.includes(command)) {
+ return "";
+ } else {
+ let inputLowerCase = command.toLowerCase();
+ let match = daemons.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0];
+
+ if (match) {
+ return chalk.gray(match.substring(command.length));
+ } else {
+ return "";
+ }
+ }
+ } else {
+ return "";
+ }
+ } else {
+ return "";
+ }
+ } else {
+ let metadata = commandsMetadata[commands.indexOf(command)];
+ return " " + chalk.gray("- " + (metadata.description ?? "<no description>"));
+ }
+ } else {
+ let inputLowerCase = command.toLowerCase();
+ let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0];
+
+ if (match) {
+ return chalk.gray(match.substring(command.length));
+ } else {
+ let alias = null;
+
+ for (let selectedCommand of commands) {
+ let metadata = commandsMetadata[commands.indexOf(selectedCommand)];
+ if (metadata['aliases'] && metadata['aliases'].map(i => i.toLowerCase()).includes(command.toLowerCase())) {
+ alias = selectedCommand;
+ }
+ }
+
+ if (alias) {
+ let metadata = commandsMetadata[commands.indexOf(alias)];
+ return " " + chalk.gray(" (-> " + alias + ") - " + (metadata.description ?? "<no description>"));
+ } else {
+ return "";
+ }
+ }
+ }
+}
+
+function autoCompleteIfPossible() {
+ let text = currentInput;
+ if (text.trim() === "") return;
+
+ let parts = text.split(" ");
+ let command = parts[0];
+
+ if (!commands.includes(command)) {
+ let inputLowerCase = command.toLowerCase();
+ let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0];
+
+ if (match) {
+ global.currentInput += match.substring(command.length);
+ global.currentInput = global.currentInput.trim() + " ";
+ global.inputPosition = currentInput.length - 1;
+ replaceWithAliasIfPossible();
+ }
+ } else if (commands.includes(command)) {
+ let metadata = commandsMetadata[commands.indexOf(command)];
+
+ let arguments = {
+ _finals: []
+ };
+ let willAddParameters = true;
+
+ for (let index in parts) {
+ if (index > 0) {
+ let part = parts[index];
+ let originalPart = part;
+
+ if (originalPart === "--") {
+ willAddParameters = false;
+ continue;
+ }
+
+ if (part.startsWith("--") && willAddParameters) part = part.substring(2);
+ if (part.startsWith("-") && willAddParameters) part = part.substring(1);
+
+ if (part.includes("=") && willAddParameters) {
+ let value = part.substring(part.split("=")[0].length + 1);
+
+ if (!isNaN(parseFloat(value))) {
+ value = parseFloat(value);
+ } else if (!isNaN(parseInt(value))) {
+ value = parseInt(value);
+ }
+
+ arguments[part.split("=")[0].toLowerCase()] = value;
+ } else {
+ if (originalPart.startsWith("-") && willAddParameters) {
+ arguments[part.toLowerCase()] = true;
+ } else {
+ if (part.trim() !== "") arguments["_finals"].push(part);
+ }
+ }
+ }
+ }
+
+ if (arguments["_finals"].length > 0) {
+ let lastFinal = arguments["_finals"][arguments["_finals"].length - 1];
+
+ if (metadata && metadata['manual']['final'] && metadata['manual']['final']['command']) {
+ command = lastFinal;
+ if (!commands.includes(command)) {
+ let inputLowerCase = command.toLowerCase();
+ let match = commands.filter(i => i.toLowerCase().startsWith(inputLowerCase))[0];
+
+ if (match) {
+ global.currentInput += match.substring(command.length);
+ global.currentInput = global.currentInput.trim() + " ";
+ global.inputPosition = currentInput.length;
+ replaceWithAliasIfPossible();
+ }
+ }
+ }
+ }
+ }
+}
+
+function updateInput() {
+ if (global.inputPosition < global.currentInput.length) {
+ global.currentInput = global.currentInput.trim();
+ }
+
+ process.stdout.clearLine(0, () => {
+ process.stdout.cursorTo(0);
+ process.stdout.write(prompt() + syntaxHighlighting(currentInput) + autoComplete(currentInput), () => {
+ process.stdout.cursorTo(promptNoColor().length + inputPosition);
+ });
+ });
+}
+
+main();
+log("Shell", "Shell successfully loaded."); \ No newline at end of file