Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
40 changes: 40 additions & 0 deletions implement-shell-tools/cat/cat.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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, numNotBlank } = 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 && !numNotBlank) || (numNotBlank && line.trim() !== '');

Choose a reason for hiding this comment

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

You are not numbering non-blank lines when -b is used. Can you see why that is?


if (shouldNumber) {
console.log(`${lineNumber.toString().padStart(6)} ${line}`);
lineNumber++;
} else {
console.log(line);
}
}
} catch (err) {
console.error(`cat: ${file}: ${err.message}`);
}
}
24 changes: 24 additions & 0 deletions implement-shell-tools/ls/ls.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { program } from "commander"
import process from "node:process"
import { promises as fs } from "node:fs"
import { readdir } from 'node:fs/promises'

program
.name("ls")
.description("Lists the files in a directory")
.option("-1, --one", "One per line")
.option("-a", "Include files starting with dot")

Choose a reason for hiding this comment

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

You offer -a as an option, but is this ever used?

.argument("filepath")
program.parse(process.argv)

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

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

const content = await fs.readdir(argv[0])

console.log(content.join(opts.one ? "\n": " "))
89 changes: 89 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,89 @@
import { program } from "commander";
import process from "node:process";
import { readFileSync, existsSync } from "node:fs";

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"];

function countFile(filePath, options) {
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;
}

if (typeof content !== 'string') {
content = "";
}

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: options.lines || (!options.words && !options.chars) ? lineCount : null,
words: options.words || (!options.lines && !options.chars) ? wordCount : null,
chars: options.chars || (!options.lines && !options.words) ? charCount : null,
};
}

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

files.forEach(file => {
const result = countFile(file, options);
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;
}
});

results.forEach(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));
console.log(output.join(" "), result.file);
});

if (hasMultipleFiles && results.length > 0) {
const totalOutput = [];
if (options.lines || (!options.words && !options.chars)) {
totalOutput.push(totalLines.toString().padStart(8));
}
if (options.words || (!options.lines && !options.chars)) {
totalOutput.push(totalWords.toString().padStart(8));
}
if (options.chars || (!options.lines && !options.words)) {
totalOutput.push(totalChars.toString().padStart(8));
}
console.log(totalOutput.join(" "), "total");
}