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.

46 changes: 42 additions & 4 deletions src/cli/interactive.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
import { createInterface } 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 { stdin, stdout, cwd } = process;

const rl = createInterface({
input: stdin,
output: stdout,
prompt: '> '
});

rl.prompt();

rl.on('line', (line) => {
const command = line.trim();

switch (command) {
case 'uptime':
console.log(`Uptime: ${process.uptime().toFixed(2)}s`);
break;
case 'cwd':
console.log(`Current directory: ${cwd()}`);
break;
case 'date':
console.log(`Current date and time: ${new Date().toISOString()}`);
break;
case 'exit':
rl.close();
break;
default:
console.log('Unknown command');
}

rl.prompt();
}).on('close', () => {
console.log('Goodbye!');
process.exit(0);
});

process.on('SIGINT', () => {
console.log('\nGoodbye!');
process.exit(0);
});
};

interactive();
60 changes: 56 additions & 4 deletions src/cli/progress.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,60 @@
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 { stdout, argv } = process;

let duration = 5000;
let interval = 100;
let length = 30;
let color = null;

for (let i = 2; i < argv.length; i++) {
switch (argv[i]) {
case '--duration':
duration = parseInt(argv[++i], 10) || duration;
break;
case '--interval':
interval = parseInt(argv[++i], 10) || interval;
break;
case '--length':
length = parseInt(argv[++i], 10) || length;
break;
case '--color':
const c = argv[++i];
if (/^#([0-9A-Fa-f]{6})$/.test(c)) color = c;
break;
default:
console.warn(`Unknown param: ${argv[i]}`);
}
}

const hexToAnsi = hex => {
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 drawProgress = percent => {
const filledLength = Math.round((percent / 100) * length);
const emptyLength = length - filledLength;
const filled = '█'.repeat(filledLength);
const empty = ' '.repeat(emptyLength);
const coloredFilled = color ? `${hexToAnsi(color)}${filled}\x1b[0m` : filled;
stdout.write(`\r[${coloredFilled}${empty}] ${percent}%`);
};

const steps = Math.ceil(duration / interval);
let currentStep = 0;

const timer = setInterval(() => {
currentStep++;
const percent = Math.min(Math.round((currentStep / steps) * 100), 100);
drawProgress(percent);

if (currentStep >= steps) {
clearInterval(timer);
stdout.write('\nDone!\n');
}
}, interval);
};

progress();
32 changes: 26 additions & 6 deletions src/cp/execCommand.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
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 { argv, exit, env } = process;

const commandArg = argv[2];

if (!commandArg) {
console.error('Command not specified.');
exit(1);
}

const [command, ...args] = commandArg.split(' ');

const child = spawn(command, args, {
stdio: 'inherit',
env,
});

child.on('exit', (code) => {
exit(code);
});

child.on('error', (err) => {
console.error('The command execution failed with an error', err.message);
exit(1);
});
};

execCommand();
45 changes: 42 additions & 3 deletions src/fs/findByExt.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
import { fileURLToPath } from 'node:url';
import { join } from 'node:path';
import { readdir } from 'node:fs/promises';

const findByExt = async () => {
// Write your code here
// Recursively find all files with specific extension
// Parse --ext CLI argument (default: .txt)
const getDirEntries = async (path) => {
const childElements = await readdir(path, { withFileTypes: true });
const dirEntries = [];

for (const element of childElements) {
const elementName = element.name;

if (element.isFile()) {
dirEntries.push(elementName);
continue;
}

const fullPath = join(path, elementName);
const subDirEntries = await getDirEntries(fullPath);

dirEntries.push(...subDirEntries.map(entry => join(elementName, entry)));
}

return dirEntries;
};

const DEFAULT_EXT = 'txt';

const args = process.argv.slice(2);
const extArgIndex = args.indexOf('--ext');
const ext = (extArgIndex !== -1 && (extArgIndex + 1) < args.length) ? args[extArgIndex + 1] : DEFAULT_EXT;

const rootPath = join(fileURLToPath(import.meta.url), '..', '..', '..', 'workspace');

try {
const dirEntries = await getDirEntries(rootPath);
const extFilePaths = dirEntries.flat(Infinity).filter(filePath => filePath.endsWith(`.${ext}`)).sort();

console.log(extFilePaths.join('\n'));
} catch (error) {
error.message = `FS operation failed\n${error.message}`;
throw error;
}
};

await findByExt();
47 changes: 43 additions & 4 deletions src/fs/merge.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,47 @@
import { fileURLToPath } from 'node:url';
import { join } from 'node:path';
import { readdir, readFile, writeFile } from 'node:fs/promises';

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 TEXT_EXT = 'txt';

const rootPath = join(fileURLToPath(import.meta.url), '..', '..', '..', 'workspace/parts');
let fullContent = '';

const args = process.argv.slice(2);
const fileNamesArgIndex = args.indexOf('--files');

try {
if (fileNamesArgIndex !== -1 && (fileNamesArgIndex + 1) < args.length) {
const fileNames = args[fileNamesArgIndex + 1].split(',');

for (const name of fileNames) {
fullContent += await readFile(join(rootPath, `${name}.${TEXT_EXT}`));
}
} else {
let isTextFileExist = false;
const allEntries = (await readdir(rootPath, { withFileTypes: true })).sort();

for (const entry of allEntries) {
if (entry.isFile() && entry.name.endsWith(`.${TEXT_EXT}`)) {
if (!isTextFileExist) {
isTextFileExist = true;
}

fullContent += await readFile(join(rootPath, entry.name));
}
}

if (!isTextFileExist) {
throw new Error('FS operation failed\nThere are no files with the .txt extension in the folder.');
}
}
} catch (error) {
error.message = `FS operation failed\n${error.message}`;
throw error;
}

await writeFile(join(rootPath, '..', 'merged.txt'), fullContent);
};

await merge();
32 changes: 28 additions & 4 deletions src/fs/restore.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
import { fileURLToPath } from 'node:url';
import { join, dirname } from 'node:path';
import { readFile, mkdir, writeFile, constants } from 'node:fs/promises';

const restore = async () => {
// Write your code here
// Read snapshot.json
// Treat snapshot.rootPath as metadata only
// Recreate directory/file structure in workspace_restored
const currentFilePath = fileURLToPath(import.meta.url);
const rootPath = join(currentFilePath, '..', '..', '..', 'workspace_restored');
const snapshotPath = join(currentFilePath, '..', '..', '..', 'snapshot.json');

try {
await mkdir(rootPath);

const dirSnapshot = JSON.parse(await readFile(snapshotPath, { encoding: 'utf8' }));

for (const entry of dirSnapshot.entries) {
if (entry.type === 'directory') {
await mkdir(join(rootPath, entry.path), { recursive: true });
continue;
}

const fileData = Buffer.from(entry.content, "base64");

await mkdir(join(rootPath, dirname(entry.path)), { recursive: true });
await writeFile(join(rootPath, entry.path), fileData);
}
} catch (error) {
error.message = `FS operation failed\n${error.message}`;
throw error;
}
};

await restore();
56 changes: 51 additions & 5 deletions src/fs/snapshot.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,55 @@
import { fileURLToPath } from 'node:url';
import { join } from 'node:path';
import { readdir, readFile, lstat, writeFile } from 'node:fs/promises';

const snapshot = async () => {
// Write your code here
// Recursively scan workspace directory
// Write snapshot.json with:
// - rootPath: absolute path to workspace
// - entries: flat array of relative paths and metadata
const getDirEntries = async (path) => {
const childElements = await readdir(path, { withFileTypes: true });
const dirEntries = [];

for (const element of childElements) {
const entry = { path: element.name };
const fullPath = join(path, entry.path);

if (element.isFile()) {
const stats = await lstat(fullPath);

entry.type = 'file';
entry.size = stats.size;
entry.content = (await readFile(fullPath)).toString('base64');

dirEntries.push(entry);
continue;
}

entry.type = 'directory';

const subDirEntries = await getDirEntries(fullPath);

dirEntries.push([
entry,
subDirEntries.map(element => ({
...element,
path: join(entry.path, element.path),
})),
]);
}

return dirEntries;
};

try {
const rootPath = join(fileURLToPath(import.meta.url), '..', '..', '..', 'workspace');
const entries = await getDirEntries(rootPath);

await writeFile(
join(rootPath, '..', 'snapshot.json'),
JSON.stringify({ rootPath, entries: entries.flat(Infinity) }, null, 2)
);
} catch (error) {
error.message = `FS operation failed\n${error.message}`;
throw error;
}
};

await snapshot();
Loading