diff options
Diffstat (limited to 'MistyCore/shell.js')
-rw-r--r-- | MistyCore/shell.js | 709 |
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 |