From 290aa417f26e1b59495832e7b20371e606afba00 Mon Sep 17 00:00:00 2001 From: Ihor Stanovyi Date: Sun, 2 Nov 2025 21:43:15 +0200 Subject: [PATCH 1/7] feat: implement fs operations --- operations/fs.js | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 operations/fs.js diff --git a/operations/fs.js b/operations/fs.js new file mode 100644 index 0000000..f22a202 --- /dev/null +++ b/operations/fs.js @@ -0,0 +1,91 @@ +import fs from "fs/promises"; +import path from "path"; +import { createReadStream, createWriteStream } from "fs"; +import { pipeline } from "stream/promises"; + +export const listDirectory = async (currentPath) => { + const items = await fs.readdir(currentPath, { withFileTypes: true }); + + const dirs = []; + const files = []; + + for (const item of items) { + if (item.isDirectory()) { + dirs.push({ Name: item.name, Type: "directory" }); + } else { + files.push({ Name: item.name, Type: "file" }); + } + } + + dirs.sort((a, b) => a.Name.localeCompare(b.Name)); + files.sort((a, b) => a.Name.localeCompare(b.Name)); + + const combined = [...dirs, ...files]; + + console.table(combined); +}; + +export const readFile = async (currentPath, filename) => { + const filePath = path.resolve(currentPath, filename); + const stream = createReadStream(filePath, "utf8"); + + return new Promise((resolve, reject) => { + stream.on("data", (chunk) => { + process.stdout.write(chunk); + }); + + stream.on("end", () => { + console.log(); + resolve(); + }); + + stream.on("error", reject); + }); +}; + +export const createFile = async (currentPath, filename) => { + const filePath = path.resolve(currentPath, filename); + const handle = await fs.open(filePath, "wx"); + await handle.close(); +}; + +export const createDirectory = async (currentPath, dirname) => { + const dirPath = path.resolve(currentPath, dirname); + await fs.mkdir(dirPath); +}; + +export const renameFile = async (currentPath, oldName, newName) => { + const oldPath = path.resolve(currentPath, oldName); + const newPath = path.resolve(currentPath, newName); + await fs.rename(oldPath, newPath); +}; + +export const copyFile = async (currentPath, source, destination) => { + const srcPath = path.resolve(currentPath, source); + const destPath = path.resolve(currentPath, destination); + + const stats = await fs.stat(destPath).catch(() => null); + let finalDest = destPath; + + if (stats && stats.isDirectory()) { + const basename = path.basename(srcPath); + finalDest = path.join(destPath, basename); + } + + const readStream = createReadStream(srcPath); + const writeStream = createWriteStream(finalDest); + + await pipeline(readStream, writeStream); +}; + +export const moveFile = async (currentPath, source, destination) => { + await copyFile(currentPath, source, destination); + + const srcPath = path.resolve(currentPath, source); + await fs.unlink(srcPath); +}; + +export const deleteFile = async (currentPath, filename) => { + const filePath = path.resolve(currentPath, filename); + await fs.unlink(filePath); +}; From a85723768cf6fd613d13394732d21df13de898eb Mon Sep 17 00:00:00 2001 From: Ihor Stanovyi Date: Sun, 2 Nov 2025 21:44:10 +0200 Subject: [PATCH 2/7] feat: implement navigation operations --- operations/navigation.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 operations/navigation.js diff --git a/operations/navigation.js b/operations/navigation.js new file mode 100644 index 0000000..cbb6178 --- /dev/null +++ b/operations/navigation.js @@ -0,0 +1,24 @@ +import fs from "fs/promises"; +import path from "path"; + +export const goUp = (currentPath) => { + const parentDir = path.dirname(currentPath); + + if (parentDir === currentPath) { + return currentPath; + } + + return parentDir; +}; + +export const changeDirectory = async (currentPath, targetPath) => { + const newPath = path.resolve(currentPath, targetPath); + + const stats = await fs.stat(newPath); + + if (!stats.isDirectory()) { + throw new Error("Not a directory"); + } + + return newPath; +}; From bd16d811553ea3e8b9cd472d78d920e5fdb8850d Mon Sep 17 00:00:00 2001 From: Ihor Stanovyi Date: Sun, 2 Nov 2025 21:44:43 +0200 Subject: [PATCH 3/7] feat: implement os operations --- operations/os.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 operations/os.js diff --git a/operations/os.js b/operations/os.js new file mode 100644 index 0000000..967ae06 --- /dev/null +++ b/operations/os.js @@ -0,0 +1,33 @@ +import os from "os"; + +export const handleOsCommand = (arg) => { + switch (arg) { + case "--EOL": + console.log(JSON.stringify(os.EOL)); + break; + + case "--cpus": + const cpus = os.cpus(); + console.log(`Overall amount of CPUs: ${cpus.length}`); + cpus.forEach((cpu, idx) => { + const ghz = (cpu.speed / 1000).toFixed(2); + console.log(`CPU ${idx + 1}: ${cpu.model}, ${ghz} GHz`); + }); + break; + + case "--homedir": + console.log(os.homedir()); + break; + + case "--username": + console.log(os.userInfo().username); + break; + + case "--architecture": + console.log(os.arch()); + break; + + default: + throw new Error("Invalid input"); + } +}; From bddfe53000bf784068b851735170a48d8c4cec44 Mon Sep 17 00:00:00 2001 From: Ihor Stanovyi Date: Sun, 2 Nov 2025 21:45:11 +0200 Subject: [PATCH 4/7] feat: implement hash operations --- operations/hash.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 operations/hash.js diff --git a/operations/hash.js b/operations/hash.js new file mode 100644 index 0000000..ec2296e --- /dev/null +++ b/operations/hash.js @@ -0,0 +1,23 @@ +import { createReadStream } from "fs"; +import { createHash } from "crypto"; +import path from "path"; + +export const calculateHash = async (currentPath, filename) => { + const filePath = path.resolve(currentPath, filename); + const hash = createHash("sha256"); + const stream = createReadStream(filePath); + + return new Promise((resolve, reject) => { + stream.on("data", (chunk) => { + hash.update(chunk); + }); + + stream.on("end", () => { + const result = hash.digest("hex"); + console.log(result); + resolve(); + }); + + stream.on("error", reject); + }); +}; From 02974550ab4bf059cc6f0e75d91fca0c98b5d977 Mon Sep 17 00:00:00 2001 From: Ihor Stanovyi Date: Sun, 2 Nov 2025 21:45:52 +0200 Subject: [PATCH 5/7] feat: implement compress operations --- operations/compress.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 operations/compress.js diff --git a/operations/compress.js b/operations/compress.js new file mode 100644 index 0000000..d1fe96b --- /dev/null +++ b/operations/compress.js @@ -0,0 +1,26 @@ +import { createReadStream, createWriteStream } from "fs"; +import { createBrotliCompress, createBrotliDecompress } from "zlib"; +import { pipeline } from "stream/promises"; +import path from "path"; + +export const compressFile = async (currentPath, source, destination) => { + const srcPath = path.resolve(currentPath, source); + const destPath = path.resolve(currentPath, destination); + + const readStream = createReadStream(srcPath); + const brotli = createBrotliCompress(); + const writeStream = createWriteStream(destPath); + + await pipeline(readStream, brotli, writeStream); +}; + +export const decompressFile = async (currentPath, source, destination) => { + const srcPath = path.resolve(currentPath, source); + const destPath = path.resolve(currentPath, destination); + + const readStream = createReadStream(srcPath); + const brotli = createBrotliDecompress(); + const writeStream = createWriteStream(destPath); + + await pipeline(readStream, brotli, writeStream); +}; From ecc148bab622f56f8e8eaa5c55fd2ecb1d23f07b Mon Sep 17 00:00:00 2001 From: Ihor Stanovyi Date: Sun, 2 Nov 2025 21:47:42 +0200 Subject: [PATCH 6/7] feat: implement and finish index.js file --- index.js | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 2 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 index.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..e1953a9 --- /dev/null +++ b/index.js @@ -0,0 +1,176 @@ +import os from "os"; +import readline from "readline"; +import { stdin as input, stdout as output } from "process"; +import * as fsOps from "./operations/fs.js"; +import * as navOps from "./operations/navigation.js"; +import * as osOps from "./operations/os.js"; +import * as hashOps from "./operations/hash.js"; +import * as compressOps from "./operations/compress.js"; + +let currentDir = os.homedir(); +let username = "Anonymous"; + +const args = process.argv.slice(2); +for (let i = 0; i < args.length; i++) { + if (args[i].startsWith("--username=")) { + username = args[i].split("=")[1]; + break; + } +} + +const rl = readline.createInterface({ input, output, prompt: "" }); + +const showCurrentDir = () => { + console.log(`You are currently in ${currentDir}`); +}; + +const showPrompt = () => { + showCurrentDir(); + rl.prompt(); +}; + +const parseCommand = (input) => { + const trimmed = input.trim(); + const spaceIdx = trimmed.indexOf(" "); + + if (spaceIdx === -1) { + return { cmd: trimmed, args: [] }; + } + + const cmd = trimmed.substring(0, spaceIdx); + const argsStr = trimmed.substring(spaceIdx + 1).trim(); + const args = argsStr.split(" ").filter((a) => a.length > 0); + + return { cmd, args }; +}; + +const handleCommand = async (line) => { + const { cmd, args } = parseCommand(line); + + try { + switch (cmd) { + case "up": + currentDir = navOps.goUp(currentDir); + break; + + case "cd": + if (args.length === 0) { + throw new Error("Invalid input"); + } + currentDir = await navOps.changeDirectory(currentDir, args[0]); + break; + + case "ls": + await fsOps.listDirectory(currentDir); + break; + + case "cat": + if (args.length === 0) { + throw new Error("Invalid input"); + } + await fsOps.readFile(currentDir, args[0]); + break; + + case "add": + if (args.length === 0) { + throw new Error("Invalid input"); + } + await fsOps.createFile(currentDir, args[0]); + break; + + case "mkdir": + if (args.length === 0) { + throw new Error("Invalid input"); + } + await fsOps.createDirectory(currentDir, args[0]); + break; + + case "rn": + if (args.length < 2) { + throw new Error("Invalid input"); + } + await fsOps.renameFile(currentDir, args[0], args[1]); + break; + + case "cp": + if (args.length < 2) { + throw new Error("Invalid input"); + } + await fsOps.copyFile(currentDir, args[0], args[1]); + break; + + case "mv": + if (args.length < 2) { + throw new Error("Invalid input"); + } + await fsOps.moveFile(currentDir, args[0], args[1]); + break; + + case "rm": + if (args.length === 0) { + throw new Error("Invalid input"); + } + await fsOps.deleteFile(currentDir, args[0]); + break; + + case "os": + if (args.length === 0) { + throw new Error("Invalid input"); + } + osOps.handleOsCommand(args[0]); + break; + + case "hash": + if (args.length === 0) { + throw new Error("Invalid input"); + } + await hashOps.calculateHash(currentDir, args[0]); + break; + + case "compress": + if (args.length < 2) { + throw new Error("Invalid input"); + } + await compressOps.compressFile(currentDir, args[0], args[1]); + break; + + case "decompress": + if (args.length < 2) { + throw new Error("Invalid input"); + } + await compressOps.decompressFile(currentDir, args[0], args[1]); + break; + + case ".exit": + rl.close(); + return; + + default: + if (cmd.length > 0) { + throw new Error("Invalid input"); + } + } + } catch (err) { + if (err.message === "Invalid input") { + console.log("Invalid input"); + } else { + console.log("Operation failed"); + } + } + + showPrompt(); +}; + +console.log(`Welcome to the File Manager, ${username}!`); +showPrompt(); + +rl.on("line", handleCommand); + +rl.on("close", () => { + console.log(`Thank you for using File Manager, ${username}, goodbye!`); + process.exit(0); +}); + +process.on("SIGINT", () => { + rl.close(); +}); diff --git a/package.json b/package.json index a8766b0..6487b9c 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,9 @@ "name": "file-manager", "version": "1.0.0", "main": "index.js", + "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node index.js" }, "repository": { "type": "git", From 030b2812916270903c9642114c328ed2c572e2f6 Mon Sep 17 00:00:00 2001 From: Ihor Stanovyi Date: Sun, 2 Nov 2025 22:15:00 +0200 Subject: [PATCH 7/7] Add project description to README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3baa801 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# 📁 File Manager +A simple command-line file manager built with Node.js, allowing users to interact with the file system directly from the terminal — navigate directories, manage files, and perform compression/decompression operations.