Added lolcat command

This commit is contained in:
Prozilla 2024-05-04 13:09:25 +02:00
parent 1e1bdf5089
commit 915be2cb99
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
17 changed files with 146 additions and 60 deletions

View file

@ -8,6 +8,7 @@ import { ANSI, HOSTNAME, USERNAME } from "../../../config/apps/terminal.config.j
import CommandsManager from "../../../features/apps/terminal/commands.js";
import { removeFromArray } from "../../../features/_utils/array.utils.js";
import Stream from "../../../features/apps/terminal/stream.js";
import { formatError } from "../../../features/apps/terminal/_utils/terminal.utils.js";
/**
* @param {import("../../windows/WindowView.jsx").windowProps} props
@ -60,7 +61,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
});
};
const connectStream = (stream) => {
const connectStream = (stream, pipes) => {
setStream(stream);
setStreamFocused(false);
@ -73,8 +74,23 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
let lastOutput = null;
stream.on(Stream.EVENT_NAMES.NEW, (text) => {
lastOutput = text;
setStreamOutput(text);
let output = text;
pipes.forEach((pipe) => {
if (output instanceof Stream)
return;
// Output from the previous command gets added as an argument for the next command
output = handleInput(output ? `${pipe} ${output}` : pipe);
});
if (output instanceof Stream) {
stream.stop();
promptOutput(ANSI.fg.red + "Stream failed");
return;
}
lastOutput = output;
setStreamOutput(output);
});
stream.on(Stream.EVENT_NAMES.STOP, () => {
@ -92,6 +108,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
const handleInput = (value) => {
const rawInputValueStart = value.indexOf(" ") + 1;
const rawInputValue = rawInputValueStart <= 0 ? "" : value.substr(rawInputValueStart);
const timestamp = Date.now();
value = value.trim();
if (value === "")
@ -109,7 +126,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
const command = CommandsManager.find(commandName);
if (!command)
return CommandsManager.formatError(commandName, "Command not found");
return formatError(commandName, "Command not found");
// Get options
const options = [];
@ -145,10 +162,10 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
// Check usage
if (command.requireArgs && args.length === 0)
return CommandsManager.formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 argument`);
return formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 argument`);
if (command.requireOptions && options.length === 0)
return CommandsManager.formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 option`);
return formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 option`);
// Execute command
let response = null;
@ -165,17 +182,18 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
rawInputValue,
options,
exit,
inputs
inputs,
timestamp
});
if (response == null)
return CommandsManager.formatError(commandName, "Command failed");
return formatError(commandName, "Command failed");
if (!response.blank)
return response;
} catch (error) {
console.error(error);
return CommandsManager.formatError(commandName, "Command failed");
return formatError(commandName, "Command failed");
}
};
@ -190,17 +208,21 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
setHistoryIndex(0);
// Piping is used to chain commands
const segments = value.split(" | ");
const pipes = value.split(" | ");
let output = null;
segments.forEach((segment) => {
pipes.forEach((pipe) => {
if (output instanceof Stream)
return;
// Output from the previous command gets added as an argument for the next command
output = handleInput(output ? `${segment} ${output}` : segment);
output = handleInput(output ? `${pipe} ${output}` : pipe);
removeFromArray(pipe, pipes);
});
if (output) {
if (output instanceof Stream) {
connectStream(output);
connectStream(output, pipes);
} else {
promptOutput(`${output}\n`);
}

View file

@ -93,31 +93,31 @@
cursor: text;
}
.ansi-black-fg { color: var(--dark-grey-e); }
.ansi-red-fg { color: var(--red-b); }
.ansi-green-fg { color: var(--green-b); }
.ansi-yellow-fg { color: var(--yellow-b); }
.ansi-blue-fg { color: var(--blue-b); }
.ansi-magenta-fg { color: var(--purple-b); }
.ansi-cyan-fg { color: var(--cyan-b); }
.ansi-white-fg { color: var(--grey-a); }
.ansi-black-fg { color: var(--dark-grey-c); }
.ansi-red-fg { color: var(--red-a); }
.ansi-green-fg { color: var(--green-a); }
.ansi-yellow-fg { color: var(--yellow-a); }
.ansi-blue-fg { color: var(--blue-a); }
.ansi-magenta-fg { color: var(--purple-a); }
.ansi-cyan-fg { color: var(--cyan-a); }
.ansi-white-fg { color: var(--foreground-color-a); }
.ansi-bright-black-fg { color: var(--dark-grey-d); }
.ansi-bright-black-fg { color: var(--dark-grey-c); }
.ansi-bright-red-fg { color: var(--red-a); }
.ansi-bright-green-fg { color: var(--green-a); }
.ansi-bright-yellow-fg { color: var(--yellow-a); }
.ansi-bright-blue-fg { color: var(--blue-a); }
.ansi-bright-magenta-fg { color: var(--purple-a); }
.ansi-bright-cyan-fg { color: var(--cyan-a); }
.ansi-bright-white-fg { color: var(--white-a); }
.ansi-bright-white-fg { color: var(--foreground-color-a); }
.ansi-black-bg { background-color: var(--dark-grey-d); }
.ansi-black-bg { background-color: var(--dark-grey-c); }
.ansi-red-bg { background-color: var(--red-a); }
.ansi-green-bg { background-color: var(--green-a); }
.ansi-yellow-bg { background-color: var(--yellow-a); }
.ansi-blue-bg { background-color: var(--blue-a); }
.ansi-magenta-bg { background-color: var(--purple-a); }
.ansi-cyan-bg { background-color: var(--cyan-a); }
.ansi-white-bg { background-color: var(--white-a); }
.ansi-white-bg { background-color: var(--foreground-color-a); }
.ansi-dim { opacity: 0.65; }

View file

@ -0,0 +1,19 @@
import { ANSI } from "../../../../config/apps/terminal.config.js";
/**
* @param {string} commandName
* @param {string} error
* @returns {string}
*/
export function formatError(commandName, error) {
return `${ANSI.fg.red}${commandName}: ${error}${ANSI.reset}`;
}
/**
* @param {string} string
* @returns {string}
*/
export function removeAnsi(string) {
// eslint-disable-next-line no-control-regex
return string.replace(/\u001b\[([0-9]+)m/gm, "");
}

View file

@ -1,4 +1,3 @@
import { ANSI } from "../../../config/apps/terminal.config.js";
import Command from "./command.js";
let commands = [];
@ -55,13 +54,4 @@ export default class CommandsManager {
loadCommands();
CommandsManager.COMMANDS = commands;
}
/**
* @param {string} commandName
* @param {string} error
* @returns {string}
*/
static formatError(commandName, error) {
return `${ANSI.fg.red}${commandName}: ${error}${ANSI.reset}`;
}
}

View file

@ -1,6 +1,6 @@
import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js";
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
export const cat = new Command()
.setRequireArgs(true)
@ -14,7 +14,7 @@ export const cat = new Command()
const file = currentDirectory.findFile(name, extension);
if (!file)
return CommandsManager.formatError(this.name, `${args[0]}: No such file`);
return formatError(this.name, `${args[0]}: No such file`);
if (file.content) {
if (!options.includes("e")) {

View file

@ -1,5 +1,5 @@
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
export const cd = new Command()
.setManual({
@ -12,7 +12,7 @@ export const cd = new Command()
const destination = currentDirectory.navigate(path);
if (!destination)
return CommandsManager.formatError(this.name, `${args[0]}: No such file or directory`);
return formatError(this.name, `${args[0]}: No such file or directory`);
setCurrentDirectory(destination);
return { blank: true };

View file

@ -1,4 +1,5 @@
import { ANSI } from "../../../../config/apps/terminal.config.js";
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
@ -18,10 +19,10 @@ export const help = new Command()
const command = CommandsManager.find(commandName);
if (!command)
return CommandsManager.formatError(this.name, `${commandName}: Command not found`);
return formatError(this.name, `${commandName}: Command not found`);
if (!command.manual?.purpose)
return CommandsManager.formatError(this.name, `${commandName}: No manual found`);
return formatError(this.name, `${commandName}: No manual found`);
return command.manual.purpose;
});

View file

@ -0,0 +1,52 @@
import { ANSI } from "../../../../config/apps/terminal.config.js";
import { removeAnsi } from "../_utils/terminal.utils.js";
import Command from "../command.js";
const COLUMN_WIDTH = 5;
const ROW_OFFSET = 2;
const RAINBOW = [
ANSI.fg.red,
ANSI.fg.yellow,
ANSI.fg.green,
ANSI.fg.cyan,
ANSI.fg.blue,
ANSI.fg.magenta,
];
export const lolcat = new Command()
.setManual({
purpose: "Display text with a rainbow effect"
})
.setExecute(function(args, { rawInputValue, timestamp }) {
let rows = removeAnsi(rawInputValue).split("\n");
const offset = timestamp / 100;
rows = rows.map((row, index) => {
const columns = [];
const rowIndex = index + offset;
const rowOffset = COLUMN_WIDTH - ((ROW_OFFSET * rowIndex) % COLUMN_WIDTH);
let rainbowIndex = Math.floor(rowIndex / (COLUMN_WIDTH / ROW_OFFSET));
const addColumn = (start, end) => {
const column = row.substring(start, end);
const color = RAINBOW[rainbowIndex++ % RAINBOW.length];
columns.push(color + column);
};
if (rowOffset > 0)
addColumn(0, rowOffset);
for (let i = rowOffset; i < row.length; i += COLUMN_WIDTH + 1) {
addColumn(i, i + COLUMN_WIDTH + 1);
}
if (row.length === 0)
return "";
return columns.join("");
});
return rows.join("\n");
});

View file

@ -1,6 +1,6 @@
import { ANSI } from "../../../../config/apps/terminal.config.js";
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
export const ls = new Command()
.setManual({
@ -16,7 +16,7 @@ export const ls = new Command()
}
if (!directory)
return CommandsManager.formatError(this.name, `Cannot access '${args[0]}': No such file or directory`);
return formatError(this.name, `Cannot access '${args[0]}': No such file or directory`);
const folderNames = directory.subFolders.map((folder) => `${ANSI.fg.blue}${folder.id}${ANSI.reset}`);
const fileNames = directory.files.map((file) => file.id);

View file

@ -1,9 +1,9 @@
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
export const make = new Command()
.setRequireArgs(true)
.setExecute(function(args) {
if (args[0] === "love")
return CommandsManager.formatError(this.name, "*** No rule to make target 'love'. Stop.");
return formatError(this.name, "*** No rule to make target 'love'. Stop.");
});

View file

@ -1,4 +1,5 @@
import { ANSI } from "../../../../config/apps/terminal.config.js";
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
@ -33,12 +34,12 @@ export const man = new Command()
const command = CommandsManager.find(commandName);
if (!command)
return `${this.name}: ${commandName}: Command not found`;
return formatError(this.name, `${commandName}: Command not found`);
const manual = command.manual;
if (!manual)
return `${this.name}: ${commandName}: No manual found`;
return formatError(this.name, `${commandName}: No manual found`);
const formatText = (text) => {
const lines = text.split("\n").map((line) => " ".repeat(MARGIN) + line);

View file

@ -1,6 +1,6 @@
import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js";
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
export const rm = new Command()
.setRequireArgs(true)
@ -12,7 +12,7 @@ export const rm = new Command()
const file = currentDirectory.findFile(name, extension);
if (!file)
return CommandsManager.formatError(this.name, `${args[0]}: No such file`);
return formatError(this.name, `${args[0]}: No such file`);
file.delete();
return { blank: true };

View file

@ -1,5 +1,5 @@
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
export const rmdir = new Command()
.setRequireArgs(true)
@ -11,7 +11,7 @@ export const rmdir = new Command()
const folder = currentDirectory.findSubFolder(name);
if (!folder)
return CommandsManager.formatError(this.name, `${args[0]}: No such directory`);
return formatError(this.name, `${args[0]}: No such directory`);
folder.delete();
return { blank: true };

View file

@ -1,5 +1,5 @@
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
import Stream from "../stream.js";
const ANIMATION_SPEED = 1.25;
@ -193,7 +193,7 @@ export const sl = new Command()
wagonCount = parseInt(inputs.w);
if (!wagonCount || wagonCount < 0) {
return CommandsManager.formatError(this.name, "Please specify a valid amount of wagons");
return formatError(this.name, "Please specify a valid amount of wagons");
}
}

View file

@ -1,6 +1,6 @@
import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js";
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
export const touch = new Command()
.setRequireArgs(true)
@ -12,7 +12,7 @@ export const touch = new Command()
})
.setExecute(function(args, { currentDirectory }) {
if (args[0] === "girls\\" && args[1] === "boo**")
return CommandsManager.formatError(this.name, "Cannot touch 'girls boo**': Permission denied");
return formatError(this.name, "Cannot touch 'girls boo**': Permission denied");
const { name, extension } = VirtualFile.convertId(args[0]);

View file

@ -1,4 +1,5 @@
import { ANSI } from "../../../../config/apps/terminal.config.js";
import { formatError } from "../_utils/terminal.utils.js";
import Command from "../command.js";
import CommandsManager from "../commands.js";
@ -12,10 +13,10 @@ export const whatis = new Command()
const command = CommandsManager.find(commandName);
if (!command)
return CommandsManager.formatError(this.name, `${commandName}: Command not found`);
return formatError(this.name, `${commandName}: Command not found`);
if (!command.manual?.purpose)
return CommandsManager.formatError(this.name, `${commandName}: No information found`);
return formatError(this.name, `${commandName}: No information found`);
return `${commandName} - ${ANSI.fg.green}${command.manual.purpose}`;
});

View file

@ -1,5 +1,5 @@
:root {
--white-a: #fff;
--white-a: hsl(210, 54%, 95%);
--pink-a: #ff9ff3;
--pink-b: #f368e0;
--yellow-a: #feca57;
@ -16,8 +16,8 @@
--blue-b: #2e86de;
--purple-a: #5f27cd;
--purple-b: #341f97;
--grey-a: #c8d6e5;
--grey-b: #8395a7;
--grey-a: hsl(210, 36%, 85%);
--grey-b: hsl(210, 18%, 60%);
--dark-grey-a: hsl(211, 14%, 40%);
--dark-grey-b: hsl(var(--background-color-a-hsl));
--dark-grey-c: hsl(var(--background-color-c-hsl));