add man pages for shell commands and implement mkdir, ping, matrix, and about commands
This commit is contained in:
		| @@ -1,6 +1,7 @@ | |||||||
| import React, { useState, useRef, useEffect } from 'react'; | import React, { useState, useRef, useEffect } from 'react'; | ||||||
| import { Terminal } from 'lucide-react'; | import { Terminal } from 'lucide-react'; | ||||||
| import { System } from '../shell/system'; | import { System } from '../shell/system'; | ||||||
|  | import '../styles/matrix.css'; | ||||||
|  |  | ||||||
| const TerminalShell: React.FC = () => { | const TerminalShell: React.FC = () => { | ||||||
|     const [history, setHistory] = useState<string[]>(['Welcome to the terminal! Type "help" for commands.']); |     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)) { |             if (system.knowsCommand(cmdName)) { | ||||||
|                 const output = system.executeCommand(cmdName, args); |                 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]); |                 setHistory(prev => [...prev, `$ ${command}`, ...newLines]); | ||||||
|             } else { |             } else { | ||||||
|                 setHistory(prev => [...prev, `$ ${command}`, `Command not found: ${cmdName}`]); |                 setHistory(prev => [...prev, `$ ${command}`, `Command not found: ${cmdName}`]); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             setCurrentCommand(''); |             setCurrentCommand(''); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| @@ -34,8 +36,8 @@ const TerminalShell: React.FC = () => { | |||||||
|         <div className="h-full flex flex-col"> |         <div className="h-full flex flex-col"> | ||||||
|             <div className="flex-1 overflow-y-auto mb-4"> |             <div className="flex-1 overflow-y-auto mb-4"> | ||||||
|                 {history.map((line, index) => ( |                 {history.map((line, index) => ( | ||||||
|                     <div key={index} className="mb-2"> |                     <div key={index} className="mb-2" dangerouslySetInnerHTML={{ __html: line }}> | ||||||
|                         {line} |                         {/* Dangerously set inner HTML */} | ||||||
|                     </div> |                     </div> | ||||||
|                 ))} |                 ))} | ||||||
|                 <div ref={bottomRef} /> |                 <div ref={bottomRef} /> | ||||||
|   | |||||||
| @@ -106,4 +106,6 @@ export class FileSystem { | |||||||
|         current.children!.delete(fileName); |         current.children!.delete(fileName); | ||||||
|         console.log(`[FileSystem] File deleted: ${path}`); |         console.log(`[FileSystem] File deleted: ${path}`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -40,4 +40,8 @@ export class ShellSyscall { | |||||||
|     deleteFile(path: string): void { |     deleteFile(path: string): void { | ||||||
|         this.fs.deleteFile(`${this.cwd}/${path}`); |         this.fs.deleteFile(`${this.cwd}/${path}`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     createDirectory(dirname: string): void { | ||||||
|  |         this.fs.createDirectory(`${this.cwd}/${dirname}`); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| export interface IShellCommand { | export interface IShellCommand { | ||||||
|     getName(): string; |     getName(): string; | ||||||
|  |     getManPage(): string; | ||||||
|     execute(input: string | string[]): 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"; |         if (args.length === 0) return "cat: missing file operand"; | ||||||
|         return this.syscall.readFile(args[0]); |         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); |         const success = this.syscall.changeDirectory(path); | ||||||
|         return success ? '' : 'Invalid directory'; |         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(); |         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(); |         const files = this.syscall.listCurrentDirectory(); | ||||||
|         return files.join('\n'); |         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 { |     execute(_: string[]): string { | ||||||
|         return this.syscall.getCurrentDirectory(); |         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); |         this.syscall.deleteFile(filename); | ||||||
|         return `Removed ${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]); |         this.syscall.createFile(args[0]); | ||||||
|         return ""; |         return ""; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     getManPage(): string { | ||||||
|  |         return "touch - create an empty file"; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,9 +5,15 @@ import { cd } from "./commands/cd"; | |||||||
| import { touch } from "./commands/touch"; | import { touch } from "./commands/touch"; | ||||||
| import { cat } from "./commands/cat"; | import { cat } from "./commands/cat"; | ||||||
| import { rm } from "./commands/rm"; | 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 { IShellCommand } from "./commands/IShellCommand"; | ||||||
| import { FileSystem } from "./FileSystem"; | import { FileSystem } from "./FileSystem"; | ||||||
| import { ShellSyscall } from "./ShellSyscall"; | import { ShellSyscall } from "./ShellSyscall"; | ||||||
|  | import { about } from "./commands/about"; | ||||||
|  | import { matrix } from "./commands/matrix"; | ||||||
|  |  | ||||||
| // System class to manage commands | // System class to manage commands | ||||||
| class System { | class System { | ||||||
| @@ -32,6 +38,12 @@ class System { | |||||||
|         this.commands.push(new touch(syscall)); |         this.commands.push(new touch(syscall)); | ||||||
|         this.commands.push(new cat(syscall)); |         this.commands.push(new cat(syscall)); | ||||||
|         this.commands.push(new rm(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 { |     public static getInstance(): System { | ||||||
| @@ -42,10 +54,14 @@ class System { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     knowsCommand(commandName: string): boolean { |     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 { |     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); |         const command = this.commands.find(c => c.getName() === commandName); | ||||||
|         if (command) { |         if (command) { | ||||||
|             return command.execute(args); |             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; } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user