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
41 changes: 41 additions & 0 deletions implement-shell-tools/cat/cat.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { readFile, stat } from 'node:fs/promises';
import { program } from 'commander';

program
.name('cat')
.description('Prints contents of files')
.option('-n, --number-all-lines', 'Number all output lines')
.option('-b, --number-non-blank', 'Number non-blank output lines')
.argument('<files...>', 'Files to read');

program.parse();

const { numberAllLines, numberNonBlank } = program.opts();
const files = program.args;

let lineNumber = 1;

for (const file of files) {
try {
const fileStat = await stat(file);
if (fileStat.isDirectory()) {
console.error(`cat: ${file}: Is a directory`);
continue;
}

const content = await readFile(file, 'utf-8');
const lines = content.split('\n');

for (const line of lines) {
const shouldNumber = (numberAllLines && !numberNonBlank) || (numberNonBlank && line.trim() !== '');
if (shouldNumber) {
console.log(`${lineNumber.toString().padStart(6)} ${line}`);
lineNumber++;
} else {
console.log(line);
}
}
} catch (err) {
console.error(`cat: ${file}: ${err.message}`);
}
}
37 changes: 37 additions & 0 deletions implement-shell-tools/ls/ls.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { program } from "commander";
import { promises as fs } from "fs";
import process from "process";

program
.name("ls")
.description("Lists the files in a directory")
.option("-1, --one", "One per line")
.option("-a, --all", "Include files starting with dot")
.argument("<path>", "Directory to list");

program.parse(process.argv);

const args = program.args;
const opts = program.opts();

if (args.length !== 1) {
console.error("Expected 1 argument");
process.exit(1);
}

try {
let files = await fs.readdir(args[0]);
if (!opts.all) {
files = files.filter(f => !f.startsWith('.'));
}

files.sort();

if (opts.one) {
console.log(files.join('\n'));
} else {
console.log(files.join(' '));
}
} catch (err) {
console.error(`ls: cannot access '${args[0]}': ${err.message}`);
}
88 changes: 88 additions & 0 deletions implement-shell-tools/wc/wc.mjs

Choose a reason for hiding this comment

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

Throughout this file there is quite a lot of duplication. Can you think of how you might reduce this?

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { program } from "commander";
import { readFileSync, existsSync } from "fs";
import process from "process";

program
.name("wc")
.description("Count lines, words, and characters in files")
.argument("[files...]", "Files to process")
.option("-l, --lines", "Count lines")
.option("-w, --words", "Count words")
.option("-c, --chars", "Count characters (bytes)");

program.parse(process.argv);

const options = program.opts();
const files = program.args.length ? program.args : ["/dev/stdin"];

// Determine active counts
const activeCounts = {
lines: options.lines || (!options.words && !options.chars),
words: options.words || (!options.lines && !options.chars),
chars: options.chars || (!options.lines && !options.words),
};

function countFile(filePath) {
let content = "";
try {
if (filePath === "/dev/stdin") {
content = readFileSync(process.stdin.fd, "utf8");
} else {
if (!existsSync(filePath)) {
console.error(`wc: ${filePath}: No such file or directory`);
return null;
}
content = readFileSync(filePath, "utf8");
}
} catch (error) {
console.error(`wc: ${filePath}: ${error.message}`);
return null;
}

const lineCount = (content.match(/\n/g) || []).length;
const wordCount = content.trim().split(/\s+/).filter(Boolean).length;
const charCount = Buffer.byteLength(content, "utf8");

return {
file: filePath,
lines: activeCounts.lines ? lineCount : null,
words: activeCounts.words ? wordCount : null,
chars: activeCounts.chars ? charCount : null,
};
}

function formatCounts(result) {
const output = [];
if (result.lines !== null) output.push(result.lines.toString().padStart(8));
if (result.words !== null) output.push(result.words.toString().padStart(8));
if (result.chars !== null) output.push(result.chars.toString().padStart(8));
return output.join(" ");
}

const results = [];
let totalLines = 0, totalWords = 0, totalChars = 0;
const hasMultipleFiles = files.length > 1;

for (const file of files) {
const result = countFile(file);
if (result) {
results.push(result);
if (result.lines !== null) totalLines += result.lines;
if (result.words !== null) totalWords += result.words;
if (result.chars !== null) totalChars += result.chars;
}
}

// Print per-file results
results.forEach(result => console.log(`${formatCounts(result)} ${result.file}`));

// Print totals if more than one file
if (hasMultipleFiles && results.length > 0) {
const total = {
file: "total",
lines: activeCounts.lines ? totalLines : null,
words: activeCounts.words ? totalWords : null,
chars: activeCounts.chars ? totalChars : null,
};
console.log(`${formatCounts(total)} total`);
}