Skip to content
43 changes: 43 additions & 0 deletions implement-shell-tools/cat/my_cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { promises as fs } from 'node:fs';
import { argv } from 'node:process';

const args = argv.slice(2); // skip node and script name

let showLineNumb = false;
let showNoBlankNumb = false
const files = [];


args.forEach(arg => {
if (arg === '-n') {
showLineNumb = true;
} else if (arg === '-b') {
showNoBlankNumb = true;
}else {
files.push(arg);
}
});

if (files.length === 0) {
console.error('No input file specified');
process.exit(1);
}

for (const file of files) {
try {
let content = await fs.readFile(file, 'utf-8');
const lines = content.split('\n');

if (showNoBlankNumb) { // Number only non-blank lines
let counter = 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try testing your updated solution with the numbering options on with multiple files now your app supports it. How would you expect it to work? If you compare your solution to the original cat, can you see a discrepancy?

content = lines.map(line =>
line.trim() === '' ? '' : `${counter++}\t${line}`
).join('\n');
} else if (showLineNumb) { // Number all lines
content = lines.map((line, i) => `${i + 1}\t${line}`).join('\n');
}
console.log(content);
} catch (err) {
console.error(`Cannot access '${file}': ${err.message}`);
}
}
32 changes: 32 additions & 0 deletions implement-shell-tools/ls/my_ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { readdirSync } from 'node:fs';
import { argv } from 'node:process';

const args = argv.slice(2); // skips node and script name

const flags = args.filter(arg => arg.startsWith('-'));
const operands = args.filter(arg => !arg.startsWith('-'));

let showAll = flags.includes('-a');
let onePerLine = flags.includes('-1');

if (operands.length === 0) operands.push('.'); // defaults to current dir

operands.forEach(dir => {
try {
let files = readdirSync(dir); // Reads the contents of the dir synchronously (readdirSync)

if (!showAll) {
files = files.filter(file => !file.startsWith('.'));
}

files.sort((a, b) => a.localeCompare(b));

if (onePerLine) {
files.forEach(file => console.log(file));
} else {
console.log(files.join(' '));
}
} catch (err) {
console.error(`ls: cannot access '${dir}': ${err.message}`);
}
});
96 changes: 96 additions & 0 deletions implement-shell-tools/wc/my_wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { promises as fs } from 'node:fs';
import { argv } from 'node:process';

const args = argv.slice(2);

if (args.length === 0) {
console.error('Usage: node my_wc.mjs [-l] [-w] [-c] <file> [file2 ...]');
process.exit(1);
}

// Parse flags and files
const flags = {
l: false,
w: false,
c: false,
};

const files = [];

for (const arg of args) {
if (arg.startsWith('-') && arg.length > 1) {
for (const ch of arg.slice(1)) {
if (flags.hasOwnProperty(ch)) {
flags[ch] = true;
} else {
console.error(`Unknown option: -${ch}`);
process.exit(1);
}
}
} else {
files.push(arg);
}
}

if (files.length === 0) {
console.error('Error: no files specified.');
process.exit(1);
}

// If no flags specified, show all by default
const showLines = flags.l || (!flags.l && !flags.w && !flags.c);
const showWords = flags.w || (!flags.l && !flags.w && !flags.c);
const showBytes = flags.c || (!flags.l && !flags.w && !flags.c);

function pad(num, width = 8) {
return num.toString().padStart(width, ' ');
}

function formatOutput({ lines, words, bytes }, label) {
let output = '';
if (showLines) output += pad(lines);
if (showWords) output += pad(words);
if (showBytes) output += pad(bytes);
output += ` ${label}`;
return output;
}

async function countFile(file) {
try {
const content = await fs.readFile(file, 'utf-8');
const lines = content.split('\n').length - 1;
const words = content.trim().split(/\s+/).filter(Boolean).length;
const bytes = Buffer.byteLength(content, 'utf-8');

const counts = { lines, words, bytes };
console.log(formatOutput(counts, file));

return counts;
} catch (err) {
console.error(`Cannot access '${file}': ${err.message}`);
return null;
}
}

async function main() {
// Instead of .map, I can explicitly collect promises using forEach():
const promises = [];
files.forEach(file => {
promises.push(countFile(file));
});
const counts = await Promise.all(promises);

const validCounts = counts.filter(c => c !== null);

if (validCounts.length > 1) {
const totals = {
lines: validCounts.reduce((sum, c) => sum + c.lines, 0),
words: validCounts.reduce((sum, c) => sum + c.words, 0),
bytes: validCounts.reduce((sum, c) => sum + c.bytes, 0),
}

console.log(formatOutput(totals, 'total'));
}
}

main();
Loading