From 6854f0cee0e3ff7c5dd09e7b1ccddacadeb6b909 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 11:17:12 +0100 Subject: [PATCH 01/18] Create package.json by initialising nom --- implement-shell-tools/ls/package.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 implement-shell-tools/ls/package.json diff --git a/implement-shell-tools/ls/package.json b/implement-shell-tools/ls/package.json new file mode 100644 index 000000000..d7b8caeb0 --- /dev/null +++ b/implement-shell-tools/ls/package.json @@ -0,0 +1,11 @@ +{ + "name": "commander", + "version": "1.0.0", + "description": "You should already be familiar with the `ls` command line tool.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} From b708c59416fe776d6d5a800cfe0475db0dd9ad77 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 11:18:13 +0100 Subject: [PATCH 02/18] Install command dependency --- implement-shell-tools/ls/package-lock.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 implement-shell-tools/ls/package-lock.json diff --git a/implement-shell-tools/ls/package-lock.json b/implement-shell-tools/ls/package-lock.json new file mode 100644 index 000000000..ccac85676 --- /dev/null +++ b/implement-shell-tools/ls/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "commander", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "commander", + "version": "1.0.0", + "license": "ISC" + } + } +} From 735d2c3c6763b2e2c54010e23e936ab5b471fb7c Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 11:21:54 +0100 Subject: [PATCH 03/18] Create ls.mjs file --- implement-shell-tools/ls/ls.mjs | 0 1 file changed, 0 insertions(+), 0 deletions(-) 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..e69de29bb From e3da7c7325a189fd887b6afb425794f5b1be2989 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 11:42:41 +0100 Subject: [PATCH 04/18] Amend package.json and install commander dependency --- implement-shell-tools/ls/package-lock.json | 14 +++++++++++++- implement-shell-tools/ls/package.json | 6 +++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/ls/package-lock.json b/implement-shell-tools/ls/package-lock.json index ccac85676..a7d9a0d28 100644 --- a/implement-shell-tools/ls/package-lock.json +++ b/implement-shell-tools/ls/package-lock.json @@ -7,7 +7,19 @@ "": { "name": "commander", "version": "1.0.0", - "license": "ISC" + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } } } } diff --git a/implement-shell-tools/ls/package.json b/implement-shell-tools/ls/package.json index d7b8caeb0..428694b60 100644 --- a/implement-shell-tools/ls/package.json +++ b/implement-shell-tools/ls/package.json @@ -1,11 +1,15 @@ { "name": "commander", "version": "1.0.0", + "type": "module", "description": "You should already be familiar with the `ls` command line tool.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", - "license": "ISC" + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + } } From e83305ada4fe19fa69290d97a5f4000ad63903e5 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 12:12:42 +0100 Subject: [PATCH 05/18] Implement the basic of ls command --- implement-shell-tools/ls/ls.mjs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/implement-shell-tools/ls/ls.mjs b/implement-shell-tools/ls/ls.mjs index e69de29bb..b80c3f038 100644 --- a/implement-shell-tools/ls/ls.mjs +++ b/implement-shell-tools/ls/ls.mjs @@ -0,0 +1,33 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program.name("ls").description("test").option("-1", "list one file per line").argument(""); + +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 = program.opts().char; + +try { + const stat = await fs.stat(path); + + if (stat.isDirectory()) { + const files = await fs.readdir(path); + + for (const file of files) { + console.log(file); + } + } else { + console.log(path); + } +} catch (err) { + console.log(err.message); +} From b0da643ad23709d135bcee5da1df5955242cca74 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 12:29:52 +0100 Subject: [PATCH 06/18] Implement -1 flag --- implement-shell-tools/ls/ls.mjs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/implement-shell-tools/ls/ls.mjs b/implement-shell-tools/ls/ls.mjs index b80c3f038..21b6eb3dd 100644 --- a/implement-shell-tools/ls/ls.mjs +++ b/implement-shell-tools/ls/ls.mjs @@ -14,7 +14,7 @@ if (argv.length != 1) { } const path = argv[0]; -const char = program.opts().char; +const options = program.opts(); try { const stat = await fs.stat(path); @@ -22,8 +22,13 @@ try { if (stat.isDirectory()) { const files = await fs.readdir(path); - for (const file of files) { - console.log(file); + if (options["1"]) { + for (const file of files) { + console.log(file); + } + } else { + const output = files.join("\t"); + console.log(output); } } else { console.log(path); From e89484fc2d08bd4aff18ad07491c76dc80020626 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 12:50:58 +0100 Subject: [PATCH 07/18] Implement -a flag --- implement-shell-tools/ls/ls.mjs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/ls/ls.mjs b/implement-shell-tools/ls/ls.mjs index 21b6eb3dd..b3ee03722 100644 --- a/implement-shell-tools/ls/ls.mjs +++ b/implement-shell-tools/ls/ls.mjs @@ -2,8 +2,11 @@ import { program } from "commander"; import { promises as fs } from "node:fs"; import process from "node:process"; -program.name("ls").description("test").option("-1", "list one file per line").argument(""); - +program.name("ls"); +program.description("test"); +program.option("-1", "list one file per line"); +program.option("-a", "show hidden files"); +program.argument(""); program.parse(); const argv = program.args; @@ -13,14 +16,18 @@ if (argv.length != 1) { process.exit(1); } -const path = argv[0]; const options = program.opts(); +const path = argv[0]; try { const stat = await fs.stat(path); if (stat.isDirectory()) { - const files = await fs.readdir(path); + let files = await fs.readdir(path); + + if (!options.a) { + files = files.filter((file) => !file.startsWith(".")); + } if (options["1"]) { for (const file of files) { From 6af9b9b60510ba351e2aec17dc324e75ca10ebb2 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 14:26:21 +0100 Subject: [PATCH 08/18] Amend the description --- implement-shell-tools/ls/ls.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implement-shell-tools/ls/ls.mjs b/implement-shell-tools/ls/ls.mjs index b3ee03722..4507ca4ba 100644 --- a/implement-shell-tools/ls/ls.mjs +++ b/implement-shell-tools/ls/ls.mjs @@ -3,7 +3,7 @@ import { promises as fs } from "node:fs"; import process from "node:process"; program.name("ls"); -program.description("test"); +program.description("simple ls clone"); program.option("-1", "list one file per line"); program.option("-a", "show hidden files"); program.argument(""); From a102908facdcbdee7c259450e33e7a7df94b15a8 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 12:52:47 +0100 Subject: [PATCH 09/18] Initialise npm --- implement-shell-tools/wc/package.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 implement-shell-tools/wc/package.json diff --git a/implement-shell-tools/wc/package.json b/implement-shell-tools/wc/package.json new file mode 100644 index 000000000..79b5b8ca9 --- /dev/null +++ b/implement-shell-tools/wc/package.json @@ -0,0 +1,12 @@ +{ + "name": "wc", + "version": "1.0.0", + "description": "You should already be familiar with the `wc` command line tool.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} From 3f2880c170cd0011d205c1f99fda7d278a0b10bf Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 12:53:44 +0100 Subject: [PATCH 10/18] Add "type": "module" --- implement-shell-tools/wc/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/implement-shell-tools/wc/package.json b/implement-shell-tools/wc/package.json index 79b5b8ca9..e3170affd 100644 --- a/implement-shell-tools/wc/package.json +++ b/implement-shell-tools/wc/package.json @@ -1,6 +1,7 @@ { "name": "wc", "version": "1.0.0", + "type": "module", "description": "You should already be familiar with the `wc` command line tool.", "main": "index.js", "scripts": { From e5aad33e06bc4ab2a56fac86e13eefb4b1fcb175 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 12:54:25 +0100 Subject: [PATCH 11/18] Install commander dependency --- implement-shell-tools/wc/package-lock.json | 25 ++++++++++++++++++++++ implement-shell-tools/wc/package.json | 5 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 implement-shell-tools/wc/package-lock.json diff --git a/implement-shell-tools/wc/package-lock.json b/implement-shell-tools/wc/package-lock.json new file mode 100644 index 000000000..8aa122ddd --- /dev/null +++ b/implement-shell-tools/wc/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "wc", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wc", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/wc/package.json b/implement-shell-tools/wc/package.json index e3170affd..791a3741a 100644 --- a/implement-shell-tools/wc/package.json +++ b/implement-shell-tools/wc/package.json @@ -9,5 +9,8 @@ }, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + } } From 42cad0b9f742e130d1bf5c1e554c1e5be143b156 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 11 Apr 2026 12:54:53 +0100 Subject: [PATCH 12/18] Create wc.mjs --- implement-shell-tools/wc/wc.mjs | 0 1 file changed, 0 insertions(+), 0 deletions(-) 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..e69de29bb From c326ecb01b34a0ef7713cd2de8db4f455b505723 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 18 Apr 2026 00:49:50 +0100 Subject: [PATCH 13/18] Implement wc command --- implement-shell-tools/wc/wc.mjs | 75 +++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/implement-shell-tools/wc/wc.mjs b/implement-shell-tools/wc/wc.mjs index e69de29bb..d656bf4dc 100644 --- a/implement-shell-tools/wc/wc.mjs +++ b/implement-shell-tools/wc/wc.mjs @@ -0,0 +1,75 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program.name("wc"); +program.description("simple wc clone"); +program.option("-w, --words", "print word count"); +program.option("-l, --lines", "print line count"); +program.option("-c, --bytes", "print byte count"); +program.argument("[paths...]", "file(s) to process"); +program.parse(); + +const options = program.opts(); +const paths = program.args; + +const noFlags = !options.words && !options.lines && !options.bytes; +const showWords = options.words || noFlags; +const showLines = options.lines || noFlags; +const showBytes = options.bytes || noFlags; + +function count(content) { + const lines = content.split("\n").length - 1; + const words = content.trim().split(/\s+/).filter(Boolean).length; + const bytes = Buffer.byteLength(content); + + return { lines, words, bytes }; +} + +function formatCounts({ lines, words, bytes }, label = "") { + const output = []; + + if (showLines) { + output.push(lines.toString().padStart(8)); + } + + if (showWords) { + output.push(words.toString().padStart(8)); + } + + if (showBytes) { + output.push(bytes.toString().padStart(8)); + } + + if (label) { + output.push(label); + } + + return output.join(" "); +} + +async function processFile(path) { + try { + const stat = await fs.stat(path); + + if (stat.isDirectory()) { + console.error(`${path}: Is a directory`); + return; + } + + const content = await fs.readFile(path, { encoding: "utf-8" }); + const counts = count(content); + const output = formatCounts(counts, path); + + console.log(output); + } catch (err) { + console.error(`${path}: ${err.message}`); + } +} + +try { + for (const path of paths) { + await processFile(path); + } +} catch (err) { + console.error(err.message); +} From 03b1248cb6f023a6ba6498706849661f5569fe57 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 18 Apr 2026 01:26:57 +0100 Subject: [PATCH 14/18] Initialise npm --- implement-shell-tools/cat/package.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 implement-shell-tools/cat/package.json diff --git a/implement-shell-tools/cat/package.json b/implement-shell-tools/cat/package.json new file mode 100644 index 000000000..4a8aace74 --- /dev/null +++ b/implement-shell-tools/cat/package.json @@ -0,0 +1,12 @@ +{ + "name": "cat", + "version": "1.0.0", + "description": "You should already be familiar with the `cat` command line tool.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} From 61b1d916354e2de18fb36f4cab5b48698813d528 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 18 Apr 2026 01:28:21 +0100 Subject: [PATCH 15/18] Add "type": "module" --- implement-shell-tools/cat/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/implement-shell-tools/cat/package.json b/implement-shell-tools/cat/package.json index 4a8aace74..7c7367723 100644 --- a/implement-shell-tools/cat/package.json +++ b/implement-shell-tools/cat/package.json @@ -1,6 +1,7 @@ { "name": "cat", "version": "1.0.0", + "type": "module", "description": "You should already be familiar with the `cat` command line tool.", "main": "index.js", "scripts": { From 668b6a34bb255c915f4c9dd899b8ed3c702db265 Mon Sep 17 00:00:00 2001 From: Ammad Date: Sat, 18 Apr 2026 01:29:45 +0100 Subject: [PATCH 16/18] Install commander dependency --- implement-shell-tools/cat/package-lock.json | 25 +++++++++++++++++++++ implement-shell-tools/cat/package.json | 5 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 implement-shell-tools/cat/package-lock.json diff --git a/implement-shell-tools/cat/package-lock.json b/implement-shell-tools/cat/package-lock.json new file mode 100644 index 000000000..737542646 --- /dev/null +++ b/implement-shell-tools/cat/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "cat", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cat", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/cat/package.json b/implement-shell-tools/cat/package.json index 7c7367723..f4e5c2a03 100644 --- a/implement-shell-tools/cat/package.json +++ b/implement-shell-tools/cat/package.json @@ -9,5 +9,8 @@ }, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + } } From ddbae1a927e446c3ec36d335803f0465f132d51e Mon Sep 17 00:00:00 2001 From: Ammad Date: Fri, 24 Apr 2026 17:04:56 +0100 Subject: [PATCH 17/18] Create cat.mjs file and define the initial implementation --- implement-shell-tools/cat/cat.mjs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 implement-shell-tools/cat/cat.mjs diff --git a/implement-shell-tools/cat/cat.mjs b/implement-shell-tools/cat/cat.mjs new file mode 100644 index 000000000..6b6a7ae23 --- /dev/null +++ b/implement-shell-tools/cat/cat.mjs @@ -0,0 +1,12 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program.name("cat"); +program.description("simple cat clone"); +program.option("-n, --number", "number all output lines"); +program.option("-b, --number-nonblank", "number non-empty output lines"); +program.argument("[paths...]", "file(s) to process"); +program.parse(); + +const { number, numberNonblank } = program.opts(); +const paths = program.args; From 6caee3c7f16bbac6b76e81c2cb4b0cd722f81a89 Mon Sep 17 00:00:00 2001 From: Ammad Date: Fri, 24 Apr 2026 17:50:10 +0100 Subject: [PATCH 18/18] Complete the cat implementation --- implement-shell-tools/cat/cat.mjs | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/implement-shell-tools/cat/cat.mjs b/implement-shell-tools/cat/cat.mjs index 6b6a7ae23..0b7e01a14 100644 --- a/implement-shell-tools/cat/cat.mjs +++ b/implement-shell-tools/cat/cat.mjs @@ -10,3 +10,53 @@ program.parse(); const { number, numberNonblank } = program.opts(); const paths = program.args; + +async function processFile(path) { + try { + const stat = await fs.stat(path); + + if (stat.isDirectory()) { + console.error(`${path}: Is a directory`); + return; + } + + const content = await fs.readFile(path, { encoding: "utf-8" }); + const lines = content.split("\n"); + const hasTrailingNewLine = content.endsWith("\n"); + const effectiveLines = hasTrailingNewLine ? lines.slice(0, -1) : lines; + let lineNumber = 1; + + const processed = effectiveLines.map((line) => { + if (numberNonblank) { + if (line.trim() !== "") { + const result = `${lineNumber}\t${line}`; + lineNumber += 1; + return result; + } + + return line; + } + + if (number) { + const result = `${lineNumber}\t${line}`; + lineNumber += 1; + return result; + } + + return line; + }); + + const output = processed.join("\n"); + console.log(output); + } catch (error) { + console.error(`${path}: ${error.message}`); + } +} + +try { + for (const path of paths) { + await processFile(path); + } +} catch (error) { + console.error(error.message); +}