From e2983eae870d7b094c0399a59cc2d57ea96a01ad Mon Sep 17 00:00:00 2001 From: Ahmad Hmedan Date: Thu, 23 Jul 2026 02:49:18 +0100 Subject: [PATCH 1/5] Implement cat CLI with -n and -b options --- implement-shell-tools/cat/cat.mjs | 51 +++++++++++++++++++++++++ implement-shell-tools/package-lock.json | 20 ++++++++++ implement-shell-tools/package.json | 5 +++ 3 files changed, 76 insertions(+) create mode 100644 implement-shell-tools/cat/cat.mjs create mode 100644 implement-shell-tools/package-lock.json create mode 100644 implement-shell-tools/package.json diff --git a/implement-shell-tools/cat/cat.mjs b/implement-shell-tools/cat/cat.mjs new file mode 100644 index 000000000..2bdabad59 --- /dev/null +++ b/implement-shell-tools/cat/cat.mjs @@ -0,0 +1,51 @@ +import { promises as fs } from "node:fs"; +import { program } from "commander"; + +program + .name("cat") + .description("my own cat program") + .option("-n", "number all lines") + .option("-b", "number non-empty lines") + .argument("", "The file path to process"); +program.parse(); + +const paths = program.args; +const options = program.opts(); + +let lineNumber = 1; + +for (const path of paths) { + try { + const content = await fs.readFile(path, "utf-8"); + if (options.b) { + const lines = content.split("\n"); + + if (lines[lines.length - 1] === "") { + lines.pop(); + } + for (const line of lines) { + if (line.trim() !== "") { + process.stdout.write(` ${lineNumber} ${line}\n`); + lineNumber++; + } else { + process.stdout.write("\n"); + } + } + } else if (options.n) { + const lines = content.split("\n"); + + if (lines[lines.length - 1] === "") { + lines.pop(); + } + + for (const line of lines) { + process.stdout.write(` ${lineNumber} ${line}\n`); + lineNumber++; + } + } else { + process.stdout.write(content); + } + } catch (error) { + console.error(error.message); + } +} diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 000000000..928714cdf --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "implement-shell-tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "commander": "^15.0.0" + } + }, + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "engines": { + "node": ">=22.12.0" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 000000000..6bd6aa2a7 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "commander": "^15.0.0" + } +} From 0f7b2b02eeebb6e150f8c830d8043f8bac2a36c2 Mon Sep 17 00:00:00 2001 From: Ahmad Hmedan Date: Thu, 23 Jul 2026 03:10:24 +0100 Subject: [PATCH 2/5] Implement ls CLI with -a options --- implement-shell-tools/ls/ls.mjs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 implement-shell-tools/ls/ls.mjs diff --git a/implement-shell-tools/ls/ls.mjs b/implement-shell-tools/ls/ls.mjs new file mode 100644 index 000000000..5bbaafdc8 --- /dev/null +++ b/implement-shell-tools/ls/ls.mjs @@ -0,0 +1,24 @@ +import { promises as fs } from "node:fs"; +import { program } from "commander"; + +program + .name("ls ") + .description("ls implementation") + .argument("[path]", "The path to process") //zero or one path + .option("-1, --one-per-line", "one file per line") + .option("-a", "show hidden files"); +program.parse(); + +const path = program.args[0] || "."; +const options = program.opts(); +try { + const files = await fs.readdir(path); + + for (const file of files) { + if (options.a || !file.startsWith(".")) { + console.log(file); + } + } +} catch (error) { + console.error(error.message); +} From 3f651c84b9a66b8154c6d5be037fd9029fa2e693 Mon Sep 17 00:00:00 2001 From: Ahmad Hmedan Date: Thu, 23 Jul 2026 03:39:13 +0100 Subject: [PATCH 3/5] Implement wc CLI with -a options --- implement-shell-tools/wc/wc.mjs | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 implement-shell-tools/wc/wc.mjs diff --git a/implement-shell-tools/wc/wc.mjs b/implement-shell-tools/wc/wc.mjs new file mode 100644 index 000000000..f1973a197 --- /dev/null +++ b/implement-shell-tools/wc/wc.mjs @@ -0,0 +1,64 @@ +import { program } from "commander"; + +import { promises as fs } from "node:fs"; + +program + .name("wc") + .description("wc implementation") + .argument("", "the file path to process") + .option("-l", "count lines") + .option("-w", "count words") + .option("-c", "count characters"); + +program.parse(); + +const paths = program.args; +const options = program.opts(); + +const total = { + linesCounter: 0, + wordsCounter: 0, + characterCounter: 0, +}; + +try { + for (const path of paths) { + const content = await fs.readFile(path, "utf-8"); + + const linesCounter = content.split("\n").length - 1; + const wordsCounter = content.trim().split(/\s+/).length; + const characterCounter = content.length; + + total.linesCounter += linesCounter; + total.wordsCounter += wordsCounter; + total.characterCounter += characterCounter; + + let results = []; + if (options.l) results.push(linesCounter); + if (options.w) results.push(wordsCounter); + if (options.c) results.push(characterCounter); + + if (!options.l && !options.w && !options.c) + console.log( + ` ${linesCounter} ${wordsCounter} ${characterCounter} ${path}`, + ); + else { + console.log(results.join(" ") + " " + path); + } + } + if (paths.length > 1) { + if (!options.l && !options.w && !options.c) { + console.log( + ` ${total.linesCounter} ${total.wordsCounter} ${total.characterCounter} total`, + ); + } else { + const totalWithFlags = []; + if (options.l) totalWithFlags.push(total.linesCounter); + if (options.w) totalWithFlags.push(total.wordsCounter); + if (options.c) totalWithFlags.push(total.characterCounter); + console.log(totalWithFlags.join(" ") + " total"); + } + } +} catch (error) { + console.error(error.message); +} From 481e07456799fce928eb95e643ab1e0623080b92 Mon Sep 17 00:00:00 2001 From: Ahmad Hmedan Date: Sun, 26 Jul 2026 20:14:26 +0100 Subject: [PATCH 4/5] fix: handle per-file errors and empty file word count Continue processing remaining files after an error. Set the process exit code to 1 if any file fails. Count zero words for empty files. --- implement-shell-tools/wc/wc.mjs | 42 +++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/implement-shell-tools/wc/wc.mjs b/implement-shell-tools/wc/wc.mjs index f1973a197..9a2666481 100644 --- a/implement-shell-tools/wc/wc.mjs +++ b/implement-shell-tools/wc/wc.mjs @@ -20,13 +20,15 @@ const total = { wordsCounter: 0, characterCounter: 0, }; - -try { - for (const path of paths) { +let hadError = false; +for (const path of paths) { + try { const content = await fs.readFile(path, "utf-8"); const linesCounter = content.split("\n").length - 1; - const wordsCounter = content.trim().split(/\s+/).length; + const trimmedContent = content.trim(); + const wordsCounter = + trimmedContent === "" ? 0 : trimmedContent.split(/\s+/).length; const characterCounter = content.length; total.linesCounter += linesCounter; @@ -45,20 +47,24 @@ try { else { console.log(results.join(" ") + " " + path); } + } catch (error) { + console.error(error.message); + hadError = true; } - if (paths.length > 1) { - if (!options.l && !options.w && !options.c) { - console.log( - ` ${total.linesCounter} ${total.wordsCounter} ${total.characterCounter} total`, - ); - } else { - const totalWithFlags = []; - if (options.l) totalWithFlags.push(total.linesCounter); - if (options.w) totalWithFlags.push(total.wordsCounter); - if (options.c) totalWithFlags.push(total.characterCounter); - console.log(totalWithFlags.join(" ") + " total"); - } +} +if (paths.length > 1) { + if (!options.l && !options.w && !options.c) { + console.log( + ` ${total.linesCounter} ${total.wordsCounter} ${total.characterCounter} total`, + ); + } else { + const totalWithFlags = []; + if (options.l) totalWithFlags.push(total.linesCounter); + if (options.w) totalWithFlags.push(total.wordsCounter); + if (options.c) totalWithFlags.push(total.characterCounter); + console.log(totalWithFlags.join(" ") + " total"); } -} catch (error) { - console.error(error.message); +} +if (hadError) { + process.exitCode = 1; } From a759353853614bf1593eb0d34967e68cee22b8f9 Mon Sep 17 00:00:00 2001 From: Ahmad Hmedan Date: Sun, 26 Jul 2026 22:21:12 +0100 Subject: [PATCH 5/5] refactor: simplify wc implemention --- implement-shell-tools/wc/wc.mjs | 49 +++++++++++++-------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/implement-shell-tools/wc/wc.mjs b/implement-shell-tools/wc/wc.mjs index 9a2666481..eb8a67f8c 100644 --- a/implement-shell-tools/wc/wc.mjs +++ b/implement-shell-tools/wc/wc.mjs @@ -14,12 +14,9 @@ program.parse(); const paths = program.args; const options = program.opts(); +const noFlag = !options.l && !options.w && !options.c; -const total = { - linesCounter: 0, - wordsCounter: 0, - characterCounter: 0, -}; +const total = {}; let hadError = false; for (const path of paths) { try { @@ -31,39 +28,31 @@ for (const path of paths) { trimmedContent === "" ? 0 : trimmedContent.split(/\s+/).length; const characterCounter = content.length; - total.linesCounter += linesCounter; - total.wordsCounter += wordsCounter; - total.characterCounter += characterCounter; - - let results = []; - if (options.l) results.push(linesCounter); - if (options.w) results.push(wordsCounter); - if (options.c) results.push(characterCounter); + const results = []; + if (options.l || noFlag) { + results.push(linesCounter); + total["lineCounter"] = (total["lineCounter"] ?? 0) + linesCounter; + } + if (options.w || noFlag) { + results.push(wordsCounter); + total["wordsCounter"] = (total["wordsCounter"] ?? 0) + wordsCounter; + } + if (options.c || noFlag) { + results.push(characterCounter); + total["characterCounter"] = + (total["characterCounter"] ?? 0) + characterCounter; + } - if (!options.l && !options.w && !options.c) - console.log( - ` ${linesCounter} ${wordsCounter} ${characterCounter} ${path}`, - ); - else { console.log(results.join(" ") + " " + path); - } + } catch (error) { console.error(error.message); hadError = true; } } if (paths.length > 1) { - if (!options.l && !options.w && !options.c) { - console.log( - ` ${total.linesCounter} ${total.wordsCounter} ${total.characterCounter} total`, - ); - } else { - const totalWithFlags = []; - if (options.l) totalWithFlags.push(total.linesCounter); - if (options.w) totalWithFlags.push(total.wordsCounter); - if (options.c) totalWithFlags.push(total.characterCounter); - console.log(totalWithFlags.join(" ") + " total"); - } + + console.log(Object.values(total).join(" "),"total") } if (hadError) { process.exitCode = 1;