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
42 changes: 38 additions & 4 deletions src/cli/interactive.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
import readline from '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 rl = readline.createInterface({
input: process.stdin,
output: process.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(process.cwd());
break;
case 'date':
console.log(new Date().toISOString());
break;
case 'exit':
console.log('Goodbye!');
process.exit(0);
break;
default:
console.log('Unknown command');
}

rl.prompt();
});

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

interactive();
61 changes: 57 additions & 4 deletions src/cli/progress.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,61 @@
const args = process.argv.slice(2);

const RESET = '\x1b[0m';

const getArg = (name) => {
const index = args.indexOf(name);
if (index === -1 || index + 1 >= args.length) return undefined;
return args[index + 1];
};

const getNumericArg = (name, defaultValue) => {
const raw = getArg(name);
if (raw === undefined) return defaultValue;
const parsed = parseInt(raw, 10);
return Number.isNaN(parsed) ? defaultValue : parsed;
};

const parseHexColor = (hex) => {
if (!hex) return null;
const match = hex.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
if (!match) return null;
const r = parseInt(match[1], 16);
const g = parseInt(match[2], 16);
const b = parseInt(match[3], 16);
return `\x1b[38;2;${r};${g};${b}m`;
};

const renderBar = (percent, length, colorCode) => {
const filled = Math.round((percent / 100) * length);
const empty = length - filled;

const filledBar = '█'.repeat(filled);
const emptyBar = ' '.repeat(empty);

const coloredFilled = colorCode ? `${colorCode}${filledBar}${RESET}` : filledBar;
return `[${coloredFilled}${emptyBar}] ${percent}%`;
};

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 duration = getNumericArg('--duration', 5000);
const interval = getNumericArg('--interval', 100);
const length = getNumericArg('--length', 30);
const colorCode = parseHexColor(getArg('--color'));

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

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

process.stdout.write(`\r${renderBar(percent, length, colorCode)}`);

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

progress();
65 changes: 59 additions & 6 deletions src/fs/snapshot.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,62 @@
import { promises as fs } from 'fs';
import path from 'path';

async function scanDirectory(dirPath, relativePath = '') {
try {
const entries = [];
const items = await fs.readdir(dirPath);
items.sort();

for (const item of items) {
const fullPath = path.join(dirPath, item);
const relPath = path.join(relativePath, item);
const stats = await fs.stat(fullPath);

if (stats.isDirectory()) {
entries.push({ path: relPath, type: 'directory' });
const subEntries = await scanDirectory(fullPath, relPath);
entries.push(...subEntries);
} else if (stats.isFile()) {
const buffer = await fs.readFile(fullPath);
entries.push({
path: relPath,
type: 'file',
size: stats.size,
content: buffer.toString('base64'),
});
}
}

return entries;
} catch (error) {
throw new Error(`FS operation failed: ${error.message}`);
}
}

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 workspacePath = path.join(process.cwd(), 'workspace');
try {
await fs.access(workspacePath);
} catch (error) {
throw new Error(`FS operation failed: ${error.message}`);
}

const entries = await scanDirectory(workspacePath);

const result = {
rootPath: workspacePath,
entries,
};

const snapshotPath = path.join(path.dirname(workspacePath), 'snapshot.json');
try {
await fs.writeFile(snapshotPath, JSON.stringify(result, null, 2));
} catch (error) {
throw new Error(`FS operation failed: ${error.message}`);
}
};

await snapshot();
await snapshot().catch((error) => {
console.error(error.message);
process.exit(1);
});
14 changes: 9 additions & 5 deletions src/modules/dynamic.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
const dynamic = async () => {
// Write your code here
// Accept plugin name as CLI argument
// Dynamically import plugin from plugins/ directory
// Call run() function and print result
// Handle missing plugin case
const args = process.argv.slice(2);
const pluginName = args.at(-1);
try {
const plugin = await import(`./plugins/${pluginName}.js`);
console.log(plugin.run());
} catch (error) {
console.error(`Plugin not found`);
process.exit(1);
}
};

await dynamic();
25 changes: 21 additions & 4 deletions src/streams/lineNumberer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import { Transform } from 'node:stream';

const addLineNumbers = (chunk, counter) =>
chunk
.toString()
.split('\n')
.map((line, i) => {
if (i === 0 && line === '') return '';
return `${counter.value++} | ${line}`;
})
.join('\n');

const counter = { value: 1 };

const transform = new Transform({
transform(chunk, enc, cb) {
cb(null, addLineNumbers(chunk, counter));
},
});

const lineNumberer = () => {
// Write your code here
// Read from process.stdin
// Use Transform Stream to prepend line numbers
// Write to process.stdout
process.stdin.pipe(transform).pipe(process.stdout);
};

lineNumberer();
146 changes: 146 additions & 0 deletions src/streams/split-to-files-no-transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Та же задача, но БЕЗ Transform — вся логика в _write()
*
* Сравни с split-to-files.js где используется Transform (lineSplitter).
* Тут всё в одном месте — и разбиение на строки, и ротация файлов.
*
* ⚠️ Работает, но _write() делает ДВЕ работы одновременно:
* 1) Склеивает огрызки строк (потому что чанк режет по байтам)
* 2) Считает строки и ротирует файлы
*/

import { Writable } from 'node:stream';
import { createReadStream, createWriteStream } from 'node:fs';
import { writeFile, unlink, mkdir, readFile } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const WORK_DIR = join(__dirname, '_split-demo-v2');
const INPUT_PATH = join(WORK_DIR, 'input.txt');

const TOTAL_LINES = 35;
const LINES_PER_FILE = 10;

await mkdir(WORK_DIR, { recursive: true });

const lines = Array.from({ length: TOTAL_LINES }, (_, i) =>
`Сегмент #${String(i + 1).padStart(2, '0')}: данные-${Math.random().toString(36).slice(2, 8)}`
);
await writeFile(INPUT_PATH, lines.join('\n'));

console.log('=== БЕЗ Transform — всё в _write() ===\n');

// ─── Всё в одном Writable ──────────────────────────────────────────

let remainder = ''; // огрызок строки с прошлого чанка
let lineCount = 0;
let fileIndex = 0;
let currentStream = null;
const createdFiles = [];

function openNewFile() {
return new Promise((resolve, reject) => {
fileIndex++;
const fileName = `chunk_${String(fileIndex).padStart(3, '0')}.txt`;
const filePath = join(WORK_DIR, fileName);
createdFiles.push(filePath);
currentStream = createWriteStream(filePath, { encoding: 'utf-8' });
console.log(`\n 📁 Создан: ${fileName}`);
currentStream.on('open', resolve);
currentStream.on('error', reject);
});
}

function closeCurrentFile() {
return new Promise((resolve) => {
if (!currentStream) return resolve();
currentStream.end(() => {
console.log(` 📦 Закрыт (${lineCount} строк)`);
lineCount = 0;
resolve();
});
});
}

const splitter = new Writable({
// НЕ objectMode! Мы получаем сырые Buffer/string чанки от Readable
decodeStrings: false,

async write(chunk, encoding, callback) {
try {
// ─── Работа 1: собираем строки из огрызков ───
// (это то, что делал Transform в прошлой версии)
remainder += chunk.toString();
const parts = remainder.split('\n');
remainder = parts.pop(); // последний — неполная строка, сохраняем

// ─── Работа 2: пишем строки, ротируем файлы ───
// (это то, что делал Writable в прошлой версии)
for (const line of parts) {
if (line.length === 0) continue;

if (!currentStream || lineCount >= LINES_PER_FILE) {
await closeCurrentFile();
await openNewFile();
}

lineCount++;
currentStream.write(line + '\n');
console.log(` ✏️ ${lineCount}/${LINES_PER_FILE} → "${line.slice(0, 40)}..."`);
}

callback();
} catch (err) {
callback(err);
}
},
});

// Обработка остатка и последнего файла
splitter._final = async function (callback) {
try {
// Остаток (последняя строка без \n)
if (remainder.length > 0) {
if (!currentStream || lineCount >= LINES_PER_FILE) {
await closeCurrentFile();
await openNewFile();
}
lineCount++;
currentStream.write(remainder + '\n');
console.log(` ✏️ ${lineCount}/${LINES_PER_FILE} → "${remainder.slice(0, 40)}..."`);
}
await closeCurrentFile();
callback();
} catch (err) {
callback(err);
}
};

// ─── Цепочка: ВСЕГО 2 звена ────────────────────────────────────────

const readable = createReadStream(INPUT_PATH, {
highWaterMark: 64,
encoding: 'utf-8',
});

readable.pipe(splitter); // ← без Transform! Напрямую.

splitter.on('finish', async () => {
console.log('\n' + '─'.repeat(60));
console.log('\n ✅ Результат:\n');

for (const filePath of createdFiles) {
const content = await readFile(filePath, 'utf-8');
const lc = content.trim().split('\n').length;
const fn = filePath.split('/').pop();
console.log(` 📄 ${fn} (${lc} строк)`);
}

// Уборка
for (const f of createdFiles) await unlink(f);
await unlink(INPUT_PATH);
const { rmdir } = await import('node:fs/promises');
await rmdir(WORK_DIR);
console.log('\n 🧹 Удалено\n');
});
Loading