From 35f0f462db334f22d034b31bcd4fd7573851b10f Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 21 Nov 2025 18:40:04 +0200 Subject: [PATCH 01/19] Project set up, npm packages installed and individual js files created --- implement-shell-tools/cat/cat.js | 42 +++++++++++++++++++++++++ implement-shell-tools/ls/ls.js | 0 implement-shell-tools/package-lock.json | 21 +++++++++++++ implement-shell-tools/package.json | 6 ++++ implement-shell-tools/wc/wc.js | 0 5 files changed, 69 insertions(+) create mode 100644 implement-shell-tools/cat/cat.js create mode 100644 implement-shell-tools/ls/ls.js create mode 100644 implement-shell-tools/package-lock.json create mode 100644 implement-shell-tools/package.json create mode 100644 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 000000000..00de49598 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,42 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("count-containing-words") + .description("Counts words in a file that contain a particular character, optionally gives word or character counts.") + .option("-c, --char ", "The character to search for", "e") + .option("-n, --count [type]", "Count type: words or chars") + .argument("", "The file path to process"); + +program.parse(); + +const argv = program.args; +if (argv.length != 1) { + console.error( + `Expected exactly 1 argument (a path) to be passed but got ${argv.length}.` + ); + process.exit(1); +} + +const path = argv[0]; +// const { char, count } = program.opts(); +console.log(path); + +const content = await fs.readFile(path, "utf-8"); + +const countOfWordsContainingChar = content + .split(/\s+/) + .filter((word) => word.includes(char)).length; + +if (count) { + if (count === "words") { + const countOfWords = content.split(" ").length; + console.log(countOfWordsContainingChar, countOfWords); + } else if (count === "chars") { + const countOfChars = content.length; + console.log(countOfWordsContainingChar, countOfChars); + } +} else { + console.log(countOfWordsContainingChar); +} diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 000000000..e69de29bb diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 000000000..09b3f387a --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "implement-shell-tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "commander": "^14.0.2" + } + }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 000000000..76dcd3f7a --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "commander": "^14.0.2" + } +} diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 000000000..e69de29bb From fa47ca2eec80c0ddbc58437b244f22f6636d1a47 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 21 Nov 2025 19:00:06 +0200 Subject: [PATCH 02/19] basic implementation of cat with 1 file --- implement-shell-tools/cat/cat.js | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 00de49598..ac307cf57 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -3,40 +3,18 @@ import { promises as fs } from "node:fs"; import process from "node:process"; program - .name("count-containing-words") - .description("Counts words in a file that contain a particular character, optionally gives word or character counts.") + .name("node-cat") + .description("A Node.js implementation of the Unix cat command") .option("-c, --char ", "The character to search for", "e") .option("-n, --count [type]", "Count type: words or chars") - .argument("", "The file path to process"); + .argument("", "The file path to process"); program.parse(); const argv = program.args; -if (argv.length != 1) { - console.error( - `Expected exactly 1 argument (a path) to be passed but got ${argv.length}.` - ); - process.exit(1); -} const path = argv[0]; -// const { char, count } = program.opts(); -console.log(path); const content = await fs.readFile(path, "utf-8"); -const countOfWordsContainingChar = content - .split(/\s+/) - .filter((word) => word.includes(char)).length; - -if (count) { - if (count === "words") { - const countOfWords = content.split(" ").length; - console.log(countOfWordsContainingChar, countOfWords); - } else if (count === "chars") { - const countOfChars = content.length; - console.log(countOfWordsContainingChar, countOfChars); - } -} else { - console.log(countOfWordsContainingChar); -} +console.log(content.trim()); From e53c2fd694fba47cb791fca8bb2ec54064f873f6 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 21 Nov 2025 19:12:33 +0200 Subject: [PATCH 03/19] cat implemented for multiple files --- implement-shell-tools/cat/cat.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index ac307cf57..f8a57d9a9 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -11,10 +11,13 @@ program program.parse(); -const argv = program.args; +const paths = program.args; -const path = argv[0]; +let content; +for (const path of paths) { + content + ? (content += await fs.readFile(path, "utf-8")) + : (content = await fs.readFile(path, "utf-8")); +} -const content = await fs.readFile(path, "utf-8"); - -console.log(content.trim()); +console.log(`${content.trim()}`); From 65c7e6ba88f8106bb9c3f777fa8ec4cb3a101fd5 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 21 Nov 2025 20:17:54 +0200 Subject: [PATCH 04/19] -n flag implemented --- implement-shell-tools/cat/cat.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index f8a57d9a9..6e6720e11 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -5,19 +5,39 @@ import process from "node:process"; program .name("node-cat") .description("A Node.js implementation of the Unix cat command") - .option("-c, --char ", "The character to search for", "e") - .option("-n, --count [type]", "Count type: words or chars") + .option("-n, --number", "Number all output lines") + .option( + "-b, --numberNonBlank", + "Numbers only non-empty lines. Overrides -n option" + ) .argument("", "The file path to process"); program.parse(); const paths = program.args; +const { number, numberNonBlank } = program.opts(); + +console.log(number, numberNonBlank); + +let content, + output, + count = 1; -let content; for (const path of paths) { content ? (content += await fs.readFile(path, "utf-8")) : (content = await fs.readFile(path, "utf-8")); } -console.log(`${content.trim()}`); +output = content.trim(); + +if (number) { + const outputArr = output.split("\n"); + for (let i = 0; i < outputArr.length; i++) { + outputArr[i] = `${String(i + 1).padStart(6, " ")} ${outputArr[i]}`; + } + console.log(outputArr.join("\n")); +} + + +// console.log(output); From 2e6fa28bab23f4a06aa2aa14d88d296460080379 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 21 Nov 2025 20:41:46 +0200 Subject: [PATCH 05/19] -b flag implemented --- implement-shell-tools/cat/cat.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 6e6720e11..b660b3fba 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -19,25 +19,29 @@ const { number, numberNonBlank } = program.opts(); console.log(number, numberNonBlank); -let content, - output, - count = 1; +let content = "", + output; for (const path of paths) { - content - ? (content += await fs.readFile(path, "utf-8")) - : (content = await fs.readFile(path, "utf-8")); + content += await fs.readFile(path, "utf-8"); } output = content.trim(); -if (number) { +if (numberNonBlank) { + const outputArr = output.split("\n"); + let lineCounter = 1; + for (let i = 0; i < outputArr.length; i++) { + if (outputArr[i].trim() === "") continue; + outputArr[i] = `${String(lineCounter++).padStart(6, " ")} ${outputArr[i]}`; + } + console.log(outputArr.join("\n")); +} else if (number) { const outputArr = output.split("\n"); for (let i = 0; i < outputArr.length; i++) { outputArr[i] = `${String(i + 1).padStart(6, " ")} ${outputArr[i]}`; } console.log(outputArr.join("\n")); +} else { + console.log(output); } - - -// console.log(output); From 96c6904df6954d849e44e905d0540f80b9cb4c7d Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 21 Nov 2025 23:11:05 +0200 Subject: [PATCH 06/19] Code refactored, functions added for numbering --- implement-shell-tools/cat/cat.js | 52 +++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index b660b3fba..ee5c49ae7 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -17,31 +17,47 @@ program.parse(); const paths = program.args; const { number, numberNonBlank } = program.opts(); -console.log(number, numberNonBlank); - -let content = "", - output; +// --- Read files --- +let content = ""; for (const path of paths) { content += await fs.readFile(path, "utf-8"); } -output = content.trim(); +// 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); +} -if (numberNonBlank) { - const outputArr = output.split("\n"); +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; - for (let i = 0; i < outputArr.length; i++) { - if (outputArr[i].trim() === "") continue; - outputArr[i] = `${String(lineCounter++).padStart(6, " ")} ${outputArr[i]}`; - } - console.log(outputArr.join("\n")); + return lines.map((line) => + line.trim() === "" + ? line + : `${String(lineCounter++).padStart(6, " ")} ${line}` + ); +} + +// --- Output logic --- +let output; + +if (numberNonBlank) { + output = numberNonEmpty(contentLines); } else if (number) { - const outputArr = output.split("\n"); - for (let i = 0; i < outputArr.length; i++) { - outputArr[i] = `${String(i + 1).padStart(6, " ")} ${outputArr[i]}`; - } - console.log(outputArr.join("\n")); + output = numberAll(contentLines); } else { - console.log(output); + output = contentLines; } + +// --- Print output --- +console.log(output.join("\n")); From 72521844dfe448d15e548fd0249d3784f4454e01 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Fri, 21 Nov 2025 23:17:00 +0200 Subject: [PATCH 07/19] Removed unused imports --- implement-shell-tools/cat/cat.js | 1 - 1 file changed, 1 deletion(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index ee5c49ae7..6011dca38 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,6 +1,5 @@ import { program } from "commander"; import { promises as fs } from "node:fs"; -import process from "node:process"; program .name("node-cat") From 8761cb698150224589b8f606fb7cc70158e61f44 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 08:52:44 +0200 Subject: [PATCH 08/19] ls command implemented with no flags --- implement-shell-tools/ls/ls.js | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index e69de29bb..6d7df445e 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,37 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import path from "node:path"; + +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 paths = program.args; + +const directory = paths.length === 0 ? "." : paths[0]; + +let entries = await fs.readdir(directory); +console.log(entries.join(" ")); + + +// entries.forEach((entry) => { +// console.log(entry); +// }); + +// Read directory entries +// console.log(entries); +// for (const directory of directories) { + +// console.log(entries); +// } + +// console.log(entries); + +// --- Read directory contents --- From 3a92500cf81a2accee59e56e8c64f8561cbd8011 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 09:07:23 +0200 Subject: [PATCH 09/19] ls -1 implemented --- implement-shell-tools/ls/ls.js | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 6d7df445e..bd69f3c49 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,4 +1,5 @@ import { program } from "commander"; +import { on } from "node:events"; import { promises as fs } from "node:fs"; import path from "node:path"; @@ -14,24 +15,14 @@ program program.parse(); const paths = program.args; +const { 1: onePerLine, all } = program.opts(); const directory = paths.length === 0 ? "." : paths[0]; let entries = await fs.readdir(directory); -console.log(entries.join(" ")); +if (onePerLine) { + console.log(entries.filter((entry) => entry[0] !== ".").join("\n")); + // entries.filter((entry) => { entry[0] === "." }) +} -// entries.forEach((entry) => { -// console.log(entry); -// }); - -// Read directory entries -// console.log(entries); -// for (const directory of directories) { - -// console.log(entries); -// } - -// console.log(entries); - -// --- Read directory contents --- From 9ff764258bd2c36e566e78a05cb4b1a84897bd87 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 09:18:22 +0200 Subject: [PATCH 10/19] ls implementation complete --- implement-shell-tools/ls/ls.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index bd69f3c49..b165220ed 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -14,15 +14,21 @@ program .argument("[directory]", "The file path to process"); program.parse(); -const paths = program.args; const { 1: onePerLine, all } = program.opts(); - -const directory = paths.length === 0 ? "." : paths[0]; +const directory = program.args[0]; let entries = await fs.readdir(directory); -if (onePerLine) { - console.log(entries.filter((entry) => entry[0] !== ".").join("\n")); - // entries.filter((entry) => { entry[0] === "." }) +// 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(" ")); +} From 836f423df02e516d35059db9973b9ae6ca057bff Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 09:19:10 +0200 Subject: [PATCH 11/19] Unused imports removed --- implement-shell-tools/ls/ls.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index b165220ed..615ddbf11 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,7 +1,5 @@ import { program } from "commander"; -import { on } from "node:events"; import { promises as fs } from "node:fs"; -import path from "node:path"; program .name("node-ls") From 812b7fbabb3b01835ad315b33a1ed34c8597b32e Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 10:28:57 +0200 Subject: [PATCH 12/19] Basic output structure for wc complete --- implement-shell-tools/wc/wc.js | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index e69de29bb..4d359f699 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,55 @@ +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("", "The file path to process"); + +program.parse(); + +const paths = program.args; +const { number, numberNonBlank } = program.opts(); + +// --- Read files and sizes --- +let content = ""; +let fileSize; + +for (const path of paths) { + content = await fs.readFile(path, "utf-8"); + if (content.endsWith("\n")) { + content = content.slice(0, -1); + } + fileSize = await fs.stat(path); + console.log( + getLineCount(content), + getWordCount(content), + fileSize.size, + path + ); +} + +function getWordCount(text) { + let words, lines; + + lines = text.split("\n"); + // console.log(lines); + words = lines.flatMap((line) => line.split(" ")); + + return words.filter((word) => word.length > 0).length; +} + +function getLineCount(text) { + let lines; + return (lines = text.split("\n").length); +} + +// 1 4 20 sample-files/1.txt +// 1 7 39 sample-files/2.txt +// 5 24 125 sample-files/3.txt +// 7 35 184 total From 2d17c59ff2fd99327d406afdccad89a520955776 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 11:34:33 +0200 Subject: [PATCH 13/19] wc implemented with totals --- implement-shell-tools/wc/wc.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 4d359f699..b40a99303 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -18,7 +18,12 @@ const { number, numberNonBlank } = program.opts(); // --- Read files and sizes --- let content = ""; -let fileSize; +let fileSize, + wordCount, + lineCount, + lineCountTotal = 0, + wordCountTotal = 0, + fileSizeTotal = 0; for (const path of paths) { content = await fs.readFile(path, "utf-8"); @@ -26,14 +31,24 @@ for (const path of paths) { content = content.slice(0, -1); } fileSize = await fs.stat(path); + fileSizeTotal += fileSize.size; + wordCount = getWordCount(content); + wordCountTotal += wordCount; + lineCount = getLineCount(content); + lineCountTotal += lineCount; console.log( - getLineCount(content), - getWordCount(content), - fileSize.size, - path + `${String(lineCount).padStart(3)}${String(wordCount).padStart(4)}${String( + fileSize.size + ).padStart(4)} ${path}` ); } +console.log( + `${String(lineCountTotal).padStart(3)}${String(wordCountTotal).padStart( + 4 + )}${String(fileSizeTotal).padStart(4)} total` +); + function getWordCount(text) { let words, lines; From 0c384d717f0a87b816c8ac3f3299b4b0df71a454 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 13:20:17 +0200 Subject: [PATCH 14/19] Restructuring code to use object for data/output --- implement-shell-tools/wc/wc.js | 75 ++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index b40a99303..a88d0be7d 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -2,52 +2,75 @@ 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" - ) + .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("", "The file path to process"); program.parse(); const paths = program.args; -const { number, numberNonBlank } = program.opts(); +const { lines, words, bytes } = program.opts(); + +const showAll = !lines && !words && !bytes; // --- Read files and sizes --- let content = ""; -let fileSize, - wordCount, - lineCount, - lineCountTotal = 0, +let output = []; + +let lineCountTotal = 0, wordCountTotal = 0, fileSizeTotal = 0; +if (Object.keys(program.opts()).length === 1) { +} + for (const path of paths) { + let fileStats; + let data = {}; + content = await fs.readFile(path, "utf-8"); if (content.endsWith("\n")) { content = content.slice(0, -1); } - fileSize = await fs.stat(path); - fileSizeTotal += fileSize.size; - wordCount = getWordCount(content); - wordCountTotal += wordCount; - lineCount = getLineCount(content); - lineCountTotal += lineCount; + + data.lineCount = getLineCount(content); + lineCountTotal += data.lineCount; + + data.wordCount = getWordCount(content); + wordCountTotal += data.wordCount; + + fileStats = await fs.stat(path); + data.fileSize = fileStats.size; + fileSizeTotal += data.fileSize; + + data.path = path; + output.push(data); +} + +console.log(output); + +if (paths.length > 1) { console.log( - `${String(lineCount).padStart(3)}${String(wordCount).padStart(4)}${String( - fileSize.size - ).padStart(4)} ${path}` + `${String(lineCountTotal).padStart(3)}${String(wordCountTotal).padStart( + 4 + )}${String(fileSizeTotal).padStart(4)} total` ); } -console.log( - `${String(lineCountTotal).padStart(3)}${String(wordCountTotal).padStart( - 4 - )}${String(fileSizeTotal).padStart(4)} total` -); +// output.push(String(fileSize.size).padStart(4)); +// console.log(`${output.join("")} ${path}`); + +function formatOutput({ lineCount, wordCount, fileSize, path }) { + let output = []; + 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; From ba910d0b7333efb1f74515baf481fc40448df4a1 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 14:49:39 +0200 Subject: [PATCH 15/19] code refactored and cleaned up --- implement-shell-tools/wc/wc.js | 51 ++++++++++++---------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index a88d0be7d..51041b667 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -11,48 +11,43 @@ program program.parse(); -const paths = program.args; +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 content = ""; -let output = []; +let fileContent = ""; +let outputData = []; let lineCountTotal = 0, wordCountTotal = 0, fileSizeTotal = 0; -if (Object.keys(program.opts()).length === 1) { -} - -for (const path of paths) { +for (const path of filePaths) { let fileStats; - let data = {}; + let fileData = {}; - content = await fs.readFile(path, "utf-8"); - if (content.endsWith("\n")) { - content = content.slice(0, -1); - } + fileContent = await fs.readFile(path, "utf-8"); - data.lineCount = getLineCount(content); - lineCountTotal += data.lineCount; + fileData.lineCount = getLineCount(fileContent); + lineCountTotal += fileData.lineCount; - data.wordCount = getWordCount(content); - wordCountTotal += data.wordCount; + fileData.wordCount = getWordCount(fileContent); + wordCountTotal += fileData.wordCount; fileStats = await fs.stat(path); - data.fileSize = fileStats.size; - fileSizeTotal += data.fileSize; + fileData.fileSize = fileStats.size; + fileSizeTotal += fileData.fileSize; - data.path = path; - output.push(data); + fileData.path = path; + outputData.push(fileData); } -console.log(output); +console.log(outputData.map(formatOutput).join("\n")); -if (paths.length > 1) { +if (filePaths.length > 1) { console.log( `${String(lineCountTotal).padStart(3)}${String(wordCountTotal).padStart( 4 @@ -60,9 +55,6 @@ if (paths.length > 1) { ); } -// output.push(String(fileSize.size).padStart(4)); -// console.log(`${output.join("")} ${path}`); - function formatOutput({ lineCount, wordCount, fileSize, path }) { let output = []; if (lines || showAll) output.push(String(lineCount).padStart(3)); @@ -76,18 +68,11 @@ function getWordCount(text) { let words, lines; lines = text.split("\n"); - // console.log(lines); words = lines.flatMap((line) => line.split(" ")); return words.filter((word) => word.length > 0).length; } function getLineCount(text) { - let lines; - return (lines = text.split("\n").length); + return text.split("\n").length; } - -// 1 4 20 sample-files/1.txt -// 1 7 39 sample-files/2.txt -// 5 24 125 sample-files/3.txt -// 7 35 184 total From 96f54896d100152e9cb48739f175135a9b3e6b55 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 16:46:40 +0200 Subject: [PATCH 16/19] bug fixed in ls --- implement-shell-tools/ls/ls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 615ddbf11..c5cba5df2 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -13,7 +13,7 @@ program program.parse(); const { 1: onePerLine, all } = program.opts(); -const directory = program.args[0]; +const directory = program.args[0] ? program.args[0] : "."; let entries = await fs.readdir(directory); From ac09bd5caf5ae9f31824006e550ffc3d4fa3f7b0 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 24 Nov 2025 17:19:30 +0200 Subject: [PATCH 17/19] handling of total updated on wc --- implement-shell-tools/wc/wc.js | 40 ++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 51041b667..b14d985b0 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -29,8 +29,16 @@ 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"); + if (fileContent.endsWith("\n")) { + fileContent = fileContent.slice(0, -1); + } + fileData.lineCount = getLineCount(fileContent); lineCountTotal += fileData.lineCount; @@ -49,17 +57,35 @@ console.log(outputData.map(formatOutput).join("\n")); if (filePaths.length > 1) { console.log( - `${String(lineCountTotal).padStart(3)}${String(wordCountTotal).padStart( - 4 - )}${String(fileSizeTotal).padStart(4)} total` + formatOutput({ + lineCount: lineCountTotal, + wordCount: wordCountTotal, + fileSize: fileSizeTotal, + path: "total", + }) ); } -function formatOutput({ lineCount, wordCount, fileSize, path }) { +function formatOutput({ + lineCount, + wordCount, + fileSize, + path, + countOfFlags, + filePaths, +}) { let output = []; - 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)); + + // 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}`; } From e8f3c0cc5ad4fcdb4446582e831b44f3520116f4 Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 22 Dec 2025 13:46:31 +0200 Subject: [PATCH 18/19] Updated gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3c3629e64..5da303acc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +**/.venv +**/requirements.txt From f5c39c9806f49550c5e69fb5724f2488dcf20f9d Mon Sep 17 00:00:00 2001 From: Rashaad Ebrahim Date: Mon, 22 Dec 2025 14:05:07 +0200 Subject: [PATCH 19/19] if statement removed for trimming \n at the end and getLineCount function updated --- implement-shell-tools/wc/wc.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index b14d985b0..cec57ac95 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -35,10 +35,6 @@ for (const path of filePaths) { fileContent = await fs.readFile(path, "utf-8"); - if (fileContent.endsWith("\n")) { - fileContent = fileContent.slice(0, -1); - } - fileData.lineCount = getLineCount(fileContent); lineCountTotal += fileData.lineCount; @@ -100,5 +96,5 @@ function getWordCount(text) { } function getLineCount(text) { - return text.split("\n").length; + return (text.match(/\n/g) || []).length; }