diff --git a/Readme.md b/Readme.md deleted file mode 100644 index 611a505f49..0000000000 --- a/Readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# Node.js basics - -## !!! Please don't submit Pull Requests to this repository !!! diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000000..2e03e8f24f --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,5 @@ +{ + // "compilerOptions": { "checkJs": true }, + "typeAcquisition": { "include": ["node"] }, + "exclude": ["node_modules"] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..8d53355a27 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "file-manager", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "file-manager", + "version": "1.0.0", + "license": "ISC", + "engines": { + "node": ">=24.10.0", + "npm": ">=10.9.2" + } + } + } +} diff --git a/package.json b/package.json index 3108c6c969..57c0837b35 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,18 @@ { - "name": "node-nodejs-basics", - "version": "1.0.0", - "description": "This repository is the part of nodejs-assignments https://github.com/AlreadyBored/nodejs-assignments", - "engines": { - "node": ">=24.10.0", - "npm": ">=10.9.2" - }, - "type": "module", - "scripts": { - "cli:args": "node src/cli/args.js --some-arg value1 --other 1337 --arg2 42", - "cli:env": "npx cross-env SOME=any RSS_foo=bar RSS_bar=baz node src/cli/env.js", - "cp": "node src/cp/cp.js", - "fs:copy": "node src/fs/copy.js", - "fs:create": "node src/fs/create.js", - "fs:delete": "node src/fs/delete.js", - "fs:list": "node src/fs/list.js", - "fs:read": "node src/fs/read.js", - "fs:rename": "node src/fs/rename.js", - "hash": "node src/hash/calcHash.js", - "modules": "node src/modules/esm.mjs", - "streams:read": "node src/streams/read.js", - "streams:transform": "node src/streams/transform.js", - "streams:write": "node src/streams/write.js", - "wt": "node src/wt/main.js", - "zip:compress": "node src/zip/compress.js", - "zip:decompress": "node src/zip/decompress.js" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/AlreadyBored/node-nodejs-basics.git" - }, - "keywords": [ - "nodejs", - "assignments", - "alreadybored" - ], - "author": "alreadybored", - "license": "ISC", - "bugs": { - "url": "https://github.com/AlreadyBored/node-nodejs-basics/issues" - }, - "homepage": "https://github.com/AlreadyBored/node-nodejs-basics#readme" + "name": "file-manager", + "version": "1.0.0", + "engines": { + "node": ">=24.10.0", + "npm": ">=10.9.2" + }, + "type": "module", + "scripts": { + "start": "node src/index.js" + }, + "author": "evgkamok", + "license": "ISC", + "bugs": { + "url": "https://github.com/AlreadyBored/nodejs-assignments/issues" + }, + "homepage": "https://github.com/AlreadyBored/nodejs-assignments/blob/main/assignments/file-manager/assignment.md" } diff --git a/src/cli/args.js b/src/cli/args.js deleted file mode 100644 index 9e3622f791..0000000000 --- a/src/cli/args.js +++ /dev/null @@ -1,5 +0,0 @@ -const parseArgs = () => { - // Write your code here -}; - -parseArgs(); diff --git a/src/cli/env.js b/src/cli/env.js deleted file mode 100644 index e3616dc8e7..0000000000 --- a/src/cli/env.js +++ /dev/null @@ -1,5 +0,0 @@ -const parseEnv = () => { - // Write your code here -}; - -parseEnv(); diff --git a/src/cli/interface.js b/src/cli/interface.js new file mode 100644 index 0000000000..a24793ce37 --- /dev/null +++ b/src/cli/interface.js @@ -0,0 +1,47 @@ +import { createInterface } from 'node:readline/promises' + +export function createCLIInterface(username, commandHandler) { + const rl = createInterface({ + input: process.stdin, + output: process.stdout, + prompt: '> ', + }) + + rl.on('line', async input => { + const command = input.trim() + + if (command === '.exit') { + rl.close() + return + } + + if (command) { + try { + await commandHandler(command) + } catch (error) { + console.log('Operation failed') + + if (error.code === 'ENOENT') { + console.log('ENOENT: no such file or directory') + } else { + console.log(error.message) + } + } + } + + console.log(`You are currently in ${process.cwd()}`) + + rl.prompt() + }) + + rl.on('close', () => { + console.log(`\nThank you for using File Manager, ${username}, goodbye!`) + process.exit(0) + }) + + rl.on('SIGINT', () => { + rl.close() + }) + + return rl +} diff --git a/src/cli/parser.js b/src/cli/parser.js new file mode 100644 index 0000000000..e55df3f52b --- /dev/null +++ b/src/cli/parser.js @@ -0,0 +1,26 @@ +import { parseArgs } from 'node:util' + +export function parseCliArgs() { + try { + const args = process.argv.slice(2) + + const options = { + username: { + type: 'string', + }, + } + + const { values } = parseArgs({ args, options }) + + if (!values.username) { + return 'user' + } else { + return { + username: values.username, + } + } + } catch (error) { + console.error('Error', error.message) + process.exit(1) + } +} diff --git a/src/commands/compress.js b/src/commands/compress.js new file mode 100644 index 0000000000..bda6898a92 --- /dev/null +++ b/src/commands/compress.js @@ -0,0 +1,37 @@ +import { createReadStream, createWriteStream } from 'node:fs' +import path from 'node:path' +import { pipeline } from 'node:stream/promises' +import { createGunzip, createGzip } from 'node:zlib' + +export async function compress(srcPath, dstPath) { + if (!srcPath && !dstPath) { + throw new Error('Both arguments is required') + } + + const absSrcPath = path.resolve(srcPath) + const absDstPath = path.resolve(dstPath) + + const source = createReadStream(absSrcPath) + const destination = createWriteStream(absDstPath) + const gzip = createGzip() + + await pipeline(source, gzip, destination) + console.log(`Compressed successfully to ${absDstPath}`) +} + +export async function decompress(srcPath, dstPath) { + if (!srcPath || !dstPath) { + throw new Error('Both arguments are required') + } + + const absSrcPath = path.resolve(srcPath) + const absDstPath = path.resolve(dstPath) + + const source = createReadStream(absSrcPath) + const destination = createWriteStream(absDstPath) + const gunZlib = createGunzip() + + await pipeline(source, gunZlib, destination) + + console.log(`Decompressed successfully to ${absDstPath}`) +} diff --git a/src/commands/files.js b/src/commands/files.js new file mode 100644 index 0000000000..f4b4010de7 --- /dev/null +++ b/src/commands/files.js @@ -0,0 +1,93 @@ +import { + readFile, + rename, + writeFile, + copyFile, + unlink, + mkdir as creteDirectory, +} from 'node:fs/promises' +import path from 'node:path' + +export async function cat(filePath) { + if (!filePath) { + throw new Error('File path is required') + } + + const absFilePath = path.resolve(filePath) + const content = await readFile(absFilePath, 'utf-8') + + process.stdout.write(content + '\n') +} + +export async function add(fileName) { + if (!fileName) { + throw new Error('File name is required') + } + + const filePath = path.join(process.cwd(), fileName) + + await writeFile(filePath, '', { flag: 'wx' }) + + console.log(`File ${fileName} successfully created`) +} + +export async function rn(oldFilePath, nameNewFile) { + if (!oldFilePath || !nameNewFile) { + throw new Error('Both arguments are required') + } + + const absOldFilePath = path.resolve(oldFilePath) + const nameDirectory = path.dirname(absOldFilePath) + const absNewFilePath = path.join(nameDirectory, nameNewFile) + + await rename(absOldFilePath, absNewFilePath) + console.log(`File successfully renamed`) +} + +export async function cp(srcPath, destDir) { + if (!srcPath || !destDir) { + throw new Error('Both arguments are required') + } + + const absSrcPath = path.resolve(srcPath) + const srcFileName = path.basename(absSrcPath) + const absDestPath = path.resolve(destDir) + + const destPath = path.join(absDestPath, srcFileName) + + await copyFile(absSrcPath, destPath) + console.log(`File copied successfully`) +} + +export async function mv(srcPath, destDir) { + if (!srcPath || !destDir) { + throw new Error('Both arguments are required') + } + + const absSrcPath = path.resolve(srcPath) + const absDestPath = path.resolve(destDir) + const srcFileName = path.basename(srcPath) + const targetPath = path.join(absDestPath, srcFileName) + + await rename(absSrcPath, targetPath) + console.log(`File moved successfully`) +} + +export async function rm(filePath) { + if (!filePath) { + throw new Error('File path argument is required') + } + + const absFilePath = path.resolve(filePath) + await unlink(absFilePath) + console.log(`File deleted successfully`) +} + +export async function mkdir(nameDir) { + if (!nameDir) { + throw new Error('Name directory is required') + } + + await creteDirectory(nameDir) + console.log(`Directory successfully created`) +} diff --git a/src/commands/hash.js b/src/commands/hash.js new file mode 100644 index 0000000000..c56f5a04be --- /dev/null +++ b/src/commands/hash.js @@ -0,0 +1,22 @@ +import { createHash } from 'node:crypto' +import { createReadStream } from 'node:fs' +import path from 'node:path' + +export async function hash(filePath) { + if (!filePath) { + throw new Error('File path is required') + } + + const absFilePath = path.resolve(filePath) + const rdStream = createReadStream(absFilePath) + + const hash = createHash('sha256') + + for await (const chunk of rdStream) { + hash.update(chunk) + } + + const hex = hash.digest('hex') + console.log(`SHA-256 hash: ${hex}`) + return hex +} diff --git a/src/commands/navigation.js b/src/commands/navigation.js new file mode 100644 index 0000000000..b465710f06 --- /dev/null +++ b/src/commands/navigation.js @@ -0,0 +1,52 @@ +import { readdir } from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' + +export async function up() { + const currentDirPath = process.cwd() + process.chdir('..') + + if (currentDirPath === process.cwd()) { + console.warn('Already at root directory') + } +} + +export async function cd(targetPath) { + if (!targetPath) { + throw new Error('Path is required') + } + + const absDstPath = path.resolve(targetPath) + process.chdir(absDstPath) +} + +export async function ls() { + const entries = await readdir(process.cwd(), { withFileTypes: true }) + + const directories = entries + .filter(entry => entry.isDirectory()) + .map(entry => ({ + Name: entry.name, + Type: 'directory', + })) + + const files = entries + .filter(entry => entry.isFile()) + .map(entry => ({ + Name: entry.name, + Type: 'file', + })) + + const contentDir = [...directories, ...files] + + console.table(contentDir) + + if (directories.length === 0 && files.length === 0) { + console.log('Directory is empty') + } +} + +export async function goHomeDir() { + const homePath = os.homedir() + process.chdir(homePath) +} diff --git a/src/commands/os.js b/src/commands/os.js new file mode 100644 index 0000000000..a6723eef31 --- /dev/null +++ b/src/commands/os.js @@ -0,0 +1,39 @@ +import os from 'node:os' + +export async function osInfo(flag) { + if (!flag) { + throw new Error('Flag is required') + } + + try { + switch (flag) { + case '--EOL': + const EOL = JSON.stringify(os.EOL) + console.log(`default system End-Of-Line is - ${EOL}`) + break + case '--cpus': + const cpus = os.cpus() + console.log('Total count CPU - ', cpus.length) + cpus.forEach((cpu, index) => { + console.log( + `${index + 1}. ${cpu.model} - ${(cpu.speed / 1000).toFixed(2)}Ghz` + ) + }) + break + case '--homedir': + console.log(`Home directory is - ${os.homedir}`) + break + case '--username': + const userInfo = os.userInfo() + console.log(`Username is ${userInfo.username}`) + break + case '--architecture': + console.log(`CPU arch is ${os.arch}`) + break + default: + throw new Error('Invalid OS flag') + } + } catch (error) { + throw new Error('Failed to get OS information') + } +} diff --git a/src/cp/cp.js b/src/cp/cp.js deleted file mode 100644 index 72c6addc9c..0000000000 --- a/src/cp/cp.js +++ /dev/null @@ -1,6 +0,0 @@ -const spawnChildProcess = async (args) => { - // Write your code here -}; - -// Put your arguments in function call to test this functionality -spawnChildProcess( /* [someArgument1, someArgument2, ...] */); diff --git a/src/cp/files/script.js b/src/cp/files/script.js deleted file mode 100644 index 0c6654f12f..0000000000 --- a/src/cp/files/script.js +++ /dev/null @@ -1,19 +0,0 @@ -import { EOL } from 'node:os'; -import { argv, stdout, stdin, exit } from 'node:process'; - -const args = argv.slice(2); - -console.log(`Total number of arguments is ${args.length}`); -console.log(`Arguments: ${JSON.stringify(args)}${EOL}`); - -const echoInput = (chunk) => { - const chunkStringified = chunk.toString(); - - if (chunkStringified.includes('CLOSE')) { - exit(0); - } - - stdout.write(`Received from master process: ${chunk.toString()}${EOL}`); -}; - -stdin.on('data', echoInput); diff --git a/src/fs/copy.js b/src/fs/copy.js deleted file mode 100644 index e226075b4c..0000000000 --- a/src/fs/copy.js +++ /dev/null @@ -1,5 +0,0 @@ -const copy = async () => { - // Write your code here -}; - -await copy(); diff --git a/src/fs/create.js b/src/fs/create.js deleted file mode 100644 index 6ede285599..0000000000 --- a/src/fs/create.js +++ /dev/null @@ -1,5 +0,0 @@ -const create = async () => { - // Write your code here -}; - -await create(); diff --git a/src/fs/delete.js b/src/fs/delete.js deleted file mode 100644 index a70b13766c..0000000000 --- a/src/fs/delete.js +++ /dev/null @@ -1,5 +0,0 @@ -const remove = async () => { - // Write your code here -}; - -await remove(); diff --git a/src/fs/files/dontLookAtMe.txt b/src/fs/files/dontLookAtMe.txt deleted file mode 100644 index 8979bab743..0000000000 --- a/src/fs/files/dontLookAtMe.txt +++ /dev/null @@ -1 +0,0 @@ -What are you looking at?! \ No newline at end of file diff --git a/src/fs/files/fileToRead.txt b/src/fs/files/fileToRead.txt deleted file mode 100644 index 5d66c332d6..0000000000 --- a/src/fs/files/fileToRead.txt +++ /dev/null @@ -1,7 +0,0 @@ -My content -should -be -printed -into -console -! \ No newline at end of file diff --git a/src/fs/files/fileToRemove.txt b/src/fs/files/fileToRemove.txt deleted file mode 100644 index 43e64cd45c..0000000000 --- a/src/fs/files/fileToRemove.txt +++ /dev/null @@ -1 +0,0 @@ -How dare you! \ No newline at end of file diff --git a/src/fs/files/hello.txt b/src/fs/files/hello.txt deleted file mode 100644 index 4e65f7775f..0000000000 --- a/src/fs/files/hello.txt +++ /dev/null @@ -1 +0,0 @@ -Hello Node.js \ No newline at end of file diff --git a/src/fs/files/wrongFilename.txt b/src/fs/files/wrongFilename.txt deleted file mode 100644 index 38cca5db19..0000000000 --- a/src/fs/files/wrongFilename.txt +++ /dev/null @@ -1,3 +0,0 @@ -# This is a file with a wrong filename - -Hello from **markdown**! \ No newline at end of file diff --git a/src/fs/list.js b/src/fs/list.js deleted file mode 100644 index 0c0fa21f7e..0000000000 --- a/src/fs/list.js +++ /dev/null @@ -1,5 +0,0 @@ -const list = async () => { - // Write your code here -}; - -await list(); diff --git a/src/fs/read.js b/src/fs/read.js deleted file mode 100644 index e3938be563..0000000000 --- a/src/fs/read.js +++ /dev/null @@ -1,5 +0,0 @@ -const read = async () => { - // Write your code here -}; - -await read(); diff --git a/src/fs/rename.js b/src/fs/rename.js deleted file mode 100644 index b1d65b0c86..0000000000 --- a/src/fs/rename.js +++ /dev/null @@ -1,5 +0,0 @@ -const rename = async () => { - // Write your code here -}; - -await rename(); diff --git a/src/hash/calcHash.js b/src/hash/calcHash.js deleted file mode 100644 index e37c17ed62..0000000000 --- a/src/hash/calcHash.js +++ /dev/null @@ -1,5 +0,0 @@ -const calculateHash = async () => { - // Write your code here -}; - -await calculateHash(); diff --git a/src/hash/files/fileToCalculateHashFor.txt b/src/hash/files/fileToCalculateHashFor.txt deleted file mode 100644 index 08f56564f8..0000000000 --- a/src/hash/files/fileToCalculateHashFor.txt +++ /dev/null @@ -1 +0,0 @@ -Calculate hash for me! \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000000..5235d9035c --- /dev/null +++ b/src/index.js @@ -0,0 +1,76 @@ +import { createCLIInterface } from './cli/interface.js' +import { parseCliArgs } from './cli/parser.js' +import { cd, goHomeDir, ls, up } from './commands/navigation.js' +import { add, cat, cp, mkdir, mv, rm, rn } from './commands/files.js' +import { hash } from './commands/hash.js' +import { osInfo } from './commands/os.js' +import { compress, decompress } from './commands/compress.js' + +const { username } = parseCliArgs() + +async function handleCommand(command) { + const [cmd, ...args] = command.split(' ') + + switch (cmd) { + // NAVIGATION + case 'up': + await up() + break + case 'cd': + await cd(args[0]) + break + case 'ls': + await ls() + break + + // FILE OPERATION + case 'cat': + await cat(args[0]) + break + case 'add': + await add(args[0]) + break + case 'rn': + await rn(args[0], args[1]) + break + case 'cp': + await cp(args[0], args[1]) + break + case 'mv': + await mv(args[0], args[1]) + break + case 'rm': + await rm(args[0], args[1]) + break + case 'mkdir': + await mkdir(args[0]) + break + + //OS + case 'os': + await osInfo(args[0]) + break + + // HASH + case 'hash': + await hash(args[0]) + break + + // COMPRESS / DECOMPRESS + case 'compress': + await compress(args[0], args[1]) + break + case 'decompress': + await decompress(args[0], args[1]) + break + default: + console.log('Invalid input') + } +} + +goHomeDir() +console.log(`Welcome to the File Manager, ${username}!`) +console.log(`You are currently in ${process.cwd()}`) + +const rl = createCLIInterface(username, handleCommand) +rl.prompt() diff --git a/src/modules/cjsToEsm.cjs b/src/modules/cjsToEsm.cjs deleted file mode 100644 index 089bd2db13..0000000000 --- a/src/modules/cjsToEsm.cjs +++ /dev/null @@ -1,34 +0,0 @@ -const path = require('node:path'); -const { release, version } = require('node:os'); -const { createServer: createServerHttp } = require('node:http'); - -require('./files/c.cjs'); - -const random = Math.random(); - -const unknownObject = random > 0.5 ? require('./files/a.json') : require('./files/b.json'); - -console.log(`Release ${release()}`); -console.log(`Version ${version()}`); -console.log(`Path segment separator is "${path.sep}"`); - -console.log(`Path to current file is ${__filename}`); -console.log(`Path to current directory is ${__dirname}`); - -const myServer = createServerHttp((_, res) => { - res.end('Request accepted'); -}); - -const PORT = 3000; - -console.log(unknownObject); - -myServer.listen(PORT, () => { - console.log(`Server is listening on port ${PORT}`); - console.log('To terminate it, use Ctrl+C combination'); -}); - -module.exports = { - unknownObject, - myServer, -}; diff --git a/src/modules/files/a.json b/src/modules/files/a.json deleted file mode 100644 index d1f6dac48a..0000000000 --- a/src/modules/files/a.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "a": 1, - "b": 2, - "c": 3 -} diff --git a/src/modules/files/b.json b/src/modules/files/b.json deleted file mode 100644 index e442128649..0000000000 --- a/src/modules/files/b.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "a": 11, - "b": 22, - "c": 33 -} diff --git a/src/modules/files/c.cjs b/src/modules/files/c.cjs deleted file mode 100644 index df728e1627..0000000000 --- a/src/modules/files/c.cjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Hello from c.cjs!'); diff --git a/src/streams/files/fileToRead.txt b/src/streams/files/fileToRead.txt deleted file mode 100644 index c7e8d132a1..0000000000 --- a/src/streams/files/fileToRead.txt +++ /dev/null @@ -1 +0,0 @@ -This file should be read using Streams API \ No newline at end of file diff --git a/src/streams/files/fileToWrite.txt b/src/streams/files/fileToWrite.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/streams/read.js b/src/streams/read.js deleted file mode 100644 index e3938be563..0000000000 --- a/src/streams/read.js +++ /dev/null @@ -1,5 +0,0 @@ -const read = async () => { - // Write your code here -}; - -await read(); diff --git a/src/streams/transform.js b/src/streams/transform.js deleted file mode 100644 index 9e6c15fe84..0000000000 --- a/src/streams/transform.js +++ /dev/null @@ -1,5 +0,0 @@ -const transform = async () => { - // Write your code here -}; - -await transform(); diff --git a/src/streams/write.js b/src/streams/write.js deleted file mode 100644 index 84aa11e7cb..0000000000 --- a/src/streams/write.js +++ /dev/null @@ -1,5 +0,0 @@ -const write = async () => { - // Write your code here -}; - -await write(); diff --git a/src/wt/main.js b/src/wt/main.js deleted file mode 100644 index e2ef054d41..0000000000 --- a/src/wt/main.js +++ /dev/null @@ -1,5 +0,0 @@ -const performCalculations = async () => { - // Write your code here -}; - -await performCalculations(); diff --git a/src/wt/worker.js b/src/wt/worker.js deleted file mode 100644 index 405595394d..0000000000 --- a/src/wt/worker.js +++ /dev/null @@ -1,8 +0,0 @@ -// n should be received from main thread -const nthFibonacci = (n) => n < 2 ? n : nthFibonacci(n - 1) + nthFibonacci(n - 2); - -const sendResult = () => { - // This function sends result of nthFibonacci computations to main thread -}; - -sendResult(); diff --git a/src/zip/compress.js b/src/zip/compress.js deleted file mode 100644 index d55209587e..0000000000 --- a/src/zip/compress.js +++ /dev/null @@ -1,5 +0,0 @@ -const compress = async () => { - // Write your code here -}; - -await compress(); diff --git a/src/zip/decompress.js b/src/zip/decompress.js deleted file mode 100644 index 8aaf26c8a4..0000000000 --- a/src/zip/decompress.js +++ /dev/null @@ -1,5 +0,0 @@ -const decompress = async () => { - // Write your code here -}; - -await decompress(); diff --git a/src/zip/files/fileToCompress.txt b/src/zip/files/fileToCompress.txt deleted file mode 100644 index 4d4efc82fe..0000000000 --- a/src/zip/files/fileToCompress.txt +++ /dev/null @@ -1 +0,0 @@ -Compress me! \ No newline at end of file