Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions src/cli/interactive.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,51 @@
import process from 'node:process';
import readline from 'node:readline'

const interactive = () => {
// Write your code here
// Use readline module for interactive CLI
// Support commands: uptime, cwd, date, exit
// Handle Ctrl+C and unknown commands

const readlineInstance = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

readlineInstance.prompt();

readlineInstance.on('line', (line) => {
switch (line.trim()) {
case 'uptime': {
console.log(process.uptime());
readlineInstance.prompt();
break;
}
case 'cwd': {
console.log(process.cwd());
readlineInstance.prompt();
break;
}
case 'date': {
console.log(new Date);
readlineInstance.prompt();
break;
}
case 'exit': {
readlineInstance.close()
break;
}
default: {
console.log('Unknown command');
readlineInstance.prompt();
}
}
});

readlineInstance.on('close', () => {
console.log('Goodbye!')
readlineInstance.close()
});
};

interactive();
70 changes: 70 additions & 0 deletions src/cli/progress.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,78 @@
import process from "node:process";
import { getArg } from '../utils/getArg.js';

const ansiColorReset = '\x1b[0m';
function hexColorToAnsi(hex) {
if (!hex || !/^#(?:[0-9a-fA-F]{3}){1,2}$/.test(hex)) {
return;
}

let cleanHex = hex.slice(1);
if (cleanHex.length === 3) {
cleanHex = cleanHex.split('').map(s => s + s).join('');
}

const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);

return `\x1b[38;2;${r};${g};${b}m`;
}

const writeProgressInPlace = (persent, length, color) => {
if (persent > 100 || persent < 0) {
throw new Error('Invalid persentage')
}
const filledChar = "█";
const emptyChar = " ";
const ansiColor = hexColorToAnsi(color);

const filledAmount = Math.floor(persent / 100 * length);
const emptyAmount = length - filledAmount;
const bar = `${filledChar.repeat(filledAmount)}${emptyChar.repeat(emptyAmount)}`;
const coloredBar = ansiColor? `${ansiColor}${bar}${ansiColorReset}` : bar;

process.stdout.write(`\r[${coloredBar}] ${persent}%`);
}

const progress = () => {
// Write your code here
// Simulate progress bar from 0% to 100% over ~5 seconds
// Update in place using \r every 100ms
// Format: [████████████████████ ] 67%

const colorArg = getArg('color');
const lengthArg = getArg('length');
const durationArg = getArg('duration');
const intervalArg = getArg('interval');

const duration = durationArg && !Number.isNaN(durationArg) ? Number(durationArg): 5000;
const interval = intervalArg && !Number.isNaN(intervalArg) ? Number(intervalArg): 5000;
const length = lengthArg && !Number.isNaN(lengthArg) ? Number(lengthArg): 30;
const startTime = new Date();

// This is my personal addition to validate data
if (duration < 3000 || 10000 < duration) {
throw Error('Duration should be in range [3s, 10s]');
}
if (intereval < 50 || 300 < intereval) {
throw Error('Interval should be in range [50ms, 300ms]');
}
if (length < 10 || 50 < length) {
throw Error('Length should be in range [10, 50]');
}

const intereval = setInterval(() => {
const durationDiff = Math.min(new Date() - startTime, duration);
const durationDiffPersent = durationDiff ? Math.round(durationDiff * 100 / duration) : 0;

writeProgressInPlace(durationDiffPersent, length, colorArg);

if (duration <= durationDiff) {
clearInterval(intereval);
process.stdout.write('\n');
}
}, interval);
};

progress();
30 changes: 30 additions & 0 deletions src/cp/execCommand.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
import process from 'node:process';
import { spawn } from 'node:child_process';

const execCommand = () => {
// Write your code here
// Take command from CLI argument
// Spawn child process
// Pipe child stdout/stderr to parent stdout/stderr
// Pass environment variables
// Exit with same code as child

const [,, ...commandArg] = process.argv;
if (!commandArg) {
console.log('Please enter command');
process.exit(1)
}

const command = commandArg.join(' ');
const spawnedProcess = spawn(command, {
env: process.env,
stdio: ['inherit', 'pipe', 'pipe'],
shell: true
});

// Can be done automatically with 'inherit' in stdio, but I added it here to try manual piping
spawnedProcess.stdout.pipe(process.stdout);
spawnedProcess.stderr.pipe(process.stderr);

spawnedProcess.on('exit', (code) => {
process.exit(code ?? 1);
})

spawnedProcess.on('error', (error) => {
console.log(`Error in handling spawned process:`);
console.error(error);
process.exit(1);
})
};

execCommand();
36 changes: 36 additions & 0 deletions src/fs/findByExt.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { getArg } from '../utils/getArg.js';

const findFiles = async (dir, ext) => {
try {
const items = await fs.readdir(dir, { withFileTypes: true, recursive: true });

const results = [];

items.forEach((item) => {
const fullPath = path.join(item.parentPath, item.name);
const relativePath = path.relative(dir, fullPath);

if (item.isFile() && item.name.endsWith(ext)) {
results.push(relativePath);
}
})

return results.sort((a, b) => a.localeCompare(b));
} catch {
throw new Error('FS operation failed')
}
};

const findByExt = async () => {
// Write your code here
// Recursively find all files with specific extension
// Parse --ext CLI argument (default: .txt)

const ext = getArg('ext') ?? '.txt';
const workspaceDir = path.join(process.cwd(), 'workspace');
const files = await findFiles(workspaceDir, ext);

if (files.length === 0) {
console.log(`No files with extention ${ext}`);
} else {
files.forEach(file => console.log(file));
}
};

await findByExt();
65 changes: 65 additions & 0 deletions src/fs/merge.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,73 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { getArg } from '../utils/getArg.js';

const getValidArgFilePaths = async (filesArg, dirPath) => {
const fileNames = filesArg.split(',');
const filePaths = fileNames.map(name => path.join(dirPath, name.trim()));

try {
await Promise.all(filePaths.map((filePath) => fs.access(filePath, fs.constants.F_OK)));
} catch {
throw new Error(`FS operation failed: No file from arguments`)
}

return filePaths;
}

const getDefaultFilePaths = async (dirPath) => {
try {
const files = await fs.readdir(dirPath, { withFileTypes: true });

const filePaths = files.filter(file => file.isFile() && file.name.endsWith('.txt'))
.sort((a, b) => a.name.localeCompare(b.name))
.map(file => path.join(dirPath, file.name));

if (files.length === 0) {
throw new Error(`FS operation failed: No files in folder ${dirPath}`)
}

return filePaths;
} catch {
throw new Error(`FS operation failed: No folder ${dirPath}`)
}
}

const getFilesToMerge = (dirPath) => {
const filesArg = getArg('files');

if (filesArg) {
return getValidArgFilePaths(filesArg, dirPath);
} else {
return getDefaultFilePaths(dirPath)
}
}

const merge = async () => {
// Write your code here
// Default: read all .txt files from workspace/parts in alphabetical order
// Optional: support --files filename1,filename2,... to merge specific files in provided order
// Concatenate content and write to workspace/merged.txt

const partsDir = path.join(process.cwd(), 'workspace/parts');
const filesToMerge = await getFilesToMerge(partsDir);

const fileContents = await Promise.all(filesToMerge.map((filePath) => {
return fs.readFile(filePath, { encoding: 'utf-8'});
}));

try {
const outputFile = path.join(process.cwd(), 'workspace/merged.txt');
const outputDir = path.dirname(outputFile);

await fs.mkdir(outputDir, { recursive: true });

await fs.writeFile(outputFile, fileContents.join('\n'));
console.log(`Completed`);
} catch {
console.error('FS operation failed: Failed to write merged file');
}
};

await merge();
42 changes: 42 additions & 0 deletions src/fs/restore.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,50 @@
import fs from 'node:fs/promises';
import path from 'node:path';

const getSnapshotEntries = async (filePath) => {
try {
const snapshotFileContent = await fs.readFile(filePath, { encoding: 'utf8' });
const parsedSnapshot = JSON.parse(snapshotFileContent);

return parsedSnapshot.entries;
} catch {
throw new Error(`FS operation failed: Snapshot ${filePath} does't exit`);
}
}

const restore = async () => {
// Write your code here
// Read snapshot.json
// Treat snapshot.rootPath as metadata only
// Recreate directory/file structure in workspace_restored

const snapshotPath = path.resolve(process.cwd(), 'workspace/snapshot.json');
const snapshotEntries = await getSnapshotEntries(snapshotPath);
const targetRoot = path.join(process.cwd(), 'workspace_restored');


try {
await fs.mkdir(targetRoot);
} catch (err) {
throw new Error(`FS operation failed: ${targetRoot} already exist`);
}

const tasks = snapshotEntries.map(async entry => {
const fullPath = path.join(targetRoot, entry.path);

if (entry.type === 'directory') {
return fs.mkdir(fullPath, { recursive: true });
} else {
const parentDir = path.dirname(fullPath);
await fs.mkdir(parentDir, { recursive: true });

return fs.writeFile(fullPath, entry.content, { encoding: 'base64' });
}
});

await Promise.all(tasks);

console.log('Restoration complete!');
};

await restore();
Loading