diff --git a/input.txt b/input.txt new file mode 100644 index 00000000..1c943a98 --- /dev/null +++ b/input.txt @@ -0,0 +1,3 @@ +a +b +c \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..755c365b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "node-nodejs-fundamentals", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node-nodejs-fundamentals", + "version": "1.0.0", + "license": "ISC", + "engines": { + "node": ">=24.10.0", + "npm": ">=10.9.2" + } + } + } +} diff --git a/src/cli/interactive.js b/src/cli/interactive.js index d0e3e0d9..e676778d 100644 --- a/src/cli/interactive.js +++ b/src/cli/interactive.js @@ -1,8 +1,47 @@ +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 rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + rl.on("line", (input) => { + const command = input.trim(); + + switch (command) { + case "uptime": + console.log(process.uptime()); + break; + + case "cwd": + console.log(process.cwd()); + break; + + case "date": + console.log(new Date().toString()); + break; + + case "exit": + rl.close(); + return; + + default: + console.log("Unknown command"); + } + }); + + rl.on("close", () => { + process.exit(0); + }); + + process.on("SIGINT", () => { + rl.close(); + }); }; interactive(); diff --git a/src/cli/progress.js b/src/cli/progress.js index 3e060763..b4d8ba4e 100644 --- a/src/cli/progress.js +++ b/src/cli/progress.js @@ -3,6 +3,26 @@ const progress = () => { // Simulate progress bar from 0% to 100% over ~5 seconds // Update in place using \r every 100ms // Format: [████████████████████ ] 67% + + let percent = 0; + const totalSteps = 50; + const barLength = 30; + + const interval = setInterval(() => { + percent += 100 / totalSteps; + + const filled = Math.round((percent / 100) * barLength); + const empty = barLength - filled; + + const bar = "█".repeat(filled) + " ".repeat(empty); + + process.stdout.write(`\r[${bar}] ${Math.round(percent)}%`); + + if (percent >= 100) { + clearInterval(interval); + process.stdout.write("\n"); + } + }, 100); }; progress(); diff --git a/src/cp/execCommand.js b/src/cp/execCommand.js index 34a89c8d..b670c9c0 100644 --- a/src/cp/execCommand.js +++ b/src/cp/execCommand.js @@ -1,3 +1,5 @@ +import { spawn } from "node:child_process"; + const execCommand = () => { // Write your code here // Take command from CLI argument @@ -5,6 +7,20 @@ const execCommand = () => { // Pipe child stdout/stderr to parent stdout/stderr // Pass environment variables // Exit with same code as child + + const command = process.argv[2]; + + const child = spawn(command, { + shell: true, + env: process.env, + }); + + child.stdout.pipe(process.stdout); + child.stderr.pipe(process.stderr); + + child.on("close", (code) => { + process.exit(code); + }); }; execCommand(); diff --git a/src/fs/findByExt.js b/src/fs/findByExt.js index 24f06cb8..889a1800 100644 --- a/src/fs/findByExt.js +++ b/src/fs/findByExt.js @@ -1,7 +1,33 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + const findByExt = async () => { // Write your code here // Recursively find all files with specific extension // Parse --ext CLI argument (default: .txt) + + /* console.log(process.argv); */ + + const extIndex = process.argv.indexOf("--ext"); + const extension = extIndex !== -1 ? "." + process.argv[extIndex + 1] : ".txt"; + + const startDir = process.cwd(); + + const search = async (dir) => { + const entries = await fs.readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + await search(fullPath); + } else if (entry.isFile() && entry.name.endsWith(extension)) { + console.log(fullPath); + } + } + }; + + await search(startDir); }; await findByExt(); diff --git a/src/fs/merge.js b/src/fs/merge.js index cb8e0d8f..8de4b7a3 100644 --- a/src/fs/merge.js +++ b/src/fs/merge.js @@ -1,8 +1,36 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + 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 workspaceDir = path.join(process.cwd(), "workspace"); + const partsDir = path.join(workspaceDir, "parts"); + const outputFile = path.join(workspaceDir, "merged.txt"); + + const filesIndex = process.argv.indexOf("--files"); + + let files = []; + + if (filesIndex !== -1) { + files = process.argv[filesIndex + 1].split(","); + } else { + const entries = await fs.readdir(partsDir); + files = entries.filter((file) => file.endsWith(".txt")).sort(); + } + + let mergedContent = ""; + + for (const file of files) { + const filePath = path.join(partsDir, file); + const content = await fs.readFile(filePath, "utf8"); + mergedContent += content; + } + + await fs.writeFile(outputFile, mergedContent); }; await merge(); diff --git a/src/fs/restore.js b/src/fs/restore.js index 96ae1ffb..fa23d37f 100644 --- a/src/fs/restore.js +++ b/src/fs/restore.js @@ -1,8 +1,39 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + const restore = async () => { // Write your code here // Read snapshot.json // Treat snapshot.rootPath as metadata only // Recreate directory/file structure in workspace_restored + try { + const snapshotRaw = await fs.readFile("snapshot.json", "utf8"); + const snapshot = JSON.parse(snapshotRaw); + + const destDir = path.join(process.cwd(), "workspace_restored"); + + try { + await fs.access(destDir); + throw new Error(); + } catch {} + + for (const entry of snapshot.entries) { + const entryPath = path.join(destDir, entry.path); + + if (entry.type === "directory") { + await fs.mkdir(entryPath, { recursive: true }); + } else if (entry.type === "file") { + await fs.mkdir(path.dirname(entryPath), { recursive: true }); + const contentBuffer = Buffer.from(entry.content, "base64"); + await fs.writeFile(entryPath, contentBuffer); + } + } + + console.log("workspace_restored created successfully"); + } catch (err) { + console.error("FS operation failed"); + process.exit(1); + } }; await restore(); diff --git a/src/fs/snapshot.js b/src/fs/snapshot.js index 050103d3..26b876d1 100644 --- a/src/fs/snapshot.js +++ b/src/fs/snapshot.js @@ -1,9 +1,53 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + +const scanDir = async (dir, baseDir) => { + const entries = []; + const items = await fs.readdir(dir, { withFileTypes: true }); + + for (const item of items) { + const fullPath = path.join(dir, item.name); + const relativePath = path.relative(baseDir, fullPath); + + if (item.isDirectory()) { + entries.push({ path: relativePath, type: "directory" }); + const subEntries = await scanDir(fullPath, baseDir); + entries.push(...subEntries); + } else if (item.isFile()) { + const contentBuffer = await fs.readFile(fullPath); + entries.push({ + path: relativePath, + type: "file", + size: contentBuffer.length, + content: contentBuffer.toString("base64"), + }); + } + } + + return entries; +}; + 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 workspaceDir = path.join(process.cwd(), "workspace"); + + try { + await fs.access(workspaceDir); + const entries = await scanDir(workspaceDir, workspaceDir); + + const snapshotData = { rootPath: workspaceDir, entries }; + await fs.writeFile("snapshot.json", JSON.stringify(snapshotData, null, 2)); + + console.log("snapshot.json created successfully"); + } catch (err) { + console.error("FS operation failed"); + process.exit(1); + } }; await snapshot(); diff --git a/src/hash/verify.js b/src/hash/verify.js index 7f1e8961..760967b9 100644 --- a/src/hash/verify.js +++ b/src/hash/verify.js @@ -1,8 +1,34 @@ +import fs from "node:fs"; +import path from "node:path"; +import crypto from "node:crypto"; + const verify = async () => { // Write your code here // Read checksums.json // Calculate SHA256 hash using Streams API // Print result: filename — OK/FAIL + const checksums = JSON.parse( + await fs.promises.readFile("checksums.json", "utf8"), + ); + + for (const item of checksums) { + const filePath = path.join("workspace", item.file); + const hash = crypto.createHash("sha256"); + const stream = fs.createReadStream(filePath); + + await new Promise((resolve, reject) => { + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("end", () => resolve()); + stream.on("error", reject); + }); + + const digest = hash.digest("hex").toLowerCase(); + if (digest === item.hash.toLowerCase()) { + console.log(`${item.file} — OK`); + } else { + console.log(`${item.file} — FAIL`); + } + } }; await verify(); diff --git a/src/modules/dynamic.js b/src/modules/dynamic.js index 008ca387..f3c3b0e6 100644 --- a/src/modules/dynamic.js +++ b/src/modules/dynamic.js @@ -4,6 +4,21 @@ const dynamic = async () => { // Dynamically import plugin from plugins/ directory // Call run() function and print result // Handle missing plugin case + + const pluginName = process.argv[2]; + if (!pluginName) { + console.log("Plugin name not provided"); + process.exit(1); + } + + try { + const plugin = await import(`./plugins/${pluginName}.js`); + const result = await plugin.run(); + console.log(result); + } catch { + console.log("Plugin not found"); + process.exit(1); + } }; await dynamic(); diff --git a/src/streams/filter.js b/src/streams/filter.js index 3868ab46..525a8b83 100644 --- a/src/streams/filter.js +++ b/src/streams/filter.js @@ -1,9 +1,26 @@ +import { Transform } from "node:stream"; + const filter = () => { // Write your code here // Read from process.stdin // Filter lines by --pattern CLI argument // Use Transform Stream // Write to process.stdout + const patternArg = process.argv.find((arg) => arg.startsWith("--pattern=")); + const pattern = patternArg ? patternArg.split("=")[1] : ""; + + const transform = new Transform({ + transform(chunk, encoding, callback) { + const lines = chunk.toString().split("\n"); + const filtered = lines + .filter((line) => line.includes(pattern)) + .join("\n"); + this.push(filtered ? filtered + "\n" : ""); + callback(); + }, + }); + + process.stdin.pipe(transform).pipe(process.stdout); }; filter(); diff --git a/src/streams/lineNumberer.js b/src/streams/lineNumberer.js index 579d662e..7166d58b 100644 --- a/src/streams/lineNumberer.js +++ b/src/streams/lineNumberer.js @@ -1,8 +1,25 @@ +import { Transform } from "node:stream"; + const lineNumberer = () => { // Write your code here // Read from process.stdin // Use Transform Stream to prepend line numbers // Write to process.stdout + + let lineCount = 0; + + const transform = new Transform({ + transform(chunk, encoding, callback) { + const lines = chunk.toString().split("\n"); + const numbered = lines + .map((line) => (line ? `${++lineCount}: ${line}` : "")) + .join("\n"); + this.push(numbered + "\n"); + callback(); + }, + }); + + process.stdin.pipe(transform).pipe(process.stdout); }; lineNumberer(); diff --git a/src/streams/split.js b/src/streams/split.js index f8f814fa..15a85f31 100644 --- a/src/streams/split.js +++ b/src/streams/split.js @@ -1,8 +1,45 @@ +import fs from "node:fs"; +import path from "node:path"; + const split = async () => { // Write your code here // Read source.txt using Readable Stream // Split into chunk_1.txt, chunk_2.txt, etc. // Each chunk max N lines (--lines CLI argument, default: 10) + + const linesArg = process.argv.find((arg) => arg.startsWith("--lines=")); + const maxLines = linesArg ? parseInt(linesArg.split("=")[1]) : 10; + + const inputFile = "source.txt"; + const stream = fs.createReadStream(inputFile, { encoding: "utf8" }); + + let buffer = ""; + let lineCount = 0; + let chunkIndex = 1; + let lines = []; + + stream.on("data", (chunk) => { + buffer += chunk; + const parts = buffer.split("\n"); + buffer = parts.pop(); + + for (const line of parts) { + lines.push(line); + lineCount++; + if (lineCount === maxLines) { + fs.writeFileSync(`chunk_${chunkIndex}.txt`, lines.join("\n")); + chunkIndex++; + lines = []; + lineCount = 0; + } + } + }); + + stream.on("end", () => { + if (lines.length > 0) { + fs.writeFileSync(`chunk_${chunkIndex}.txt`, lines.join("\n")); + } + }); }; await split(); diff --git a/src/wt/main.js b/src/wt/main.js index d7d21f0c..e94ecb88 100644 --- a/src/wt/main.js +++ b/src/wt/main.js @@ -1,3 +1,7 @@ +import { Worker } from "node:worker_threads"; +import fs from "node:fs/promises"; +import os from "node:os"; + const main = async () => { // Write your code here // Read data.json containing array of numbers @@ -6,6 +10,48 @@ const main = async () => { // Collect sorted chunks // Merge using k-way merge algorithm // Log final sorted array + const data = JSON.parse(await fs.readFile("data.json", "utf8")); + const cores = os.cpus().length; + const chunkSize = Math.ceil(data.length / cores); + + const chunks = []; + for (let i = 0; i < data.length; i += chunkSize) { + chunks.push(data.slice(i, i + chunkSize)); + } + + const workers = chunks.map( + (chunk) => + new Promise((resolve, reject) => { + const worker = new Worker(new URL("./worker.js", import.meta.url)); + worker.on("message", resolve); + worker.on("error", reject); + worker.postMessage(chunk); + }), + ); + + const sortedChunks = await Promise.all(workers); + + const result = []; + const pointers = Array(sortedChunks.length).fill(0); + + while (true) { + let min = Infinity; + let minIdx = -1; + for (let i = 0; i < sortedChunks.length; i++) { + if ( + pointers[i] < sortedChunks[i].length && + sortedChunks[i][pointers[i]] < min + ) { + min = sortedChunks[i][pointers[i]]; + minIdx = i; + } + } + if (minIdx === -1) break; + result.push(min); + pointers[minIdx]++; + } + + console.log(result); }; await main(); diff --git a/src/wt/worker.js b/src/wt/worker.js index 15f42fc8..bf0c68a4 100644 --- a/src/wt/worker.js +++ b/src/wt/worker.js @@ -1,9 +1,10 @@ -import { parentPort } from 'worker_threads'; - +import { parentPort } from "worker_threads"; // Receive array from main thread // Sort in ascending order // Send back to main thread -parentPort.on('message', (data) => { +parentPort.on("message", (data) => { // Write your code here + const sorted = data.sort((a, b) => a - b); + parentPort.postMessage(sorted); }); diff --git a/src/zip/compressDir.js b/src/zip/compressDir.js index 3a3c5089..5fa4c1b5 100644 --- a/src/zip/compressDir.js +++ b/src/zip/compressDir.js @@ -1,9 +1,29 @@ +import fs from "node:fs"; +import path from "node:path"; +import { createBrotliCompress } from "node:zlib"; + const compressDir = async () => { // Write your code here // Read all files from workspace/toCompress/ // Compress entire directory structure into archive.br // Save to workspace/compressed/ // Use Streams API + const inputDir = path.join("workspace", "toCompress"); + const outputDir = path.join("workspace", "compressed"); + fs.mkdirSync(outputDir, { recursive: true }); + + const output = fs.createWriteStream(path.join(outputDir, "archive.br")); + const compress = createBrotliCompress(); + compress.pipe(output); + + const files = fs.readdirSync(inputDir); + for (const file of files) { + const data = fs.readFileSync(path.join(inputDir, file)); + compress.write(data); + } + + compress.end(); + compress.on("finish", () => console.log("Compression done")); }; await compressDir(); diff --git a/src/zip/decompressDir.js b/src/zip/decompressDir.js index d6e770f6..5e641366 100644 --- a/src/zip/decompressDir.js +++ b/src/zip/decompressDir.js @@ -1,8 +1,26 @@ +import fs from "node:fs"; +import path from "node:path"; +import { pipeline } from "node:stream"; +import { promisify } from "node:util"; +import { createBrotliDecompress } from "node:zlib"; + +const pipe = promisify(pipeline); + const decompressDir = async () => { // Write your code here // Read archive.br from workspace/compressed/ // Decompress and extract to workspace/decompressed/ // Use Streams API + const input = path.join("workspace", "compressed", "archive.br"); + const outputDir = path.join("workspace", "decompressed"); + fs.mkdirSync(outputDir, { recursive: true }); + + const decompress = createBrotliDecompress(); + const output = fs.createWriteStream(path.join(outputDir, "restoredFile.txt")); + + await pipe(fs.createReadStream(input), decompress, output); + + console.log("Decompression done"); }; await decompressDir(); diff --git a/submission.md b/submission.md new file mode 100644 index 00000000..25c79da2 Binary files /dev/null and b/submission.md differ