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.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"modules:dynamic": "node src/modules/dynamic.js uppercase",
"hash:verify": "node src/hash/verify.js",
"streams:lineNumberer": "echo 'hello\nworld' | node src/streams/lineNumberer.js",
"streams:lineNumbererWin": "echo 'hello' & echo 'world' | node src/streams/lineNumberer.js",
"streams:filter": "echo 'hello\nworld\ntest' | node src/streams/filter.js --pattern test",
"streams:split": "node src/streams/split.js --lines 10",
"zip:compressDir": "node src/zip/compressDir.js",
Expand All @@ -35,4 +36,4 @@
],
"author": "alreadybored",
"license": "ISC"
}
}
30 changes: 26 additions & 4 deletions src/cli/interactive.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
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 });

rl.setPrompt('> ');
rl.prompt();

rl.on('line', (line) => {
const cmd = line.trim();
if (cmd === 'uptime') {
console.log(process.uptime());
} else if (cmd === 'cwd') {
console.log(process.cwd());
} else if (cmd === 'date') {
console.log(new Date().toISOString());
} else if (cmd === 'exit') {
process.exit(0);
} else {
console.log(`Unknown command: ${cmd}`);
}
rl.prompt();
});

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

interactive();
35 changes: 31 additions & 4 deletions src/cli/progress.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
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 total = 50;
const barWidth = 30;
let step = 0;

const args = process.argv;
const colorIdx = args.indexOf('--color');
let ansiColor = '';
let ansiReset = '';

if (colorIdx !== -1 && args[colorIdx + 1]) {
const hex = args[colorIdx + 1].replace('#', '');
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
ansiColor = `\x1b[38;2;${r};${g};${b}m`;
ansiReset = '\x1b[0m';
}

const interval = setInterval(() => {
step++;
const percent = Math.round((step / total) * 100);
const filled = Math.round((step / total) * barWidth);
const filledBar = '█'.repeat(filled);
const emptyBar = ' '.repeat(barWidth - filled);
process.stdout.write(`\r[${ansiColor}${filledBar}${ansiReset}${emptyBar}] ${percent}%`);

if (step >= total) {
clearInterval(interval);
process.stdout.write('\n');
}
}, 100);
};

progress();
19 changes: 13 additions & 6 deletions src/cp/execCommand.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { spawn } from '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 command = process.argv[2];
if (!command) {
console.error('Please provide a command as an argument.');
process.exit(1);
}

const child = spawn(command, { shell: true, stdio: 'inherit' });

child.on('close', (code) => {
process.exit(code ?? 0);
});
};

execCommand();
43 changes: 40 additions & 3 deletions src/fs/findByExt.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,44 @@
import fs from "fs";
import path from "path";

async function scanDir(dirPath, ext, results = []) {
const items = await fs.promises.readdir(dirPath);

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

if (stat.isDirectory()) {
if (item === "node_modules") continue;
await scanDir(fullPath, ext, results);
} else {
if (path.extname(item) === ext) {
results.push(fullPath);
}
}
}

return results;
}

const findByExt = async () => {
// Write your code here
// Recursively find all files with specific extension
// Parse --ext CLI argument (default: .txt)
const args = process.argv;
const extIndex = args.indexOf("--ext");
let ext = extIndex !== -1 ? args[extIndex + 1] : ".txt";

if (!ext.startsWith(".")) ext = "." + ext;

const results = await scanDir(process.cwd(), ext);
results.sort();

if (results.length === 0) {
console.log(`No files found with extension ${ext}`);
} else {
console.log(`Found ${results.length} file(s) with extension ${ext}:`);
for (const file of results) {
console.log(" ", file);
}
}
};

await findByExt();
35 changes: 31 additions & 4 deletions src/fs/merge.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
import fs from "fs";
import path from "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 partsDir = path.join(process.cwd(), "workspace", "parts");
const outputFile = path.join(process.cwd(), "workspace", "merged.txt");

let files;

const filesIndex = process.argv.indexOf("--files");
if (filesIndex !== -1) {
const names = process.argv[filesIndex + 1].split(",");
files = names.map((name) => path.join(partsDir, name.trim()));
} else {
const items = await fs.promises.readdir(partsDir);
const txtFiles = items.filter((item) => path.extname(item) === ".txt");
txtFiles.sort();
files = txtFiles.map((name) => path.join(partsDir, name));
}

let merged = "";
for (const file of files) {
const content = await fs.promises.readFile(file, "utf-8");
merged += content;
}

await fs.promises.writeFile(outputFile, merged, "utf-8");

console.log(`✓ Merged ${files.length} file(s) into workspace/merged.txt`);
for (const file of files) {
console.log(" ", path.basename(file));
}
};

await merge();
24 changes: 20 additions & 4 deletions src/fs/restore.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import fs from "fs";
import path from "path";

const restore = async () => {
// Write your code here
// Read snapshot.json
// Treat snapshot.rootPath as metadata only
// Recreate directory/file structure in workspace_restored
const snapshotFile = await fs.promises.readFile("snapshot.json", "utf-8");
const snapshot = JSON.parse(snapshotFile);

const restoreDir = path.join(process.cwd(), "workspace_restored");

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

for (const entry of snapshot.entries) {
const entryPath = path.join(restoreDir, entry.path);

if (entry.type === "directory") {
await fs.promises.mkdir(entryPath, { recursive: true });
} else {
await fs.promises.mkdir(path.dirname(entryPath), { recursive: true });
await fs.promises.writeFile(entryPath, Buffer.from(entry.content, "base64"));
}
}
};

await restore();
43 changes: 38 additions & 5 deletions src/fs/snapshot.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,42 @@
import fs from "fs";
import path from "path";

async function scanDirectory(dirPath, basePath, entries = []) {
const items = await fs.promises.readdir(dirPath);

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

if (stat.isDirectory()) {
entries.push({ path: relativePath, type: "directory" });
await scanDirectory(fullPath, basePath, entries);
} else {
const content = await fs.promises.readFile(fullPath);
entries.push({
path: relativePath,
type: "file",
size: stat.size,
content: content.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 workspacePath = path.resolve("workspace");
const entries = await scanDirectory(workspacePath, workspacePath);

const data = { rootPath: workspacePath, entries };

await fs.promises.writeFile(
"snapshot.json",
JSON.stringify(data, null, 2),
"utf-8",
);
};

await snapshot();
28 changes: 24 additions & 4 deletions src/hash/verify.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import fs from 'fs';
import crypto from 'crypto';
import { readFile } from 'fs/promises';

const hashFile = (filePath) =>
new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256');
const stream = fs.createReadStream(filePath);
stream.on('error', reject);
stream.on('data', (chunk) => hash.update(chunk));
stream.on('end', () => resolve(hash.digest('hex')));
});

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 readFile('checksums.json', 'utf8'));

for (const [filename, expected] of Object.entries(checksums)) {
try {
const actual = await hashFile(filename);
const status = actual === expected ? 'OK' : 'FAIL';
console.log(`${filename} — ${status}`);
} catch {
console.log(`${filename} — FAIL`);
}
}
};

await verify();
24 changes: 19 additions & 5 deletions src/modules/dynamic.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { fileURLToPath } from 'url';
import path from 'path';

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 name = process.argv[2];
if (!name) {
console.error('Please provide a plugin name as an argument.');
process.exit(1);
}

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const pluginPath = path.join(__dirname, 'plugins', `${name}.js`);

try {
const plugin = await import(pluginPath);
console.log(plugin.run());
} catch {
console.error(`Plugin "${name}" not found.`);
process.exit(1);
}
};

await dynamic();
33 changes: 28 additions & 5 deletions src/streams/filter.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
import { Transform } from '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 patternIndex = process.argv.indexOf('--pattern');
const pattern = patternIndex !== -1 ? process.argv[patternIndex + 1] : '';

let buffer = '';

const transform = new Transform({
transform(chunk, encoding, callback) {
buffer += chunk.toString();
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (line.includes(pattern)) {
this.push(`${line}\n`);
}
}
callback();
},
flush(callback) {
if (buffer.length > 0 && buffer.includes(pattern)) {
this.push(`${buffer}\n`);
}
callback();
},
});

process.stdin.pipe(transform).pipe(process.stdout);
};

filter();
Loading