add man pages for shell commands and implement mkdir, ping, matrix, and about commands
This commit is contained in:
parent
f39333cae6
commit
427f864ad6
@ -1,6 +1,7 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Terminal } from 'lucide-react';
|
||||
import { System } from '../shell/system';
|
||||
import '../styles/matrix.css';
|
||||
|
||||
const TerminalShell: React.FC = () => {
|
||||
const [history, setHistory] = useState<string[]>(['Welcome to the terminal! Type "help" for commands.']);
|
||||
@ -20,12 +21,13 @@ const TerminalShell: React.FC = () => {
|
||||
if (system.knowsCommand(cmdName)) {
|
||||
const output = system.executeCommand(cmdName, args);
|
||||
|
||||
const newLines = output.split('\\n').filter(l => l);
|
||||
const newLines = output.split('\n').filter(l => l);
|
||||
|
||||
setHistory(prev => [...prev, `$ ${command}`, ...newLines]);
|
||||
} else {
|
||||
setHistory(prev => [...prev, `$ ${command}`, `Command not found: ${cmdName}`]);
|
||||
}
|
||||
|
||||
setCurrentCommand('');
|
||||
}
|
||||
};
|
||||
@ -34,8 +36,8 @@ const TerminalShell: React.FC = () => {
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex-1 overflow-y-auto mb-4">
|
||||
{history.map((line, index) => (
|
||||
<div key={index} className="mb-2">
|
||||
{line}
|
||||
<div key={index} className="mb-2" dangerouslySetInnerHTML={{ __html: line }}>
|
||||
{/* Dangerously set inner HTML */}
|
||||
</div>
|
||||
))}
|
||||
<div ref={bottomRef} />
|
||||
|
@ -106,4 +106,6 @@ export class FileSystem {
|
||||
current.children!.delete(fileName);
|
||||
console.log(`[FileSystem] File deleted: ${path}`);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -40,4 +40,8 @@ export class ShellSyscall {
|
||||
deleteFile(path: string): void {
|
||||
this.fs.deleteFile(`${this.cwd}/${path}`);
|
||||
}
|
||||
|
||||
createDirectory(dirname: string): void {
|
||||
this.fs.createDirectory(`${this.cwd}/${dirname}`);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
export interface IShellCommand {
|
||||
getName(): string;
|
||||
getManPage(): string;
|
||||
execute(input: string | string[]): string;
|
||||
}
|
20
src/shell/commands/about.ts
Normal file
20
src/shell/commands/about.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { IShellCommand } from "./IShellCommand";
|
||||
|
||||
export class about implements IShellCommand {
|
||||
getName(): string {
|
||||
return 'about';
|
||||
}
|
||||
|
||||
execute(_: string | string[]): string {
|
||||
let output = 'Really cool terminal ;)';
|
||||
output += '\n\n';
|
||||
output += 'Built by ivanch';
|
||||
output += '\n';
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return 'about - about this terminal';
|
||||
}
|
||||
}
|
@ -12,4 +12,8 @@ export class cat implements IShellCommand {
|
||||
if (args.length === 0) return "cat: missing file operand";
|
||||
return this.syscall.readFile(args[0]);
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return "cat - concatenate files and print on the standard output";
|
||||
}
|
||||
}
|
||||
|
@ -13,4 +13,8 @@ export class cd implements IShellCommand {
|
||||
const success = this.syscall.changeDirectory(path);
|
||||
return success ? '' : 'Invalid directory';
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return "cd - change the shell working directory";
|
||||
}
|
||||
}
|
||||
|
@ -11,4 +11,8 @@ export class echo implements IShellCommand {
|
||||
}
|
||||
return input.trim();
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return 'echo - echo a line of text';
|
||||
}
|
||||
}
|
@ -12,4 +12,8 @@ export class ls implements IShellCommand {
|
||||
const files = this.syscall.listCurrentDirectory();
|
||||
return files.join('\n');
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return 'ls - list directory contents';
|
||||
}
|
||||
}
|
||||
|
27
src/shell/commands/man.ts
Normal file
27
src/shell/commands/man.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { IShellCommand } from "./IShellCommand";
|
||||
import { System } from "../system";
|
||||
|
||||
export class man implements IShellCommand {
|
||||
getName(): string {
|
||||
return 'man';
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return 'man - display system reference manuals\n\nUsage: man command_name';
|
||||
}
|
||||
|
||||
execute(args: string[]): string {
|
||||
if (args.length !== 1) {
|
||||
return 'Usage: man command_name';
|
||||
}
|
||||
|
||||
const system = System.getInstance();
|
||||
const command = system['commands'].find(c => c.getName() === args[0]);
|
||||
|
||||
if (!command) {
|
||||
return `No manual entry for ${args[0]}`;
|
||||
}
|
||||
|
||||
return command.getManPage();
|
||||
}
|
||||
}
|
33
src/shell/commands/matrix.ts
Normal file
33
src/shell/commands/matrix.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { IShellCommand } from "./IShellCommand";
|
||||
|
||||
export class matrix implements IShellCommand {
|
||||
getName(): string {
|
||||
return 'matrix';
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return 'matrix - displays a matrix-style text animation\n\nUsage: matrix';
|
||||
}
|
||||
|
||||
execute(): string {
|
||||
const width = 40;
|
||||
const height = 15;
|
||||
const characters = '日ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘ';
|
||||
|
||||
const matrixHtml = `
|
||||
<div class="matrix-text-animation">
|
||||
${Array(height).fill(0).map(() => `
|
||||
<div class="matrix-line">
|
||||
${Array(width).fill(0).map(() => `
|
||||
<span class="matrix-char" style="animation-delay: ${Math.random() * 5}s">
|
||||
${characters[Math.floor(Math.random() * characters.length)]}
|
||||
</span>
|
||||
`).join('')}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return matrixHtml;
|
||||
}
|
||||
}
|
23
src/shell/commands/mkdir.ts
Normal file
23
src/shell/commands/mkdir.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { IShellCommand } from "./IShellCommand";
|
||||
import { ShellSyscall } from "../ShellSyscall";
|
||||
|
||||
export class mkdir implements IShellCommand {
|
||||
constructor(private syscall: ShellSyscall) {}
|
||||
|
||||
getName(): string {
|
||||
return 'mkdir';
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return 'mkdir - make directories\n\nUsage: mkdir directory_name';
|
||||
}
|
||||
|
||||
execute(args: string[]): string {
|
||||
if (args.length !== 1) {
|
||||
return 'Usage: mkdir directory_name';
|
||||
}
|
||||
|
||||
this.syscall.createDirectory(args[0]);
|
||||
return '';
|
||||
}
|
||||
}
|
31
src/shell/commands/ping.ts
Normal file
31
src/shell/commands/ping.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { IShellCommand } from "./IShellCommand";
|
||||
|
||||
export class ping implements IShellCommand {
|
||||
getName(): string {
|
||||
return 'ping';
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return 'ping - send ICMP ECHO_REQUEST to network hosts\n\nUsage: ping [ip-address]';
|
||||
}
|
||||
|
||||
execute(args: string[]): string {
|
||||
if (args.length !== 1) {
|
||||
return 'Usage: ping [ip-address]';
|
||||
}
|
||||
|
||||
const ip = args[0];
|
||||
let output = `PING ${ip} (${ip}) 56(84) bytes of data.\n`;
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const time = Math.random() * 50 + 20;
|
||||
output += `64 bytes from ${ip}: icmp_seq=${i + 1} ttl=64 time=${time.toFixed(1)} ms\n`;
|
||||
}
|
||||
|
||||
output += '\n--- ping statistics ---\n';
|
||||
output += `4 packets transmitted, 4 received, 0% packet loss, time 3000ms\n`;
|
||||
output += `rtt min/avg/max/mdev = 20.0/30.0/40.0/5.0 ms\n`;
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
@ -11,4 +11,8 @@ export class pwd implements IShellCommand {
|
||||
execute(_: string[]): string {
|
||||
return this.syscall.getCurrentDirectory();
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return "pwd - print name of current/working directory";
|
||||
}
|
||||
}
|
||||
|
@ -21,4 +21,8 @@ export class rm implements IShellCommand {
|
||||
this.syscall.deleteFile(filename);
|
||||
return `Removed ${filename}`;
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return "rm - remove files or directories";
|
||||
}
|
||||
}
|
||||
|
15
src/shell/commands/sudo.ts
Normal file
15
src/shell/commands/sudo.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { IShellCommand } from "./IShellCommand";
|
||||
|
||||
export class sudo implements IShellCommand {
|
||||
getName(): string {
|
||||
return 'sudo';
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return 'sudo - execute a command as another user\n\nUsage: sudo command';
|
||||
}
|
||||
|
||||
execute(args: string[]): string {
|
||||
return 'Permission denied';
|
||||
}
|
||||
}
|
@ -13,4 +13,8 @@ export class touch implements IShellCommand {
|
||||
this.syscall.createFile(args[0]);
|
||||
return "";
|
||||
}
|
||||
|
||||
getManPage(): string {
|
||||
return "touch - create an empty file";
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,15 @@ import { cd } from "./commands/cd";
|
||||
import { touch } from "./commands/touch";
|
||||
import { cat } from "./commands/cat";
|
||||
import { rm } from "./commands/rm";
|
||||
import { ping } from "./commands/ping";
|
||||
import { mkdir } from "./commands/mkdir";
|
||||
import { man } from "./commands/man";
|
||||
import { sudo } from "./commands/sudo";
|
||||
import { IShellCommand } from "./commands/IShellCommand";
|
||||
import { FileSystem } from "./FileSystem";
|
||||
import { ShellSyscall } from "./ShellSyscall";
|
||||
import { about } from "./commands/about";
|
||||
import { matrix } from "./commands/matrix";
|
||||
|
||||
// System class to manage commands
|
||||
class System {
|
||||
@ -32,6 +38,12 @@ class System {
|
||||
this.commands.push(new touch(syscall));
|
||||
this.commands.push(new cat(syscall));
|
||||
this.commands.push(new rm(syscall));
|
||||
this.commands.push(new ping());
|
||||
this.commands.push(new mkdir(syscall));
|
||||
this.commands.push(new man());
|
||||
this.commands.push(new sudo());
|
||||
this.commands.push(new about());
|
||||
// this.commands.push(new matrix());
|
||||
}
|
||||
|
||||
public static getInstance(): System {
|
||||
@ -42,10 +54,14 @@ class System {
|
||||
}
|
||||
|
||||
knowsCommand(commandName: string): boolean {
|
||||
return this.commands.some(c => c.getName() === commandName);
|
||||
return commandName === 'help' || this.commands.some(c => c.getName() === commandName);
|
||||
}
|
||||
|
||||
executeCommand(commandName: string, args: string[]): string {
|
||||
if (commandName === 'help') {
|
||||
return this.commands.map(c => c.getName()).join('\n');
|
||||
}
|
||||
|
||||
const command = this.commands.find(c => c.getName() === commandName);
|
||||
if (command) {
|
||||
return command.execute(args);
|
||||
|
30
src/styles/matrix.css
Normal file
30
src/styles/matrix.css
Normal file
@ -0,0 +1,30 @@
|
||||
.matrix-text-animation {
|
||||
color: #0F0;
|
||||
font-family: monospace;
|
||||
line-height: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
.matrix-line {
|
||||
white-space: pre;
|
||||
}
|
||||
.matrix-char {
|
||||
opacity: 0;
|
||||
display: inline-block;
|
||||
animation: matrix-fade 3s infinite;
|
||||
}
|
||||
@keyframes matrix-fade {
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
.matrix-text-animation::after {
|
||||
content: "Wake up, Neo...";
|
||||
display: block;
|
||||
color: #0F0;
|
||||
font-weight: bold;
|
||||
margin-top: 1em;
|
||||
animation: text-fade 4s infinite;
|
||||
}
|
||||
@keyframes text-fade {
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user