Updated terminal app
This commit is contained in:
parent
c4868a040f
commit
9718530c9f
20 changed files with 294 additions and 144 deletions
|
|
@ -6,8 +6,9 @@ import { OutputLine } from "./OutputLine.jsx";
|
||||||
import { InputLine } from "./InputLine.jsx";
|
import { InputLine } from "./InputLine.jsx";
|
||||||
import { HOSTNAME, USERNAME } from "../../../constants/applications/terminal.js";
|
import { HOSTNAME, USERNAME } from "../../../constants/applications/terminal.js";
|
||||||
import CommandsManager from "../../../features/applications/terminal/commands.js";
|
import CommandsManager from "../../../features/applications/terminal/commands.js";
|
||||||
|
import { removeFromArray } from "../../../features/utils/array.js";
|
||||||
|
|
||||||
export function Terminal({ setTitle }) {
|
export function Terminal({ setTitle, close: exit }) {
|
||||||
const [inputKey, setInputKey] = useState(0);
|
const [inputKey, setInputKey] = useState(0);
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [history, setHistory] = useState([]);
|
const [history, setHistory] = useState([]);
|
||||||
|
|
@ -43,22 +44,46 @@ export function Terminal({ setTitle }) {
|
||||||
if (value === "")
|
if (value === "")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const args = value.split(/ +/);
|
// Get arguments
|
||||||
|
const args = value.match(/(?:[^\s"]+|"[^"]*")+/g);
|
||||||
|
|
||||||
if (args[0].toLowerCase() === "sudo" && args.length > 1) {
|
if (args[0].toLowerCase() === "sudo" && args.length > 1) {
|
||||||
args.shift();
|
args.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get command
|
||||||
const commandName = args.shift().toLowerCase();
|
const commandName = args.shift().toLowerCase();
|
||||||
|
|
||||||
const command = CommandsManager.find(commandName);
|
const command = CommandsManager.find(commandName);
|
||||||
|
|
||||||
if (!command)
|
if (!command)
|
||||||
return `${commandName}: Command not found`;
|
return `${commandName}: Command not found`;
|
||||||
|
|
||||||
|
// Get options
|
||||||
|
const options = [];
|
||||||
|
args.filter((arg) => arg.startsWith("-")).forEach((option) => {
|
||||||
|
if (option.startsWith("--")) {
|
||||||
|
const longOption = option.substring(2).toLowerCase();
|
||||||
|
if (!options.includes(longOption))
|
||||||
|
options.push(longOption);
|
||||||
|
} else {
|
||||||
|
const shortOptions = option.substring(1).split("");
|
||||||
|
shortOptions.forEach((shortOption) => {
|
||||||
|
if (!options.includes(shortOption))
|
||||||
|
options.push(shortOption);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromArray(option, args);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check usage
|
||||||
if (command.requireArgs && args.length === 0)
|
if (command.requireArgs && args.length === 0)
|
||||||
return `${commandName}: Incorrect syntax: ${commandName} requires at least 1 argument`;
|
return `${commandName}: Incorrect usage: ${commandName} requires at least 1 argument`;
|
||||||
|
|
||||||
|
if (command.requireOptions && options.length === 0)
|
||||||
|
return `${commandName}: Incorrect usage: ${commandName} requires at least 1 option`;
|
||||||
|
|
||||||
|
// Execute command
|
||||||
let response = null;
|
let response = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -70,7 +95,9 @@ export function Terminal({ setTitle }) {
|
||||||
setCurrentDirectory,
|
setCurrentDirectory,
|
||||||
username: USERNAME,
|
username: USERNAME,
|
||||||
hostname: HOSTNAME,
|
hostname: HOSTNAME,
|
||||||
rawInputValue
|
rawInputValue,
|
||||||
|
options,
|
||||||
|
exit
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response == null)
|
if (response == null)
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,33 @@
|
||||||
import { VirtualFolder } from "../../virtual-drive/virtualFolder.js";
|
import { VirtualFolder } from "../../virtual-drive/virtualFolder.js";
|
||||||
import { VirtualRoot } from "../../virtual-drive/virtualRoot.js";
|
import { VirtualRoot } from "../../virtual-drive/virtualRoot.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback executeType
|
||||||
|
* @param {string[]} args
|
||||||
|
* @param {object} options
|
||||||
|
* @param {Function} options.promptOutput
|
||||||
|
* @param {Function} options.pushHistory
|
||||||
|
* @param {VirtualRoot} options.virtualRoot
|
||||||
|
* @param {VirtualFolder} options.currentDirectory
|
||||||
|
* @param {Function} options.setCurrentDirectory
|
||||||
|
* @param {string} options.username
|
||||||
|
* @param {string} options.hostname
|
||||||
|
* @param {string} options.rawInputValue
|
||||||
|
* @param {string[]} options.options
|
||||||
|
* @param {Function} options.exit
|
||||||
|
* @returns {string|{ blank: boolean }}
|
||||||
|
*/
|
||||||
|
|
||||||
export default class Command {
|
export default class Command {
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
name;
|
name;
|
||||||
|
|
||||||
/**
|
/** @type {executeType} */
|
||||||
* @param {string[]} args
|
execute = () => {};
|
||||||
* @param {object} options
|
|
||||||
* @param {Function} options.promptOutput
|
|
||||||
* @param {Function} options.pushHistory
|
|
||||||
* @param {VirtualRoot} options.virtualRoot
|
|
||||||
* @param {VirtualFolder} options.currentDirectory
|
|
||||||
* @param {Function} options.setCurrentDirectory
|
|
||||||
* @param {string} options.username
|
|
||||||
* @param {string} options.hostname
|
|
||||||
* @param {string} options.rawInputValue
|
|
||||||
* @returns {string|{ blank: boolean }}
|
|
||||||
*/
|
|
||||||
execute = (args, options) => {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {Function} execute
|
* @param {executeType} execute
|
||||||
*/
|
*/
|
||||||
constructor(name, execute) {
|
constructor(name, execute) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
@ -39,7 +44,7 @@ export default class Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Function} execute
|
* @param {executeType} execute
|
||||||
* @returns {Command}
|
* @returns {Command}
|
||||||
*/
|
*/
|
||||||
setExecute(execute) {
|
setExecute(execute) {
|
||||||
|
|
@ -57,11 +62,20 @@ export default class Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ purpose: string, usage: string, description: string}} name
|
* @param {boolean} value
|
||||||
* @returns {Command}
|
* @returns {Command}
|
||||||
*/
|
*/
|
||||||
setManual({ purpose, usage, description }) {
|
setRequireOptions(value) {
|
||||||
this.manual = { purpose, usage, description };
|
this.requireOptions = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ purpose: string, usage: string, description: string, options: object }} manual
|
||||||
|
* @returns {Command}
|
||||||
|
*/
|
||||||
|
setManual({ purpose, usage, description, options }) {
|
||||||
|
this.manual = { purpose, usage, description, options };
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
|
import Command from "./command.js";
|
||||||
import { blow } from "./commands/blow.js";
|
import { blow } from "./commands/blow.js";
|
||||||
import { cat } from "./commands/cat.js";
|
import { cat } from "./commands/cat.js";
|
||||||
import { cd } from "./commands/cd.js";
|
import { cd } from "./commands/cd.js";
|
||||||
import { clear } from "./commands/clear.js";
|
import { clear } from "./commands/clear.js";
|
||||||
|
import { compgen } from "./commands/compgen.js";
|
||||||
import { cowsay } from "./commands/cowsay.js";
|
import { cowsay } from "./commands/cowsay.js";
|
||||||
import { dir } from "./commands/dir.js";
|
import { dir } from "./commands/dir.js";
|
||||||
import { echo } from "./commands/echo.js";
|
import { echo } from "./commands/echo.js";
|
||||||
|
import { exit } from "./commands/exit.js";
|
||||||
import { fortune } from "./commands/fortune.js";
|
import { fortune } from "./commands/fortune.js";
|
||||||
import { hostname } from "./commands/hostname.js";
|
import { hostname } from "./commands/hostname.js";
|
||||||
import { ls } from "./commands/ls.js";
|
import { ls } from "./commands/ls.js";
|
||||||
|
|
@ -18,12 +21,14 @@ import { reboot } from "./commands/reboot.js";
|
||||||
import { rm } from "./commands/rm.js";
|
import { rm } from "./commands/rm.js";
|
||||||
import { rmdir } from "./commands/rmdir.js";
|
import { rmdir } from "./commands/rmdir.js";
|
||||||
import { touch } from "./commands/touch.js";
|
import { touch } from "./commands/touch.js";
|
||||||
|
import { whatis } from "./commands/whatis.js";
|
||||||
|
import { whoami } from "./commands/whoami.js";
|
||||||
import { world } from "./commands/world.js";
|
import { world } from "./commands/world.js";
|
||||||
|
|
||||||
export default class CommandsManager {
|
export default class CommandsManager {
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @returns {CommandsManager}
|
* @returns {Command}
|
||||||
*/
|
*/
|
||||||
static find(name) {
|
static find(name) {
|
||||||
let matchCommand = null;
|
let matchCommand = null;
|
||||||
|
|
@ -38,6 +43,15 @@ export default class CommandsManager {
|
||||||
return matchCommand;
|
return matchCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} pattern
|
||||||
|
* @returns {Command[]}
|
||||||
|
*/
|
||||||
|
static search(pattern) {
|
||||||
|
const matches = this.COMMANDS.filter((command) => command.name.match(pattern));
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
static COMMANDS = [
|
static COMMANDS = [
|
||||||
echo,
|
echo,
|
||||||
clear,
|
clear,
|
||||||
|
|
@ -60,5 +74,9 @@ export default class CommandsManager {
|
||||||
cat,
|
cat,
|
||||||
man,
|
man,
|
||||||
reboot,
|
reboot,
|
||||||
|
compgen,
|
||||||
|
whoami,
|
||||||
|
whatis,
|
||||||
|
exit,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import Command from "../command.js";
|
import Command from "../command.js";
|
||||||
|
|
||||||
export const blow = new Command("%blow", () => {
|
export const blow = new Command("%blow")
|
||||||
return "fg: %blow: No such job";
|
.setExecute(function() {
|
||||||
});
|
return `fg: ${this.name}: No such job`;
|
||||||
|
});
|
||||||
|
|
@ -7,15 +7,20 @@ export const cat = new Command("cat")
|
||||||
usage: "cat [OPTION]... [FILE]...",
|
usage: "cat [OPTION]... [FILE]...",
|
||||||
description: "Concetenate FILE(s) to standard output."
|
description: "Concetenate FILE(s) to standard output."
|
||||||
})
|
})
|
||||||
.setExecute((args, { currentDirectory }) => {
|
.setExecute(function(args, { currentDirectory, options }) {
|
||||||
const [name, extension] = args[0].split(".");
|
const [name, extension] = args[0].split(".");
|
||||||
const file = currentDirectory.findFile(name, extension);
|
const file = currentDirectory.findFile(name, extension);
|
||||||
|
|
||||||
if (!file)
|
if (!file)
|
||||||
return `rm: ${args[0]}: No such file`;
|
return `${this.name}: ${args[0]}: No such file`;
|
||||||
|
|
||||||
if (file.content) {
|
if (file.content) {
|
||||||
return file.content;
|
if (!options.includes("e")) {
|
||||||
|
return file.content;
|
||||||
|
} else {
|
||||||
|
// Append "$" at the end of every line
|
||||||
|
return file.content.split("\n").join("$\n") + "$";
|
||||||
|
}
|
||||||
} else if (file.source) {
|
} else if (file.source) {
|
||||||
return `Src: ${file.source}`;
|
return `Src: ${file.source}`;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,20 @@
|
||||||
import Command from "../command.js";
|
import Command from "../command.js";
|
||||||
|
|
||||||
export const cd = new Command("cd", (args, { currentDirectory, setCurrentDirectory }) => {
|
export const cd = new Command("cd")
|
||||||
const path = args[0] ?? "~";
|
.setRequireArgs(true)
|
||||||
const destination = currentDirectory.navigate(path);
|
.setManual({
|
||||||
|
purpose: "Change directory",
|
||||||
if (!destination)
|
usage: "cd path",
|
||||||
return `cd: ${args[0]}: No such file or directory`;
|
description: "Change working directory to given path (the home directory by default)."
|
||||||
|
})
|
||||||
console.log(destination);
|
.setExecute(function(args, { currentDirectory, setCurrentDirectory }) {
|
||||||
setCurrentDirectory(destination);
|
const path = args[0] ?? "~";
|
||||||
return { blank: true };
|
const destination = currentDirectory.navigate(path);
|
||||||
}).setRequireArgs(true);
|
|
||||||
|
if (!destination)
|
||||||
|
return `${this.name}: ${args[0]}: No such file or directory`;
|
||||||
|
|
||||||
|
console.log(destination);
|
||||||
|
setCurrentDirectory(destination);
|
||||||
|
return { blank: true };
|
||||||
|
});
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
import Command from "../command.js";
|
import Command from "../command.js";
|
||||||
|
|
||||||
export const clear = new Command("clear", (args, { pushHistory }) => {
|
export const clear = new Command("clear")
|
||||||
pushHistory({
|
.setManual({
|
||||||
clear: true,
|
purpose: "Clear terminal screen",
|
||||||
isInput: false
|
usage: "clear",
|
||||||
});
|
})
|
||||||
|
.setExecute(function(args, { pushHistory }) {
|
||||||
return { blank: true };
|
pushHistory({
|
||||||
});
|
clear: true,
|
||||||
|
isInput: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return { blank: true };
|
||||||
|
});
|
||||||
10
src/features/applications/terminal/commands/compgen.js
Normal file
10
src/features/applications/terminal/commands/compgen.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
import CommandsManager from "../commands.js";
|
||||||
|
|
||||||
|
export const compgen = new Command("compgen")
|
||||||
|
.setRequireOptions(true)
|
||||||
|
.setExecute(function(args, { options }) {
|
||||||
|
if (options.includes("c")) {
|
||||||
|
return CommandsManager.COMMANDS.map((command) => command.name).sort().join("\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -8,92 +8,99 @@ const COW = `
|
||||||
||----w |
|
||----w |
|
||||||
|| ||`;
|
|| ||`;
|
||||||
|
|
||||||
export const cowsay = new Command("cowsay", (args, { rawInputValue }) => {
|
export const cowsay = new Command("cowsay")
|
||||||
// Separate input value into lines
|
.setRequireArgs(true)
|
||||||
const segments = rawInputValue.split(" ");
|
.setManual({
|
||||||
const lines = [];
|
purpose: "Show a cow saying something",
|
||||||
let currentLine = "";
|
usage: "cowsay text",
|
||||||
let maxLineWidth = 0;
|
description: "Show ASCII art of a cow saying something."
|
||||||
|
})
|
||||||
|
.setExecute(function(args, { rawInputValue }) {
|
||||||
|
// Separate input value into lines
|
||||||
|
const segments = rawInputValue.split(" ");
|
||||||
|
const lines = [];
|
||||||
|
let currentLine = "";
|
||||||
|
let maxLineWidth = 0;
|
||||||
|
|
||||||
const addLine = (line) => {
|
const addLine = (line) => {
|
||||||
line = line.trimEnd();
|
line = line.trimEnd();
|
||||||
lines.push(line);
|
lines.push(line);
|
||||||
if (line.length > maxLineWidth)
|
if (line.length > maxLineWidth)
|
||||||
maxLineWidth = line.length;
|
maxLineWidth = line.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextLine = (word) => {
|
const nextLine = (word) => {
|
||||||
addLine(currentLine);
|
addLine(currentLine);
|
||||||
|
|
||||||
if (word) {
|
if (word) {
|
||||||
currentLine = word + " ";
|
currentLine = word + " ";
|
||||||
} else {
|
} else {
|
||||||
currentLine = "";
|
currentLine = "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
segments.forEach((segment) => {
|
segments.forEach((segment) => {
|
||||||
// Add empty spaces preceding lines
|
// Add empty spaces preceding lines
|
||||||
if (segment === "") {
|
if (segment === "") {
|
||||||
currentLine += " ";
|
currentLine += " ";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const words = segment.split("\n");
|
const words = segment.split("\n");
|
||||||
for (let i = 0; i < words.length; i++) {
|
for (let i = 0; i < words.length; i++) {
|
||||||
const word = words[i];
|
const word = words[i];
|
||||||
|
|
||||||
// Handle next lines
|
// Handle next lines
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
nextLine();
|
nextLine();
|
||||||
|
|
||||||
// Fit word on current line
|
// Fit word on current line
|
||||||
if ((currentLine + word).length <= MAX_WIDTH) {
|
if ((currentLine + word).length <= MAX_WIDTH) {
|
||||||
currentLine += word + " ";
|
currentLine += word + " ";
|
||||||
} else if (word.length > MAX_WIDTH) {
|
} else if (word.length > MAX_WIDTH) {
|
||||||
const remainingSpaces = MAX_WIDTH - currentLine.length;
|
const remainingSpaces = MAX_WIDTH - currentLine.length;
|
||||||
|
|
||||||
if (remainingSpaces >= 2) {
|
if (remainingSpaces >= 2) {
|
||||||
addLine(currentLine + word.substring(0, remainingSpaces - 1) + "-");
|
addLine(currentLine + word.substring(0, remainingSpaces - 1) + "-");
|
||||||
currentLine = word.substring(remainingSpaces - 1) + " ";
|
currentLine = word.substring(remainingSpaces - 1) + " ";
|
||||||
|
} else {
|
||||||
|
nextLine(word);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
nextLine(word);
|
nextLine(word);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
nextLine(word);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (currentLine.length > 0)
|
if (currentLine.length > 0)
|
||||||
addLine(currentLine);
|
addLine(currentLine);
|
||||||
|
|
||||||
// Turn lines into speech bubble
|
// Turn lines into speech bubble
|
||||||
const speechBubble = [` ${"_".repeat(maxLineWidth + 2)} `];
|
const speechBubble = [` ${"_".repeat(maxLineWidth + 2)} `];
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
let line = lines[i];
|
let line = lines[i];
|
||||||
const missingSpaces = maxLineWidth - line.length;
|
const missingSpaces = maxLineWidth - line.length;
|
||||||
|
|
||||||
if (missingSpaces > 0)
|
if (missingSpaces > 0)
|
||||||
line += " ".repeat(missingSpaces);
|
line += " ".repeat(missingSpaces);
|
||||||
|
|
||||||
if (lines.length > 1) {
|
if (lines.length > 1) {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
line = `/ ${line} \\`;
|
line = `/ ${line} \\`;
|
||||||
} else if (i === lines.length - 1) {
|
} else if (i === lines.length - 1) {
|
||||||
line = `\\ ${line} /`;
|
line = `\\ ${line} /`;
|
||||||
|
} else {
|
||||||
|
line = `| ${line} |`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
line = `| ${line} |`;
|
line = `< ${line} >`;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
line = `< ${line} >`;
|
speechBubble.push(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
speechBubble.push(line);
|
speechBubble.push(` ${"-".repeat(maxLineWidth + 2)} `);
|
||||||
}
|
|
||||||
|
|
||||||
speechBubble.push(` ${"-".repeat(maxLineWidth + 2)} `);
|
return speechBubble.join("\n") + COW;
|
||||||
|
});
|
||||||
return speechBubble.join("\n") + COW;
|
|
||||||
}).setRequireArgs(true);
|
|
||||||
6
src/features/applications/terminal/commands/exit.js
Normal file
6
src/features/applications/terminal/commands/exit.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const exit = new Command("exit", (args, { exit }) => {
|
||||||
|
exit();
|
||||||
|
return { blank: true };
|
||||||
|
});
|
||||||
|
|
@ -7,7 +7,7 @@ export const ls = new Command("ls")
|
||||||
description: "List information about the FILEs (the current directory by default).\n"
|
description: "List information about the FILEs (the current directory by default).\n"
|
||||||
+ "Sort entries alphabetically if none of -cftuvSUX nor --sort is specified."
|
+ "Sort entries alphabetically if none of -cftuvSUX nor --sort is specified."
|
||||||
})
|
})
|
||||||
.setExecute((args, { currentDirectory }) => {
|
.setExecute(function(args, { currentDirectory }) {
|
||||||
let directory = currentDirectory;
|
let directory = currentDirectory;
|
||||||
|
|
||||||
if (args.length > 0) {
|
if (args.length > 0) {
|
||||||
|
|
@ -15,7 +15,7 @@ export const ls = new Command("ls")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!directory)
|
if (!directory)
|
||||||
return `ls: Cannot access '${args[0]}': No such file or directory`;
|
return `${this.name}: Cannot access '${args[0]}': No such file or directory`;
|
||||||
|
|
||||||
const folderNames = directory.subFolders.map((folder) => folder.id);
|
const folderNames = directory.subFolders.map((folder) => folder.id);
|
||||||
const fileNames = directory.files.map((file) => file.id);
|
const fileNames = directory.files.map((file) => file.id);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import Command from "../command.js";
|
import Command from "../command.js";
|
||||||
|
|
||||||
export const make = new Command("make", (args) => {
|
export const make = new Command("make")
|
||||||
if (args[0] === "love")
|
.setExecute(function(args) {
|
||||||
return "make: *** No rule to make target `love'. Stop.";
|
if (args[0] === "love")
|
||||||
});
|
return `${this.name}: *** No rule to make target 'love'. Stop.`;
|
||||||
|
});
|
||||||
|
|
@ -6,22 +6,38 @@ const MARGIN = 5;
|
||||||
export const man = new Command("man")
|
export const man = new Command("man")
|
||||||
.setRequireArgs(true)
|
.setRequireArgs(true)
|
||||||
.setManual({
|
.setManual({
|
||||||
purpose: "show system reference manuals",
|
purpose: "Show system reference manuals",
|
||||||
usage: "man [OPTION]... page",
|
usage: "man [OPTION]... page\n"
|
||||||
|
+ "man -k [OPTION]... regexp",
|
||||||
description: "Each page arguments given to man is normally the name of a command.\n"
|
description: "Each page arguments given to man is normally the name of a command.\n"
|
||||||
+ "The manual page associated with this command is then found and displayed."
|
+ "The manual page associated with this command is then found and displayed.",
|
||||||
|
options: {
|
||||||
|
"-k": "Search for manual page using regexp"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.setExecute((args) => {
|
.setExecute(function(args, { options }) {
|
||||||
|
// Search function
|
||||||
|
if (options.includes("k")) {
|
||||||
|
const commands = CommandsManager.search(args[0]);
|
||||||
|
return commands.map((command) => {
|
||||||
|
if (command.manual?.purpose) {
|
||||||
|
return `${command.name} - ${command.manual.purpose}`;
|
||||||
|
} else {
|
||||||
|
return command.name;
|
||||||
|
}
|
||||||
|
}).join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
const commandName = args[0].toLowerCase();
|
const commandName = args[0].toLowerCase();
|
||||||
const command = CommandsManager.find(commandName);
|
const command = CommandsManager.find(commandName);
|
||||||
|
|
||||||
if (!command)
|
if (!command)
|
||||||
return `man: ${commandName}: Command not found`;
|
return `${this.name}: ${commandName}: Command not found`;
|
||||||
|
|
||||||
const manual = command.manual;
|
const manual = command.manual;
|
||||||
|
|
||||||
if (!manual)
|
if (!manual)
|
||||||
return `man: ${commandName}: No manual found`;
|
return `${this.name}: ${commandName}: No manual found`;
|
||||||
|
|
||||||
const formatText = (text) => {
|
const formatText = (text) => {
|
||||||
const lines = text.split("\n").map((line) => " ".repeat(MARGIN) + line);
|
const lines = text.split("\n").map((line) => " ".repeat(MARGIN) + line);
|
||||||
|
|
@ -50,5 +66,14 @@ export const man = new Command("man")
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (manual.options) {
|
||||||
|
sections.push([
|
||||||
|
"OPTIONS",
|
||||||
|
formatText(Object.entries(manual.options).map(([key, value]) => {
|
||||||
|
return `${key} ${value}`;
|
||||||
|
}).join("\n"))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return sections.map((section) => section.join("\n")).join("\n\n");
|
return sections.map((section) => section.join("\n")).join("\n\n");
|
||||||
});
|
});
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import Command from "../command.js";
|
import Command from "../command.js";
|
||||||
|
|
||||||
export const nice = new Command("nice", (args) => {
|
export const nice = new Command("nice")
|
||||||
if (args[0] === "man" && args[1] === "woman")
|
.setExecute(function(args) {
|
||||||
return "nice: No manual entry for woman";
|
if (args[0] === "man" && args[1] === "woman")
|
||||||
});
|
return `${this.name}: No manual entry for woman`;
|
||||||
|
});
|
||||||
|
|
@ -5,7 +5,7 @@ export const rm = new Command("rm", (args, { currentDirectory }) => {
|
||||||
const file = currentDirectory.findFile(name, extension);
|
const file = currentDirectory.findFile(name, extension);
|
||||||
|
|
||||||
if (!file)
|
if (!file)
|
||||||
return `rm: ${args[0]}: No such file`;
|
return `${this.name}: ${args[0]}: No such file`;
|
||||||
|
|
||||||
file.delete();
|
file.delete();
|
||||||
return { blank: true };
|
return { blank: true };
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export const rmdir = new Command("rmdir", (args, { currentDirectory }) => {
|
||||||
const folder = currentDirectory.findSubFolder(name);
|
const folder = currentDirectory.findSubFolder(name);
|
||||||
|
|
||||||
if (!folder)
|
if (!folder)
|
||||||
return `rmdir: ${args[0]}: No such directory`;
|
return `${this.name}: ${args[0]}: No such directory`;
|
||||||
|
|
||||||
folder.delete();
|
folder.delete();
|
||||||
return { blank: true };
|
return { blank: true };
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@ import Command from "../command.js";
|
||||||
export const touch = new Command("touch")
|
export const touch = new Command("touch")
|
||||||
.setRequireArgs(true)
|
.setRequireArgs(true)
|
||||||
.setManual({
|
.setManual({
|
||||||
purpose: "change file timestamps",
|
purpose: "Change file timestamps",
|
||||||
usage: "touch [OPTION]... FILE...",
|
usage: "touch [OPTION]... FILE...",
|
||||||
description: "Update the access and modification times of each FILE to the current time.\n\n"
|
description: "Update the access and modification times of each FILE to the current time.\n\n"
|
||||||
+ "A FILE argument that does not exist is created empty."
|
+ "A FILE argument that does not exist is created empty."
|
||||||
})
|
})
|
||||||
.setExecute((args, { currentDirectory }) => {
|
.setExecute(function(args, { currentDirectory }) {
|
||||||
if (args[0] === "girls\\" && args[1] === "boo**")
|
if (args[0] === "girls\\" && args[1] === "boo**")
|
||||||
return "touch: Cannot touch 'girls boo**': Permission denied";
|
return `${this.name}: Cannot touch 'girls boo**': Permission denied`;
|
||||||
|
|
||||||
const [name, extension] = args[0].split(".");
|
const [name, extension] = args[0].split(".");
|
||||||
|
|
||||||
|
|
|
||||||
17
src/features/applications/terminal/commands/whatis.js
Normal file
17
src/features/applications/terminal/commands/whatis.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
import CommandsManager from "../commands.js";
|
||||||
|
|
||||||
|
export const whatis = new Command("whatis")
|
||||||
|
.setRequireArgs(true)
|
||||||
|
.setExecute(function(args) {
|
||||||
|
const commandName = args[0].toLowerCase();
|
||||||
|
const command = CommandsManager.find(commandName);
|
||||||
|
|
||||||
|
if (!command)
|
||||||
|
return `${this.name}: ${commandName}: Command not found`;
|
||||||
|
|
||||||
|
if (!command.manual?.purpose)
|
||||||
|
return `${this.name}: ${commandName}: No information found`;
|
||||||
|
|
||||||
|
return `${commandName} - ${command.manual.purpose}`;
|
||||||
|
});
|
||||||
5
src/features/applications/terminal/commands/whoami.js
Normal file
5
src/features/applications/terminal/commands/whoami.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Command from "../command.js";
|
||||||
|
|
||||||
|
export const whoami = new Command("whoami", (args, { username }) => {
|
||||||
|
return username;
|
||||||
|
});
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import Command from "../command.js";
|
import Command from "../command.js";
|
||||||
|
|
||||||
export const world = new Command("world", () => {
|
export const world = new Command("world")
|
||||||
return "world: Not found";
|
.setExecute(function() {
|
||||||
});
|
return `${this.name}: Not found`;
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue