diff --git a/.gitignore b/.gitignore index 3c3629e..eb79dd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +.idea diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/file-manager.iml b/.idea/file-manager.iml deleted file mode 100644 index 24643cc..0000000 --- a/.idea/file-manager.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml deleted file mode 100644 index d23208f..0000000 --- a/.idea/jsLibraryMappings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 1a85d97..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/FileManager/FileManager.js b/FileManager/FileManager.js new file mode 100644 index 0000000..25dada2 --- /dev/null +++ b/FileManager/FileManager.js @@ -0,0 +1,449 @@ +import os from 'os'; +import readline from 'readline'; +import path from 'path'; +import fs from 'fs'; +import stream from 'stream'; +import {StatsService} from './services/StatsService.js'; +import {StreamsService} from './services/StreamsService.js'; +import {PathService} from './services/PathService.js'; +import {CompressService} from './services/CompressService.js'; +import {Converter} from './services/Converter.js'; + + +export class FileManager { + static #invalidSinglePathMessage = 'Invalid path argument'; + static #unknownAttributeMessage = 'Unknown attribute'; + static #invalid2PathsMessage = 'Invalid paths argument. 2 paths must be provided. If file-path contains spaces, it should be wrapped with "quoues"'; + + #currentDirectory; + + constructor() { + this.#currentDirectory = os.homedir(); + } + + async run() { + const consoleInputReader = readline.promises.createInterface({ + input: process.stdin, output: process.stdout + }); + + while (true) { + console.info(`Current directory: ${this.#currentDirectory}`); + + const input = await consoleInputReader.question(''); + const splittedInput = input.split(' '); + const command = splittedInput.at(0); + const restPartOfInput = splittedInput.slice(1).join(' '); + + switch (command) { + case 'up': { + this.#up(); + break; + } + + case 'cd': { + const parsed = PathService.extractFilePathFromString(restPartOfInput, 1); + if (!parsed.parseStatusSuccess) { + console.warn(FileManager.#invalidSinglePathMessage); + break; + } + + await this.#cd(parsed.paths[0]); + break; + } + + case 'ls': { + await this.#ls(); + break; + } + + case 'cat': { + const parsed = PathService.extractFilePathFromString(restPartOfInput, 1); + if (!parsed.parseStatusSuccess) { + console.warn(FileManager.#invalidSinglePathMessage); + break; + } + + await this.#cat(parsed.paths[0]); + break; + } + + case 'add': { + const parsed = PathService.extractFilePathFromString(restPartOfInput, 1); + if (!parsed.parseStatusSuccess) { + console.warn(FileManager.#invalidSinglePathMessage); + break; + } + await this.#add(parsed.paths[0]); + break; + } + + case 'rn': { + const parsed = PathService.extractFilePathFromString(restPartOfInput, 2); + if (!parsed.parseStatusSuccess) { + console.warn(FileManager.#invalid2PathsMessage); + break; + } + await this.#rn(...parsed.paths); + break; + } + + case 'cp': { + const parsed = PathService.extractFilePathFromString(restPartOfInput, 2); + if (!parsed.parseStatusSuccess) { + console.warn(FileManager.#invalid2PathsMessage); + break; + } + await this.#cp(...parsed.paths); + break; + } + + case 'mv': { + const parsed = PathService.extractFilePathFromString(restPartOfInput, 2); + if (!parsed.parseStatusSuccess) { + console.warn(FileManager.#invalid2PathsMessage); + break; + } + await this.#mv(...parsed.paths); + break; + } + + case 'rm': { + const parsed = PathService.extractFilePathFromString(restPartOfInput, 1); + if (!parsed.parseStatusSuccess) { + console.warn(FileManager.#invalidSinglePathMessage); + break; + } + + await this.#rm(parsed.paths[0]); + break; + } + + case 'os': { + const argument = splittedInput.at(1); + this.#os(argument); + break; + } + + case 'hash': { + const parsed = PathService.extractFilePathFromString(restPartOfInput, 1); + if (!parsed.parseStatusSuccess) { + console.warn(FileManager.#invalidSinglePathMessage); + break; + } + + await this.#hashFile(parsed.paths.at(0)); + break; + } + + case 'compress': { + const parsed = PathService.extractFilePathFromString(restPartOfInput, 2); + if (!parsed.parseStatusSuccess) { + console.warn(FileManager.#invalid2PathsMessage); + break; + } + + await this.#compress(...parsed.paths); + break; + } + + case 'decompress': { + const enteredFilename = PathService.extractFilePathFromString(restPartOfInput, 2); + if (!enteredFilename.parseStatusSuccess) { + console.warn(FileManager.#invalid2PathsMessage); + break; + } + await this.#decompress(...enteredFilename.paths); + break; + } + + case '.exit': { + consoleInputReader.close(); + return; + } + + case '': { + console.warn('Empty command'); + break; + } + + default: { + console.warn(`Unknown command "${command}"`); + break; + } + } + } + } + + #up() { + this.#currentDirectory = PathService.toAbsolute(this.#currentDirectory, '../'); + } + + async #cd(enteredPath) { + const absolutePath = PathService.toAbsolute(this.#currentDirectory, enteredPath); + const doesPathExist = await StatsService.doesPathExist(absolutePath); + + if (!doesPathExist) { + console.warn('Path does not exist'); + return; + } + + const isFolder = (await StatsService.stats(absolutePath)).isDirectory(); + + if (!isFolder) { + console.warn('Can\'t move into non-directory'); + return; + } + + this.#currentDirectory = absolutePath; + } + + async #ls() { + const list = await new Promise(resolve => { + fs.readdir(this.#currentDirectory, (error, files) => resolve(files)); + }); + + const table = await Promise.all(list.map(async entity => { + const fullPath = PathService.toAbsolute(this.#currentDirectory, entity); + const stats = await StatsService.stats(fullPath); + + try { + if (stats.isDirectory()) { + return [entity, 'directory']; + } else if (stats.isFile()) { + return [entity, 'file']; + } else if (stats.isSymbolicLink()) { + return [entity, 'symbolic link']; + } else { + return [entity, 'other']; + } + } catch (error) { + return [entity, 'unknown']; + } + })); + + console.table(table); + } + + async #cat(filePath) { + const absolutePath = PathService.toAbsolute(this.#currentDirectory, filePath); + try { + const data = await StreamsService.readFile(absolutePath); + console.info(data); + } catch { + console.warn('Unable to read contents by passed path'); + } + } + + async #add(filename) { + const absolutePath = PathService.toAbsolute(this.#currentDirectory, filename); + + const doesAlreadyExist = await StatsService.doesPathExist(absolutePath); + if (doesAlreadyExist) { + console.warn('File with such name already exists. Aborting'); + return; + } + + await new Promise(resolve => { + fs.open(absolutePath, 'w', (err, descriptor) => { + if (err) { + console.info(err.message); + resolve(); + } + + fs.close(descriptor, () => { + resolve(); + }); + }); + }); + } + + async #rn(source, destination) { + const [sourceAbsolute, destinationAbsolute] = [PathService.toAbsolute(this.#currentDirectory, source), PathService.toAbsolute(this.#currentDirectory, destination)]; + + await new Promise(resolve => { + fs.rename(sourceAbsolute, destinationAbsolute, error => { + if (error) { + console.log(error.message); + } + resolve(); + }); + }); + } + + async #cp(source, destination) { + const absoluteSource = PathService.toAbsolute(this.#currentDirectory, source); + const copiedFileName = PathService.getFullFilename(absoluteSource); + const absoluteDestination = path.resolve(PathService.toAbsolute(this.#currentDirectory, destination), copiedFileName); + + const isSourceAFile = (await StatsService.stats(absoluteSource))?.isFile(); + + if (!isSourceAFile) { + console.warn('cp command works only with file types and existing source-paths'); + return false; + } + + try { + const sourceStream = StreamsService.getReadStream(absoluteSource); + const destinationStream = StreamsService.getWriteStream(absoluteDestination, {flags: 'wx+'}); + + await stream.promises.pipeline(sourceStream, destinationStream); + } catch { + console.warn('Unable to copy. Some error occurred. Maybe some path is invalid or target file already exists in destination folder'); + return false; + } + + return true; + } + + async #rm(path) { + const absolutePath = PathService.toAbsolute(this.#currentDirectory, path); + + await new Promise(resolve => { + fs.rm(absolutePath, error => { + if (error) { + console.warn(error.message); + } + + resolve(); + }); + }); + } + + async #mv(source, destination) { + const isCopySuccessful = await this.#cp(source, destination); + + if (isCopySuccessful) { + await this.#rm(source); + } else { + console.warn('Failed to execute mv command'); + } + } + + #os(argument) { + switch (argument) { + case '--EOL': { + console.info(os.EOL); + break; + } + case '--cpus': { + const table = os.cpus().map(cpu => { + return [cpu.model, `${Converter.MHzToGHz(cpu.speed)} GHz`]; + }); + console.info(`CPUs count: ${table.length}`); + console.table(table); + break; + } + case '--homedir': { + console.info(os.homedir()); + break; + } + case '--username': { + console.info(os.userInfo().username); + break; + } + case '--architecture': { + console.info(os.arch()); + break; + } + case undefined: { + console.info('os command requires an attribute'); + break; + } + default: { + console.info(FileManager.#unknownAttributeMessage); + break; + } + } + } + + async #hashFile(filePath) { + const absolutePath = PathService.toAbsolute(this.#currentDirectory, filePath); + + try { + const fileData = await StreamsService.readFile(absolutePath); + const hashedValue = CompressService.hash(fileData); + console.info(hashedValue); + } catch { + console.warn('Provided path is not a file or not valid or unable to hash such contents'); + } + } + + async #compress(source, destination) { + const absoluteSource = PathService.toAbsolute(this.#currentDirectory, source); + const absoluteDestinationFolder = PathService.toAbsolute(this.#currentDirectory, destination); + + const destinationFolderExists = await new Promise(resolve => fs.exists(absoluteDestinationFolder, resolve)); + + if (!destinationFolderExists) { + console.warn('Destination folder does not exist: ' + absoluteDestinationFolder); + return; + } + + const absoluteDestination = path.resolve( + absoluteDestinationFolder, + PathService.getFullFilename(source) + CompressService.compressExtension + ); + + const destinationExists = await new Promise(resolve => fs.exists(absoluteDestination, resolve)); + + if (destinationExists) { + console.warn('Destination file already exists: ' + absoluteDestination); + return; + } + + try { + await fs.promises.access(absoluteSource); + + const sourceStream = StreamsService.getReadStream(absoluteSource); + const destinationStream = StreamsService.getWriteStream(absoluteDestination, {flags: 'wx+'}); + + await CompressService.compressWithBrotli(sourceStream, destinationStream); + } catch (error) { + console.warn('Unable to compress. Paths may be incorrect, or destination folder already contains compressed file with same name'); + console.warn(error.message); + } + } + + async #decompress(source, destination) { + const absoluteSource = PathService.toAbsolute(this.#currentDirectory, source); + const absoluteDestinationFolder = PathService.toAbsolute(this.#currentDirectory, destination); + + const destinationFolderExists = await new Promise(resolve => fs.exists(absoluteDestinationFolder, resolve)); + + if (!destinationFolderExists) { + console.warn('Destination folder does not exist: ' + absoluteDestinationFolder); + return; + } + + let absoluteDestination = path.resolve( + absoluteDestinationFolder, + PathService.getFullFilename(source) + ); + + if (absoluteDestination.endsWith(CompressService.compressExtension)) { + absoluteDestination = absoluteDestination.slice(0, -1 * CompressService.compressExtension.length); + } else { + console.warn(`Can not decompress files without ${CompressService.compressExtension} extension`); + return; + } + + const destinationExists = await new Promise(resolve => fs.exists(absoluteDestination, resolve)); + + if (destinationExists) { + console.warn('Destination file already exists: ' + absoluteDestination); + return; + } + + try { + await fs.promises.access(absoluteSource); + + const sourceStream = StreamsService.getReadStream(absoluteSource); + const destinationStream = StreamsService.getWriteStream(absoluteDestination, {flags: 'wx+'}); + + await CompressService.decompressWithBrotli(sourceStream, destinationStream); + } catch (error) { + console.warn('Unable to decompress. Paths may be incorrect, or destination folder already contains compressed file with same name'); + console.warn(error.message); + } + } +} diff --git a/FileManager/services/CompressService.js b/FileManager/services/CompressService.js new file mode 100644 index 0000000..26e71f5 --- /dev/null +++ b/FileManager/services/CompressService.js @@ -0,0 +1,39 @@ +import crypto from 'crypto'; +import zlib from 'zlib'; + + +export class CompressService { + static compressExtension = '.br'; + + static hash(data) { + return crypto.createHash('md5').update(data).digest('hex'); + } + + static async compressWithBrotli(sourceStream, destinationStream) { + const brotli = zlib.createBrotliCompress(); + + try { + await new Promise(resolve => { + const stream = sourceStream.pipe(brotli).pipe(destinationStream); + stream.on('finish', resolve); + }); + } catch (error) { + throw error; + } + } + + static async decompressWithBrotli(sourceStream, destinationStream) { + const brotli = zlib.createBrotliDecompress(); + + try { + await new Promise(resolve => { + const stream = sourceStream.pipe(brotli).pipe(destinationStream); + stream.on('finish', () => { + resolve(); + }); + }); + } catch (error) { + throw error; + } + } +} diff --git a/FileManager/services/Converter.js b/FileManager/services/Converter.js new file mode 100644 index 0000000..0c6a245 --- /dev/null +++ b/FileManager/services/Converter.js @@ -0,0 +1,5 @@ +export class Converter { + static MHzToGHz(mhz) { + return mhz / 1000; + } +} diff --git a/FileManager/services/PathService.js b/FileManager/services/PathService.js new file mode 100644 index 0000000..afbb113 --- /dev/null +++ b/FileManager/services/PathService.js @@ -0,0 +1,69 @@ +import path from 'path'; + +export class PathService { + static isAbsolute(checkPath) { + return path.isAbsolute(checkPath); + } + + static toAbsolute(dirname, relative) { + if (PathService.isAbsolute(relative)) { + return relative; + } + + return path.resolve(dirname, relative); + } + + /** + PathService::extractFilePathFromString() receives a string and expected number of paths inside it + If path contains spaces, it must be wrapped with quotes, for example: cp "./path-to-file/file name with space.txt". + But if we are expecting only one file in function, for example cat new file.txt -- then we can not use quotes. + So quotes in required as wrapper for path/filename only if >= 2 entities are expected as arguments. + So acceptable combinations for entering paths: cp "folder 1" "folder 2", cp folder-no-space "folder 2", cp "folder space" folder-no-space, cp folder-no-space1 no-space2 + */ + static extractFilePathFromString(wholeStringFromInputSource, expectedPathsCount) { + const invalidPathResponse = {parseStatusSuccess: false, paths: []}; + const longPathsDelimiter = '"'; + const basePathsDelimiter = ' '; + + const wholeStringFromInput = wholeStringFromInputSource.trim(); + const quotesCount = wholeStringFromInput.split('').filter(symbol => symbol === longPathsDelimiter).length; + if (quotesCount % 2 !== 0) { // every filename must be wrapped from both sides ==> number of quotes is even + return invalidPathResponse; + } + + const splittedByQuotes = PathService.#splitPathsByDelimiter(wholeStringFromInput, longPathsDelimiter); + + if (expectedPathsCount === 1) { + if (splittedByQuotes.length !== 1) { + return invalidPathResponse; + } + + return {parseStatusSuccess: true, paths: splittedByQuotes}; + } + + if (splittedByQuotes.length === 1) { + // No long paths detected, for example: cp ./file1 ./file2 ==> split by space + const paths = PathService.#splitPathsByDelimiter(wholeStringFromInput, basePathsDelimiter); + if (paths.length !== expectedPathsCount) { + return invalidPathResponse; + } + + return {parseStatusSuccess: true, paths}; + } + + // one or all paths are in quotes, for example: cp "./file.txt" file2.txt + if (splittedByQuotes.length !== expectedPathsCount) { + return invalidPathResponse; + } + + return {parseStatusSuccess: true, paths: splittedByQuotes}; + } + + static #splitPathsByDelimiter(input, delimiter) { + return input.split(delimiter).filter(path => path.trim() !== '').map(item => item.trim()); + } + + static getFullFilename(filepath) { + return path.basename(filepath); + } +} diff --git a/FileManager/services/StatsService.js b/FileManager/services/StatsService.js new file mode 100644 index 0000000..836d37c --- /dev/null +++ b/FileManager/services/StatsService.js @@ -0,0 +1,17 @@ +import fs from 'fs'; + +export class StatsService { + static async stats(absolutePath) { + return await new Promise(resolve => { + fs.lstat(absolutePath, (error, stats) => { + resolve(stats); + }); + }); + } + + static async doesPathExist(absolutePath) { + return await new Promise(resolve => { + fs.exists(absolutePath, resolve); + }); + } +} diff --git a/FileManager/services/StreamsService.js b/FileManager/services/StreamsService.js new file mode 100644 index 0000000..6a0a955 --- /dev/null +++ b/FileManager/services/StreamsService.js @@ -0,0 +1,39 @@ +import fs from 'fs'; + +export class StreamsService { + static async readFile(absolutePath) { + return await new Promise((resolve, reject) => { + let response = ''; + const readStream = fs.createReadStream(absolutePath, {encoding: 'utf-8'}); + readStream.on('data', chunk => { + response += chunk; + }); + + readStream.on('error', reject); + + readStream.on('close', () => { + resolve(response); + }); + }); + } + + static getReadStream(source, options = {}) { + try { + return fs.createReadStream(source, options).on('error', error => { + throw error; + }); + } catch (error) { + throw error; + } + } + + static getWriteStream(destination, options = {}) { + try { + return fs.createWriteStream(destination, options).on('error', error => { + throw error; + }); + } catch (error) { + throw error; + } + } +} diff --git a/FileManager/tests/PathService.test.js b/FileManager/tests/PathService.test.js new file mode 100644 index 0000000..5cca7d2 --- /dev/null +++ b/FileManager/tests/PathService.test.js @@ -0,0 +1,34 @@ +import {PathService} from '../services/PathService.js'; +import {testCasesForFullFileName, testCasesForPathExtracting} from './test.data.js'; + +const testExtractingPathsFromString = () => { + testCasesForPathExtracting.forEach((testCase, index) => { + console.log(`[Paths extracting] Test case #${index}`); + + const input = testCase[0]; + const expectedResult = testCase[1]; + const receivedResult = PathService.extractFilePathFromString(...input); + + console.assert(JSON.stringify(expectedResult) === JSON.stringify(receivedResult)); + }); +}; + + +const testGettingFullFilename = () => { + testCasesForFullFileName.forEach((testCase, index) => { + console.log(`[Getting full filename] Test case #${index}`); + + const input = testCase[0]; + const expectedResult = testCase[1]; + const receivedResult = PathService.getFullFilename(input); + + console.assert(receivedResult === expectedResult); + }); +}; + +const runTests = () => { + testExtractingPathsFromString(); + testGettingFullFilename(); +}; + +runTests(); diff --git a/FileManager/tests/test.data.js b/FileManager/tests/test.data.js new file mode 100644 index 0000000..93aa9a9 --- /dev/null +++ b/FileManager/tests/test.data.js @@ -0,0 +1,21 @@ +export const testCasesForPathExtracting = [ + [['"path-in-quotes" path-without-quotes', 2], {parseStatusSuccess: true, paths: ['path-in-quotes', 'path-without-quotes']}], + [['"./long path with space/path-in-quotes.txt" path-without-quotes', 2], {parseStatusSuccess: true, paths: ['./long path with space/path-in-quotes.txt', 'path-without-quotes']}], + [['"./long path with space/path-in-quotes.txt" path-without-quotes', 1], {parseStatusSuccess: false, paths: []}], + [[' "./long path with space/path-in-quotes.txt" ', 1], {parseStatusSuccess: true, paths: ['./long path with space/path-in-quotes.txt']}], + [[' "./long path with space/path-in-quotes.txt" ', 2], {parseStatusSuccess: false, paths: []}], + [[' "./long path with space/path-in-quotes.txt" ', 3], {parseStatusSuccess: false, paths: []}], + [[' "./long path with space/path-in-quotes.txt" "../../another-path.txt"', 2], {parseStatusSuccess: true, paths: ['./long path with space/path-in-quotes.txt', '../../another-path.txt']}], + [[' "./long path with space/path-in-quotes.txt" "../../another-path.txt"', 1], {parseStatusSuccess: false, paths: []}], + [['./long path with space/path-in-quotes.txt', 1], {parseStatusSuccess: true, paths: ['./long path with space/path-in-quotes.txt']}], + [['./long path with space/path-in-quotes.txt ./another-long-path/file with space.txt', 2], {parseStatusSuccess: false, paths: []}], + [['./long path with space/path-in-quotes.txt ./another-long-path/file with space.txt', 1], {parseStatusSuccess: true, paths: ['./long path with space/path-in-quotes.txt ./another-long-path/file with space.txt']}], + [['"folder 1" "./folder 2/file"', 2], {parseStatusSuccess: true, paths: ['folder 1', './folder 2/file']}], + [['file with long name/file.txt "./another folder 2/file"', 2], {parseStatusSuccess: true, paths: ['file with long name/file.txt', './another folder 2/file']}], // strange case, but actually it does not break anything :) +]; + +export const testCasesForFullFileName = [ + ['../folder 1/filename.txt', 'filename.txt'], + ['filename.txt', 'filename.txt'], + ['./filename.bat.txt', 'filename.bat.txt'], +]; diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..cb8eef0 --- /dev/null +++ b/Readme.md @@ -0,0 +1,22 @@ +## _Simple File Manager_ +___ +### _Manual:_ +To run this console application, use command `npm run start -- --username=YOUR_NAME`. Or just use `npm run start:with-default-user`. +#### _About paths:_ +1. If command requires only one path argument, for example `cd PATH`, `add PATH` etc., then you can __optionally wrap__ path with quotes. But not necessary. +For example, `cd "./files"`, `cd ./files`, `cd ./folder with space/files`, `cd "./folder with space/files"`; ![Demo](./demo/single-argument-path.PNG) ![Demo](./demo/single-argument-path-2.PNG) +2. If command requires 2+ paths and one of them contains space — then you _must__ wrap this path with quotes. For example: +`cp ./folder1/file1.txt "../folder with space/folder2"`, `cp "../folder with space/folder2" ./folder1` or also wrap all: `cp "../folder with space/folder2" "./folder1"`; ![Demo](./demo/2-args-paths.PNG) __NOTE: cd REQUIRES ONLY 1 PATH, THAT'S WHY IT CAN BE WRITTEN BOTH WITH WRAPPING QUOTES AND WITHOUT THEM__ +3. If my parser considers you inserting invalid number of paths for command or inserting them in not valid way — it will abort operation and print warning about this; +4. _You can see parser's expected logic in `./FileManager/tests/PathService.test.js`_ + +___ +### _Technologies stack:_ +- _Node.js_ +- _JavaScript_ + +___ + +###### Copyright © 2024 +###### Created by Slutski Mikita +###### Inspired by Rolling Scopes and RS NodeJS 2024 Q1 diff --git a/demo/2-args-paths.PNG b/demo/2-args-paths.PNG new file mode 100644 index 0000000..d143760 Binary files /dev/null and b/demo/2-args-paths.PNG differ diff --git a/demo/single-argument-path-2.PNG b/demo/single-argument-path-2.PNG new file mode 100644 index 0000000..9f8cba2 Binary files /dev/null and b/demo/single-argument-path-2.PNG differ diff --git a/demo/single-argument-path.PNG b/demo/single-argument-path.PNG new file mode 100644 index 0000000..8b7ba5b Binary files /dev/null and b/demo/single-argument-path.PNG differ diff --git a/index.js b/index.js index 8a5f682..a548e16 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,22 @@ +import {FileManager} from './fileManager/FileManager.js'; +import {getUsernameFromArgs} from './utils.js'; + const main = async () => { - const username = process.argv[3]; - console.log(`Welcome to the File Manager, ${username}!`); + const username = getUsernameFromArgs(process.argv); -}; + if (username === undefined) { + console.warn('Username argument not provided'); + } else { + console.info(`Welcome to the File Manager, ${username}!`); + } + process.addListener('exit', () => { + console.info(`Thank you for using File Manager, ${username}, goodbye!`); + }); + + const fileManager = new FileManager(); + await fileManager.run(); +}; -main(); +await main(); diff --git a/package.json b/package.json index 6992092..c128f32 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,18 @@ { "name": "file-manager", - "version": "1.0.0", - "description": "File manager for Rolling Scopes", + "version": "0.1.0", + "description": "File manager for Rolling Scopes Node.js 2024Q1", "type": "module", "main": "index.js", "scripts": { - "start": "node ./index.js" + "start": "node ./index.js", + "start:with-default-user": "node ./index.js --username=Mikita", + "test:path-service": "node fileManager/tests/PathService.test.js" }, "keywords": [], - "author": "Slutski Mikita (@user-of-github)", + "author": { + "name": "Slutski Mikita", + "url": "https://github.com/user-of-github" + }, "license": "ISC" } diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..dbcfb49 --- /dev/null +++ b/utils.js @@ -0,0 +1,16 @@ +/** + Looks for --username={name} pattern in args +*/ +export const getUsernameFromArgs = processArgv => { + const usernameArgKey = '--username'; + + for (let index = 2; index < processArgv.length; ++index) { + const splitted = processArgv.at(index).split('='); + + if (splitted.at(0) === usernameArgKey) { + return splitted.at(1); + } + } + + return undefined; +};