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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
node_modules
**/.venv
**/requirements.txt
62 changes: 62 additions & 0 deletions implement-shell-tools/cat/cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { program } from "commander";
import { promises as fs } from "node:fs";

program
.name("node-cat")
.description("A Node.js implementation of the Unix cat command")
.option("-n, --number", "Number all output lines")
.option(
"-b, --numberNonBlank",
"Numbers only non-empty lines. Overrides -n option"
)
.argument("<path...>", "The file path to process");

program.parse();

const paths = program.args;
const { number, numberNonBlank } = program.opts();

// --- Read files ---
let content = "";

for (const path of paths) {
content += await fs.readFile(path, "utf-8");
}

// Remove the trailing newline
// I do realise that this is not exactly how cat works, but for the files that we have, we get a trailing new line and this makes the output look just as it would with the Unix cat command.
if (content.endsWith("\n")) {
content = content.slice(0, -1);
}

const contentLines = content.split("\n");

// --- Numbering functions ---
function numberAll(lines) {
return lines.map(
(line, index) => `${String(index + 1).padStart(6, " ")} ${line}`
);
}

function numberNonEmpty(lines) {
let lineCounter = 1;
return lines.map((line) =>
line.trim() === ""
? line
: `${String(lineCounter++).padStart(6, " ")} ${line}`
);
}

// --- Output logic ---
let output;

if (numberNonBlank) {
output = numberNonEmpty(contentLines);
} else if (number) {
output = numberAll(contentLines);
} else {
output = contentLines;
}

// --- Print output ---
console.log(output.join("\n"));
32 changes: 32 additions & 0 deletions implement-shell-tools/ls/ls.js

Choose a reason for hiding this comment

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

Very clean and concise solution!

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { program } from "commander";
import { promises as fs } from "node:fs";

program
.name("node-ls")
.description("A Node.js implementation of the Unix ls command")
.option("-1", "list one file per line")
.option(
"-a, --all",
"include directory entries whose names begin with a dot (.)"
)
.argument("[directory]", "The file path to process");
program.parse();

const { 1: onePerLine, all } = program.opts();
const directory = program.args[0] ? program.args[0] : ".";

let entries = await fs.readdir(directory);

// If -a is used, I've included "." and ".." to mimic what the Unix ls does
if (all) {
entries = [".", "..", ...entries];
} else {
// hide dotfiles
entries = entries.filter((entry) => entry[0] !== ".");
}

if (onePerLine) {
console.log(entries.join("\n"));
} else {
console.log(entries.join(" "));
}
21 changes: 21 additions & 0 deletions implement-shell-tools/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions implement-shell-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "module",
"dependencies": {
"commander": "^14.0.2"
}
}
100 changes: 100 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { program } from "commander";
import { promises as fs } from "node:fs";

program
.name("node-wc")
.description("A Node.js implementation of the Unix wc command")
.option("-l, --lines", "Print the newline counts")
.option("-w, --words", "Print the word counts")
.option("-c, --bytes", "Print the byte counts")
.argument("<path...>", "The file path to process");

program.parse();

const filePaths = program.args;
const { lines, words, bytes } = program.opts();

// When no options are provided, show all counts
const showAll = !lines && !words && !bytes;

// --- Read files and sizes ---
let fileContent = "";
let outputData = [];

let lineCountTotal = 0,
wordCountTotal = 0,
fileSizeTotal = 0;

for (const path of filePaths) {
let fileStats;
let fileData = {};

// Count of flags and arguments provided -- basically state
fileData.countOfFlags = Object.values(program.opts()).filter(Boolean).length;
fileData.filePaths = filePaths.length;

fileContent = await fs.readFile(path, "utf-8");

fileData.lineCount = getLineCount(fileContent);
lineCountTotal += fileData.lineCount;

fileData.wordCount = getWordCount(fileContent);
wordCountTotal += fileData.wordCount;

fileStats = await fs.stat(path);
fileData.fileSize = fileStats.size;
fileSizeTotal += fileData.fileSize;

fileData.path = path;
outputData.push(fileData);
}

console.log(outputData.map(formatOutput).join("\n"));

if (filePaths.length > 1) {
console.log(
formatOutput({
lineCount: lineCountTotal,
wordCount: wordCountTotal,
fileSize: fileSizeTotal,
path: "total",
})
);
}

function formatOutput({
lineCount,
wordCount,
fileSize,
path,
countOfFlags,
filePaths,
}) {
let output = [];

// I've added this if statement as I found my node wc output looked misaligned compared to the Unix wc output when only one flag and one file were provided.
if (countOfFlags === 1 && filePaths <= 1) {
if (lines || showAll) output.push(String(lineCount));
if (words || showAll) output.push(String(wordCount));
if (bytes || showAll) output.push(String(fileSize));
} else {
if (lines || showAll) output.push(String(lineCount).padStart(3));
if (words || showAll) output.push(String(wordCount).padStart(4));
if (bytes || showAll) output.push(String(fileSize).padStart(4));
}

return `${output.join("")} ${path}`;
}

function getWordCount(text) {
let words, lines;

lines = text.split("\n");
words = lines.flatMap((line) => line.split(" "));

return words.filter((word) => word.length > 0).length;
}

function getLineCount(text) {
return (text.match(/\n/g) || []).length;
}