Skip to content
79 changes: 79 additions & 0 deletions implement-shell-tools/cat/sample-files/myCat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env node
const { program } = require("commander");
const fs = require("fs");
const path = require("path");



function expandWildcard(pattern) {
const dir = path.dirname(pattern);
const base = path.basename(pattern);

if (!base.includes("*")) return [pattern];

let files;
try {
files = fs.readdirSync(dir);
} catch (e) {
console.error(`cat: ${pattern}: No such directory`);
return [];
}

const regex = new RegExp("^" + base.replace(/\*/g, ".*") + "$");

return files
.filter((f) => regex.test(f))
.map((f) => path.join(dir, f));
}

function printFile(filename, options) {
let text;
try {
text = fs.readFileSync(filename, "utf-8");
} catch {
console.error(`cat: ${filename}: No such file`);
return;
}

const lines = text.split("\n");
if (lines[lines.length - 1] === "") {
lines.pop();
}
let counter = 1;

lines.forEach((line) => {
if (options.numberAll) {
const paddingSize = 6;
console.log(`${String(counter).padStart(paddingSize)} ${line}`);
counter++;
} else if (options.numberNonempty) {
if (line.trim() === "") {
console.log("");
} else {
console.log(`${String(counter).padStart(paddingSize)} ${line}`);
Comment thread
LonMcGregor marked this conversation as resolved.
Outdated
counter++;
}
} else {
console.log(line);
}
});
}

program
.name("mycat")
.description("A custom implementation of the cat command")
.argument("<files...>", "files or wildcard patterns")
.option("-n, --number-all", "number all lines")
.option("-b, --number-nonempty", "number non-empty lines")
.action((patterns, options) => {
let allFiles = [];

patterns.forEach((p) => {
allFiles = allFiles.concat(expandWildcard(p));
});

allFiles.forEach((file) => printFile(file, options));
});

program.parse();

50 changes: 50 additions & 0 deletions implement-shell-tools/ls/sample-files/myLs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env node
const { program } = require("commander");
const fs = require("fs");
const path = require("path");

function listDirectory(dir, options) {
try {
const stats = fs.statSync(dir);

if (stats.isFile()) {
console.log(dir);
return;
}
} catch (e) {
console.error(`ls: cannot access '${dir}': No such file or directory`);
return;
}

let entries;

try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch (e) {
console.error(`ls: cannot access '${dir}': No such file or directory`);
return;
}

let names = entries.map(e => e.name);

if (options.all) {
names.unshift(".", "..");
} else {
names = names.filter(name => !name.startsWith("."));
}

names.sort();
names.forEach(name => console.log(name));
}

program
.name("myls")
.description("Custom implementation of ls")
.option("-1", "list one file per line (default in our version)")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if -1 is the default, how can you test with this turned off?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@LonMcGregor thank you .Good point — since this implementation only supports -1 output, I’ve made the -1 flag required rather than treating it as a default. This makes the behaviour explicit and testable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good work making these changes. You've made this now a required flag. What does this mean? If the flag is always required and always active, how would you test the alternative, of having the output on a single line?

Copy link
Copy Markdown
Author

@RahwaZeslusHaile RahwaZeslusHaile Jan 17, 2026

Choose a reason for hiding this comment

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

@LonMcGregor, you’re right — making the flag always required means it’s always active, so I wouldn’t be able to test the alternative output (without the single-line format). I updated the implementation so the -1 flag is optional, allowing the script to produce multi-column output when it’s not provided.
Thanks for the feedback!

.option("-a, --all", "include hidden files")
.argument("[dir]", "directory to list", ".")
.action((dir, options) => {
listDirectory(dir, options);
});

program.parse();
97 changes: 97 additions & 0 deletions implement-shell-tools/wc/sample-files/myWc.js
Comment thread
LonMcGregor marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env node
const { program } = require("commander");
const fs = require("fs");
const path = require("path");

function expandWildcard(pattern) {
const dir = path.dirname(pattern);
const base = path.basename(pattern);

if (!base.includes("*")) return [pattern];

let files;
try {
files = fs.readdirSync(dir);
} catch {
console.error(`wc: ${pattern}: No such directory`);
return [];
}

const regex = new RegExp("^" + base.replace(/\*/g, ".*") + "$");
return files
.filter(f => regex.test(f))
.map(f => path.join(dir, f));
}

function countLines(text) {
if (text === "") return 0;
const matches = text.match(/\n/g) || [];
return text.endsWith("\n") ? matches.length : matches.length + 1;
}

function countWords(text) {
return text.split(/\s+/).filter(Boolean).length;
}

function countChars(text) {
return Buffer.byteLength(text, "utf-8");
}

function wcFile(filename, options) {
let text;
try {
text = fs.readFileSync(filename, "utf-8");
} catch {
console.error(`wc: ${filename}: No such file`);
return null;
}

const lineCount = countLines(text);
const wordCount = countWords(text);
const charCount = countChars(text);

let output;
const paddingSize = 7;
if (options.lines && !options.words && !options.chars) output = `${lineCount} ${filename}`;
else if (options.words && !options.lines && !options.chars) output = `${wordCount} ${filename}`;
else if (options.chars && !options.lines && !options.words) output = `${charCount} ${filename}`;
else output = `${String(lineCount).padStart(paddingSize)} ${String(wordCount).padStart(paddingSize)} ${String(charCount).padStart(paddingSize)} ${filename}`;
console.log(output);

return { lines: lineCount, words: wordCount, chars: charCount };
}

program
.name("mywc")
.description("Custom implementation of wc")
.option("-l, --lines", "count lines")
.option("-w, --words", "count words")
.option("-c, --chars", "count characters")
.argument("<files...>", "files or wildcard patterns")
.action((patterns, options) => {
let allFiles = [];
patterns.forEach(p => allFiles = allFiles.concat(expandWildcard(p)));

let totalLines = 0, totalWords = 0, totalChars = 0;

allFiles.forEach(file => {
const result = wcFile(file, options);
if (result) {
totalLines += result.lines;
totalWords += result.words;
totalChars += result.chars;
}
});
const paddingSize = 7;
if (allFiles.length > 1) {
if (options.lines && !options.words && !options.chars) console.log(`${totalLines} total`);
Comment thread
LonMcGregor marked this conversation as resolved.
Outdated
else if (options.words && !options.lines && !options.chars) console.log(`${totalWords} total`);
else if (options.chars && !options.lines && !options.words) console.log(`${totalChars} total`);
else console.log(
`${String(totalLines).padStart(paddingSize)} ` +
`${String(totalWords).padStart(paddingSize)} ` +
`${String(totalChars).padStart(paddingSize)} total`);
}
});

program.parse();
Loading