diff --git a/index.html b/index.html index e4b78ea..bb6896f 100755 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Vite + React + TS + demo
diff --git a/package-lock.json b/package-lock.json index 72b398f..324f16d 100755 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "vite-react-typescript-starter", "version": "0.0.0", "dependencies": { + "axios": "^1.7.9", "gray-matter": "^4.0.3", "lucide-react": "^0.344.0", "react": "^18.3.1", @@ -27,7 +28,7 @@ "eslint-plugin-react-refresh": "^0.4.11", "globals": "^15.9.0", "postcss": "^8.4.35", - "tailwindcss": "^3.4.1", + "tailwindcss": "^3.4.17", "typescript": "^5.5.3", "typescript-eslint": "^8.3.0", "vite": "^5.4.2" @@ -1698,6 +1699,11 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -1735,6 +1741,16 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -1964,6 +1980,17 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -2059,6 +2086,14 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2566,6 +2601,25 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -2582,6 +2636,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3087,12 +3154,15 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -4003,6 +4073,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4241,9 +4330,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/picomatch": { @@ -4375,18 +4464,6 @@ } } }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", @@ -4449,6 +4526,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4981,33 +5063,33 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", - "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", - "chokidar": "^3.5.3", + "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.3.0", + "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.21.0", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", diff --git a/package.json b/package.json index 06692c4..471764b 100755 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "axios": "^1.7.9", "gray-matter": "^4.0.3", "lucide-react": "^0.344.0", "react": "^18.3.1", @@ -29,7 +30,7 @@ "eslint-plugin-react-refresh": "^0.4.11", "globals": "^15.9.0", "postcss": "^8.4.35", - "tailwindcss": "^3.4.1", + "tailwindcss": "^3.4.17", "typescript": "^5.5.3", "typescript-eslint": "^8.3.0", "vite": "^5.4.2" diff --git a/public/content/projects.md b/public/content/projects.md index 80aebbf..b53e4d2 100644 --- a/public/content/projects.md +++ b/public/content/projects.md @@ -2,7 +2,7 @@ ## Project 1: Personal Portfolio - **Tech Stack:** React, TypeScript, Tailwind CSS -- **Description:** A retro TV-themed portfolio website showcasing my work and skills +- **Description:** A retro TV-themed portfolio website - [View Source](https://github.com/username/portfolio) ## Project 2: Task Manager diff --git a/src/App.tsx b/src/App.tsx index c544f1a..90854e5 100755 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { Terminal } from 'lucide-react'; import TerminalButton from './components/TerminalButton'; import ProfileContent from './components/ProfileContent'; import ProjectsContent from './components/ProjectsContent'; +import TerminalShell from './components/TerminalShell'; function App() { const [activeSection, setActiveSection] = React.useState('about'); @@ -10,7 +11,7 @@ function App() { return (
{/* TV Container */} -
+
{/* TV Frame */}
{/* Screen Container with Convex Effect */} @@ -47,18 +48,18 @@ function App() { > SKILLS - setActiveSection('contact')} - isSelected={activeSection === 'contact'} - > - CONTACT - setActiveSection('resume')} isSelected={activeSection === 'resume'} > RESUME + setActiveSection('shell')} + isSelected={activeSection === 'shell'} + > + SHELL +
{/* Vertical Separator */} @@ -70,6 +71,7 @@ function App() { {activeSection === 'projects' && ( )} + {activeSection === 'shell' && }
diff --git a/src/components/TerminalShell.tsx b/src/components/TerminalShell.tsx new file mode 100644 index 0000000..3752ce7 --- /dev/null +++ b/src/components/TerminalShell.tsx @@ -0,0 +1,59 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Terminal } from 'lucide-react'; +import { System } from '../shell/system'; + +const TerminalShell: React.FC = () => { + const [history, setHistory] = useState(['Welcome to the terminal! Type "help" for commands.']); + const [currentCommand, setCurrentCommand] = useState(''); + const bottomRef = useRef(null); + const system = System.getInstance(); // Use singleton instead of creating new instance + + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [history]); + + const handleCommand = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + const command = currentCommand.trim(); + const [cmdName, ...args] = command.split(' '); + + if (system.knowsCommand(cmdName)) { + const output = system.executeCommand(cmdName, args); + + const newLines = output.split('\\n').filter(l => l); + + setHistory(prev => [...prev, `$ ${command}`, ...newLines]); + } else { + setHistory(prev => [...prev, `$ ${command}`, `Command not found: ${cmdName}`]); + } + setCurrentCommand(''); + } + }; + + return ( +
+
+ {history.map((line, index) => ( +
+ {line} +
+ ))} +
+
+
+ + $ + setCurrentCommand(e.target.value)} + onKeyDown={handleCommand} + className="flex-1 bg-transparent border-none outline-none text-[#00FF00]" + autoFocus + /> +
+
+ ); +}; + +export default TerminalShell; diff --git a/src/index.css b/src/index.css index a789814..7388d7a 100755 --- a/src/index.css +++ b/src/index.css @@ -173,7 +173,7 @@ transparent 100% ); animation: glare 3s ease-in-out infinite; - opacity: 0.5; + opacity: 0.2; } .screen-glare { @@ -187,6 +187,7 @@ transparent 100% ); animation: horizontal-glare 4s linear infinite; + opacity: 0.3; } } diff --git a/src/shell/FileSystem.ts b/src/shell/FileSystem.ts new file mode 100644 index 0000000..a95ecd0 --- /dev/null +++ b/src/shell/FileSystem.ts @@ -0,0 +1,109 @@ +interface INode { + name: string; + isDirectory: boolean; + content?: string; + children?: Map; +} + +export class FileSystem { + private root: INode; + + constructor() { + console.log('[FileSystem] Initializing new filesystem'); + this.root = { + name: '/', + isDirectory: true, + children: new Map() + }; + } + + createDirectory(path: string): void { + console.log(`[FileSystem] Creating directory: ${path}`); + const parts = path.split('/').filter(p => p); + let current = this.root; + + for (const part of parts) { + if (!current.children!.has(part)) { + console.log(`[FileSystem] Creating new directory node: ${part}`); + current.children!.set(part, { + name: part, + isDirectory: true, + children: new Map() + }); + } + current = current.children!.get(part)!; + } + } + + writeFile(path: string, content: string): void { + console.log(`[FileSystem] Writing file: ${path}`); + const parts = path.split('/').filter(p => p); + const fileName = parts.pop()!; + let current = this.root; + + for (const part of parts) { + if (!current.children!.has(part)) { + console.log(`[FileSystem] Creating parent directory: ${part}`); + this.createDirectory(part); + } + current = current.children!.get(part)!; + } + + current.children!.set(fileName, { + name: fileName, + isDirectory: false, + content + }); + console.log(`[FileSystem] File written: ${fileName}`); + } + + listDirectory(path: string): string[] { + console.log(`[FileSystem] Listing directory: ${path}`); + const parts = path.split('/').filter(p => p); + let current = this.root; + + for (const part of parts) { + if (!current.children!.has(part)) return []; + current = current.children!.get(part)!; + } + + const result = Array.from(current.children!.keys()); + console.log(`[FileSystem] Found ${result.length} entries`); + return result; + } + + readFile(path: string): string | null { + console.log(`[FileSystem] Reading file: ${path}`); + const parts = path.split('/').filter(p => p); + const fileName = parts.pop()!; + let current = this.root; + + for (const part of parts) { + if (!current.children!.has(part)) return null; + current = current.children!.get(part)!; + } + + const file = current.children!.get(fileName); + if (!file || file.isDirectory) { + console.log(`[FileSystem] File not found or is directory: ${path}`); + return null; + } + console.log(`[FileSystem] File read successfully: ${path}`); + return file.content!; + } + + deleteFile(path: string): void { + console.log(`[FileSystem] Deleting file: ${path}`); + const parts = path.split('/').filter(p => p); + const fileName = parts.pop()!; + let current = this.root; + + for (const part of parts) { + if (!current.children!.has(part)) return; + current = current.children!.get(part)!; + } + + current.children!.delete(fileName); + console.log(`[FileSystem] File deleted: ${path}`); + } +} diff --git a/src/shell/ShellSyscall.ts b/src/shell/ShellSyscall.ts new file mode 100644 index 0000000..bf58e54 --- /dev/null +++ b/src/shell/ShellSyscall.ts @@ -0,0 +1,43 @@ +import { FileSystem } from "./FileSystem"; +import axios from 'axios'; + +export class ShellSyscall { + constructor( + private fs: FileSystem, + private cwd: string + ) {} + + getCurrentDirectory(): string { + return this.cwd; + } + + listCurrentDirectory(): string[] { + return this.fs.listDirectory(this.cwd); + } + + changeDirectory(path: string): boolean { + // For now, just accept any path + this.cwd = path; + return true; + } + + createFile(filename: string): void { + this.fs.writeFile(`${this.cwd}/${filename}`, ''); + } + + readFile(filename: string): string { + return this.fs.readFile(`${this.cwd}/${filename}`) || ''; + } + + writeFile(filename: string, content: string) { + this.fs.writeFile(`${this.cwd}/${filename}`, content); + } + + async fetchUrl(url: string) { + return await axios.get(url); + } + + deleteFile(path: string): void { + this.fs.deleteFile(`${this.cwd}/${path}`); + } +} diff --git a/src/shell/commands/IShellCommand.ts b/src/shell/commands/IShellCommand.ts new file mode 100644 index 0000000..82a24f9 --- /dev/null +++ b/src/shell/commands/IShellCommand.ts @@ -0,0 +1,4 @@ +export interface IShellCommand { + getName(): string; + execute(input: string | string[]): string; +} \ No newline at end of file diff --git a/src/shell/commands/cat.ts b/src/shell/commands/cat.ts new file mode 100644 index 0000000..c5decee --- /dev/null +++ b/src/shell/commands/cat.ts @@ -0,0 +1,15 @@ +import { IShellCommand } from "./IShellCommand"; +import { ShellSyscall } from "../ShellSyscall"; + +export class cat implements IShellCommand { + constructor(private syscall: ShellSyscall) {} + + getName(): string { + return "cat"; + } + + execute(args: string[]): string { + if (args.length === 0) return "cat: missing file operand"; + return this.syscall.readFile(args[0]); + } +} diff --git a/src/shell/commands/cd.ts b/src/shell/commands/cd.ts new file mode 100644 index 0000000..afaf87a --- /dev/null +++ b/src/shell/commands/cd.ts @@ -0,0 +1,16 @@ +import { IShellCommand } from "./IShellCommand"; +import { ShellSyscall } from "../ShellSyscall"; + +export class cd implements IShellCommand { + constructor(private syscall: ShellSyscall) {} + + getName(): string { + return "cd"; + } + + execute(args: string[]): string { + const path = args[0] || '/'; + const success = this.syscall.changeDirectory(path); + return success ? '' : 'Invalid directory'; + } +} diff --git a/src/shell/commands/echo.ts b/src/shell/commands/echo.ts new file mode 100644 index 0000000..9128d46 --- /dev/null +++ b/src/shell/commands/echo.ts @@ -0,0 +1,14 @@ +import { IShellCommand } from "./IShellCommand"; + +export class echo implements IShellCommand { + getName(): string { + return 'echo'; + } + + execute(input: string | string[]): string { + if (Array.isArray(input)) { + return input.join(' ').trim(); + } + return input.trim(); + } +} \ No newline at end of file diff --git a/src/shell/commands/ls.ts b/src/shell/commands/ls.ts new file mode 100644 index 0000000..86e58c8 --- /dev/null +++ b/src/shell/commands/ls.ts @@ -0,0 +1,15 @@ +import { IShellCommand } from "./IShellCommand"; +import { ShellSyscall } from "../ShellSyscall"; + +export class ls implements IShellCommand { + constructor(private syscall: ShellSyscall) {} + + getName(): string { + return 'ls'; + } + + execute(dir: string[]): string { + const files = this.syscall.listCurrentDirectory(); + return files.join('\n'); + } +} diff --git a/src/shell/commands/pwd.ts b/src/shell/commands/pwd.ts new file mode 100644 index 0000000..197a2c3 --- /dev/null +++ b/src/shell/commands/pwd.ts @@ -0,0 +1,14 @@ +import { IShellCommand } from "./IShellCommand"; +import { ShellSyscall } from "../ShellSyscall"; + +export class pwd implements IShellCommand { + constructor(private syscall: ShellSyscall) {} + + getName(): string { + return "pwd"; + } + + execute(_: string[]): string { + return this.syscall.getCurrentDirectory(); + } +} diff --git a/src/shell/commands/rm.ts b/src/shell/commands/rm.ts new file mode 100644 index 0000000..0d7cbc1 --- /dev/null +++ b/src/shell/commands/rm.ts @@ -0,0 +1,24 @@ +import { IShellCommand } from "./IShellCommand"; +import { ShellSyscall } from "../ShellSyscall"; + +export class rm implements IShellCommand { + private syscall: ShellSyscall; + + constructor(syscall: ShellSyscall) { + this.syscall = syscall; + } + + getName(): string { + return "rm"; + } + + execute(args: string[]): string { + if (args.length < 1) { + return "Usage: rm "; + } + + const filename = args[0]; + this.syscall.deleteFile(filename); + return `Removed ${filename}`; + } +} diff --git a/src/shell/commands/touch.ts b/src/shell/commands/touch.ts new file mode 100644 index 0000000..f785b17 --- /dev/null +++ b/src/shell/commands/touch.ts @@ -0,0 +1,16 @@ +import { IShellCommand } from "./IShellCommand"; +import { ShellSyscall } from "../ShellSyscall"; + +export class touch implements IShellCommand { + constructor(private syscall: ShellSyscall) {} + + getName(): string { + return "touch"; + } + + execute(args: string[]): string { + if (args.length === 0) return "touch: missing file operand"; + this.syscall.createFile(args[0]); + return ""; + } +} diff --git a/src/shell/system.ts b/src/shell/system.ts new file mode 100644 index 0000000..b354dc7 --- /dev/null +++ b/src/shell/system.ts @@ -0,0 +1,58 @@ +import { echo } from "./commands/echo"; +import { ls } from "./commands/ls"; +import { pwd } from "./commands/pwd"; +import { cd } from "./commands/cd"; +import { touch } from "./commands/touch"; +import { cat } from "./commands/cat"; +import { rm } from "./commands/rm"; +import { IShellCommand } from "./commands/IShellCommand"; +import { FileSystem } from "./FileSystem"; +import { ShellSyscall } from "./ShellSyscall"; + +// System class to manage commands +class System { + private static instance: System | null = null; + private commands: Array; + private fs: FileSystem; + private cwd: string; + + constructor() { + this.commands = new Array(); + this.fs = new FileSystem(); + this.cwd = '/'; + this.initializeCommands(); + } + + private initializeCommands(): void { + const syscall = new ShellSyscall(this.fs, this.cwd); + this.commands.push(new echo()); + this.commands.push(new ls(syscall)); + this.commands.push(new pwd(syscall)); + this.commands.push(new cd(syscall)); + this.commands.push(new touch(syscall)); + this.commands.push(new cat(syscall)); + this.commands.push(new rm(syscall)); + } + + public static getInstance(): System { + if (!System.instance) { + System.instance = new System(); + } + return System.instance; + } + + knowsCommand(commandName: string): boolean { + return this.commands.some(c => c.getName() === commandName); + } + + executeCommand(commandName: string, args: string[]): string { + const command = this.commands.find(c => c.getName() === commandName); + if (command) { + return command.execute(args); + } + return `Command not found: ${commandName}`; + } +} + +// Export the System class and getInstance for use in other files +export { System }; \ No newline at end of file