From 04e0f692fd379c5029029b63a2c7d8bc79846139 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Tue, 21 Jul 2026 21:01:38 +0100 Subject: [PATCH 01/22] cat initial implementation --- implement-shell-tools/cat/cat.js | 16 ++++++++++++++++ implement-shell-tools/cat/package.json | 13 +++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 implement-shell-tools/cat/cat.js create mode 100644 implement-shell-tools/cat/package.json diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 000000000..032e8a475 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,16 @@ +import { promises as fs } from "node:fs"; +import process from "node:process"; + +const path = process.argv.slice(2)[0]; +const numLines = process.argv.slice(3)[0]; + +// console.log(numLines); +// console.log(process.argv.slice(3)[0]); +const myFile = await fs.readFile(path, "utf-8"); +let lines = myFile.split("\n"); + +if (!isNaN(numLines)) { + lines = lines.slice(0, numLines); +} + +lines.forEach((e) => console.log(e)); diff --git a/implement-shell-tools/cat/package.json b/implement-shell-tools/cat/package.json new file mode 100644 index 000000000..c927a8982 --- /dev/null +++ b/implement-shell-tools/cat/package.json @@ -0,0 +1,13 @@ +{ + "name": "cat", + "version": "1.0.0", + "description": "You should already be familiar with the `cat` command line tool.", + "main": "cat.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module" +} From 8cadca9736a02d7a86a742de9b40e041a128bd4f Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Tue, 21 Jul 2026 21:05:42 +0100 Subject: [PATCH 02/22] a file to test cat functionality --- implement-shell-tools/cat/testFile.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 implement-shell-tools/cat/testFile.txt diff --git a/implement-shell-tools/cat/testFile.txt b/implement-shell-tools/cat/testFile.txt new file mode 100644 index 000000000..864534491 --- /dev/null +++ b/implement-shell-tools/cat/testFile.txt @@ -0,0 +1,4 @@ +This is line 1 +This is line 2 + +This is line 3 after an empty line \ No newline at end of file From 8294ca8db2a8b678e12cc3278b0463aca6612fd8 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 13:22:25 +0100 Subject: [PATCH 03/22] redo: parsing arguments --- implement-shell-tools/cat/cat.js | 49 ++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 032e8a475..87fe24cca 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,16 +1,47 @@ import { promises as fs } from "node:fs"; import process from "node:process"; -const path = process.argv.slice(2)[0]; -const numLines = process.argv.slice(3)[0]; +/* +BASIC requirements: +single file with no flags: + cat sample-files/1.txt +single file with a valid flag: + cat -n sample-files/1.txt + cat -b sample-files/1.txt +read mutliple files with the * wildcard + cat -n sample/files/*.txt -// console.log(numLines); -// console.log(process.argv.slice(3)[0]); -const myFile = await fs.readFile(path, "utf-8"); -let lines = myFile.split("\n"); +Cat strips trailing new line in file -if (!isNaN(numLines)) { - lines = lines.slice(0, numLines); +-n = number each line +-b = number each non-empty line + +STRETCH GOALS +-b takes priority when -nb or -bn +must take in mutiple files: + node cat file1.txt file2.txt file3.txt + node cat -n file1.txt file2.txt file3.txt + +must be able to take in multiple flags or combined flags + node cat -bn file.txt + node cat -b -n file.txt +*/ + +// capturing the user args +const args = process.argv.slice(2); + +let flag; +const files = []; + +// takes only one flag, and accepts whatever the last flag is +// can parse flag from any position in args +for (const arg of args) { + if (arg === "-n" || arg === "-b") { + flag = arg; + } else { + files.push(arg); + } } -lines.forEach((e) => console.log(e)); +console.log(`flags: ${flag}`); +console.log(`files: ${files}`); From 71441246e657e0227552d8c52a8ec40f218cb4d5 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 13:31:57 +0100 Subject: [PATCH 04/22] Error message if no file supplied. --- implement-shell-tools/cat/cat.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 87fe24cca..4bcff00eb 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -35,6 +35,8 @@ const files = []; // takes only one flag, and accepts whatever the last flag is // can parse flag from any position in args + +// the * (glob expansion is done automatically by the zsh, bash etc. on linux) for (const arg of args) { if (arg === "-n" || arg === "-b") { flag = arg; @@ -43,5 +45,11 @@ for (const arg of args) { } } -console.log(`flags: ${flag}`); -console.log(`files: ${files}`); +// starting file number, if lines need to be prepended +let lineNum = 1; + +// if no file is supplied +if (files.length === 0) { + console.error("usage: cat [-n] "); + process.exit(1); // exit with error +} From aeca1f7e5507d3c6ae1466e42b5d7cd59866663c Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 13:33:16 +0100 Subject: [PATCH 05/22] rename file to path --- implement-shell-tools/cat/cat.js | 4 ++-- implement-shell-tools/cat/testFile.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 4bcff00eb..a2859c783 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -31,7 +31,7 @@ must be able to take in multiple flags or combined flags const args = process.argv.slice(2); let flag; -const files = []; +const paths = []; // takes only one flag, and accepts whatever the last flag is // can parse flag from any position in args @@ -41,7 +41,7 @@ for (const arg of args) { if (arg === "-n" || arg === "-b") { flag = arg; } else { - files.push(arg); + paths.push(arg); } } diff --git a/implement-shell-tools/cat/testFile.txt b/implement-shell-tools/cat/testFile.txt index 864534491..18991b761 100644 --- a/implement-shell-tools/cat/testFile.txt +++ b/implement-shell-tools/cat/testFile.txt @@ -1,4 +1,4 @@ This is line 1 This is line 2 -This is line 3 after an empty line \ No newline at end of file +This is line 3 after an empty line From 1d9d1df854f670c464f20a8cdbe2d8b86111466b Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 14:16:01 +0100 Subject: [PATCH 06/22] finished cat --- implement-shell-tools/cat/cat.js | 78 +++++++++++++++----------- implement-shell-tools/cat/testFile.txt | 4 -- 2 files changed, 44 insertions(+), 38 deletions(-) delete mode 100644 implement-shell-tools/cat/testFile.txt diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index a2859c783..6a1e74345 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,31 +1,8 @@ -import { promises as fs } from "node:fs"; +import { readFileSync } from "node:fs"; import process from "node:process"; -/* -BASIC requirements: -single file with no flags: - cat sample-files/1.txt -single file with a valid flag: - cat -n sample-files/1.txt - cat -b sample-files/1.txt -read mutliple files with the * wildcard - cat -n sample/files/*.txt - -Cat strips trailing new line in file - --n = number each line --b = number each non-empty line - -STRETCH GOALS --b takes priority when -nb or -bn -must take in mutiple files: - node cat file1.txt file2.txt file3.txt - node cat -n file1.txt file2.txt file3.txt - -must be able to take in multiple flags or combined flags - node cat -bn file.txt - node cat -b -n file.txt -*/ +// Note: takes only one flag, -n or -b, and accepts whatever the last flag is +// can parse flag from any position in args // capturing the user args const args = process.argv.slice(2); @@ -33,10 +10,7 @@ const args = process.argv.slice(2); let flag; const paths = []; -// takes only one flag, and accepts whatever the last flag is -// can parse flag from any position in args - -// the * (glob expansion is done automatically by the zsh, bash etc. on linux) +// the * (glob expansion is done automatically by zsh, bash etc. on linux) for (const arg of args) { if (arg === "-n" || arg === "-b") { flag = arg; @@ -45,11 +19,47 @@ for (const arg of args) { } } +// if no file is supplied exit with error +if (paths.length === 0) { + console.error("usage: cat [-n] "); + process.exit(1); +} + // starting file number, if lines need to be prepended let lineNum = 1; -// if no file is supplied -if (files.length === 0) { - console.error("usage: cat [-n] "); - process.exit(1); // exit with error +for (const path of paths) { + let file; + try { + // using sync as it's a simple short program + file = readFileSync(path, "utf-8"); + } catch (err) { + console.error(`cat: ${path}: ${err.message}`); + continue; // Real cat continues to next file if current file not found + } + + const lines = file.split("\n"); + + // remove trailing empty line as this how real cat works + if (lines[lines.length - 1] === "") lines.pop(); + + if (flag === "-n") { + for (const line of lines) { + console.log(`${lineNum} ${line}`); + lineNum++; + } + } else if (flag === "-b") { + for (const line of lines) { + if (line === "") { + console.log(line); + } else { + console.log(`${lineNum} ${line}`); + lineNum++; + } + } + } else { + for (const line of lines) { + console.log(line); + } + } } diff --git a/implement-shell-tools/cat/testFile.txt b/implement-shell-tools/cat/testFile.txt deleted file mode 100644 index 18991b761..000000000 --- a/implement-shell-tools/cat/testFile.txt +++ /dev/null @@ -1,4 +0,0 @@ -This is line 1 -This is line 2 - -This is line 3 after an empty line From d57789975842b0e1078c52243fb085faeef61bbe Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 14:30:05 +0100 Subject: [PATCH 07/22] capture flag arguments --- implement-shell-tools/ls/ls.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 implement-shell-tools/ls/ls.js diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 000000000..03e35a242 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,26 @@ +import fs from "node:fs"; +import process from "node:process"; + +const args = process.argv.slice(2); + +const flags = new Set(); +const path = ""; + +for (const arg of args) { + if (arg.startsWith("-") && arg !== "-") { + // capture the flags without the - + for (const ch of arg.slice(1)) { + flags.add(ch); + } + } +} + +console.log(flags); + +// console.log( +// fs.readdir("./", (err, files) => { +// if (err) throw err; + +// console.log(files); +// }), +// ); From 4d0e873beba76178fdb2f0610835e6afb4bd2337 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 17:09:43 +0100 Subject: [PATCH 08/22] working subdirectories --- implement-shell-tools/ls/ls.js | 43 +++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 03e35a242..c29d76e0c 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,26 +1,53 @@ +import { dir } from "node:console"; import fs from "node:fs"; +import path from "node:path"; import process from "node:process"; const args = process.argv.slice(2); const flags = new Set(); -const path = ""; +let paths = []; + +if (args.length === 0) { + args.push("."); +} for (const arg of args) { if (arg.startsWith("-") && arg !== "-") { // capture the flags without the - + // supports combined flags like -1a for (const ch of arg.slice(1)) { flags.add(ch); } + } else { + paths.push(arg); } } -console.log(flags); +const files = []; +const directories = []; -// console.log( -// fs.readdir("./", (err, files) => { -// if (err) throw err; +if (paths.length > 1) { + paths.forEach((path) => { + if (fs.statSync(path).isDirectory()) { + directories.push(path); + } else { + files.push(path); + } + }); +} else { + fs.readdir(paths[0], (err, files) => console.log(files.join("\t"))); +} -// console.log(files); -// }), -// ); +if (files.length > 0) { + console.log(files.join("\t")); +} + +if (directories.length > 0) { + directories.forEach((dir) => { + console.log(`\n${dir}:`); + fs.readdir(dir, (err, files) => { + console.log(files.join("\t")); + }); + }); +} From 75e9d0a9b224ad9f42905ddc2dada0d3bd608886 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 18:25:19 +0100 Subject: [PATCH 09/22] initial working version --- implement-shell-tools/ls/ls.js | 59 ++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index c29d76e0c..6a7e2da8e 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,6 +1,5 @@ import { dir } from "node:console"; import fs from "node:fs"; -import path from "node:path"; import process from "node:process"; const args = process.argv.slice(2); @@ -8,10 +7,6 @@ const args = process.argv.slice(2); const flags = new Set(); let paths = []; -if (args.length === 0) { - args.push("."); -} - for (const arg of args) { if (arg.startsWith("-") && arg !== "-") { // capture the flags without the - @@ -24,30 +19,44 @@ for (const arg of args) { } } -const files = []; -const directories = []; +if (paths.length === 0) { + paths.push("."); +} -if (paths.length > 1) { - paths.forEach((path) => { - if (fs.statSync(path).isDirectory()) { - directories.push(path); +function printFiles(path) { + if (fs.statSync(path).isDirectory()) { + if (flags.has("a")) { + if (flags.has("1")) { + fs.readdirSync(path).forEach((e) => console.log(e)); + } else { + console.log(fs.readdirSync(path).join("\t")); + } } else { - files.push(path); + if (flags.has("1")) { + fs.readdirSync(path) + .filter((e) => !e.startsWith(".")) + .forEach((e) => console.log(e)); + } else { + console.log( + fs + .readdirSync(path) + .filter((e) => !e.startsWith(".")) + .join("\t"), + ); + } } - }); -} else { - fs.readdir(paths[0], (err, files) => console.log(files.join("\t"))); -} - -if (files.length > 0) { - console.log(files.join("\t")); + } else { + console.log(path); + } } -if (directories.length > 0) { - directories.forEach((dir) => { - console.log(`\n${dir}:`); - fs.readdir(dir, (err, files) => { - console.log(files.join("\t")); - }); +if (paths.length === 1) { + printFiles(paths[0]); +} else { + paths.forEach((path) => { + if (fs.statSync(path).isDirectory()) { + console.log(`\n${path}:`); + } + printFiles(path); }); } From b77ab503b24511be2d5f28cbb1dd9b6b558ecd98 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 18:45:55 +0100 Subject: [PATCH 10/22] redo: function to get entries for a given path --- implement-shell-tools/ls/ls.js | 44 ++++++++-------------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 6a7e2da8e..526334bd8 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,5 +1,6 @@ import { dir } from "node:console"; import fs from "node:fs"; +import path from "node:path"; import process from "node:process"; const args = process.argv.slice(2); @@ -23,40 +24,15 @@ if (paths.length === 0) { paths.push("."); } -function printFiles(path) { - if (fs.statSync(path).isDirectory()) { - if (flags.has("a")) { - if (flags.has("1")) { - fs.readdirSync(path).forEach((e) => console.log(e)); - } else { - console.log(fs.readdirSync(path).join("\t")); - } - } else { - if (flags.has("1")) { - fs.readdirSync(path) - .filter((e) => !e.startsWith(".")) - .forEach((e) => console.log(e)); - } else { - console.log( - fs - .readdirSync(path) - .filter((e) => !e.startsWith(".")) - .join("\t"), - ); - } - } - } else { - console.log(path); +// returns all entries for a given path +// if -a flag, then include dotfiles, else exclude dotfiles +function getPathEntries(path, aFlag = flags.has("a")) { + let entries = fs.readdirSync(path); + + if (!aFlag) { + entries = entries.filter((e) => !e.startsWith(".")); } + return entries; } -if (paths.length === 1) { - printFiles(paths[0]); -} else { - paths.forEach((path) => { - if (fs.statSync(path).isDirectory()) { - console.log(`\n${path}:`); - } - printFiles(path); - }); -} +console.log(getPathEntries(paths[0])); From abce30aacdcc540cf3c6200223256c9fc5cc485b Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 18:54:14 +0100 Subject: [PATCH 11/22] printing entries for a single path --- implement-shell-tools/ls/ls.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 526334bd8..9441b9844 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -35,4 +35,14 @@ function getPathEntries(path, aFlag = flags.has("a")) { return entries; } -console.log(getPathEntries(paths[0])); +// formatter: if -1 flag, print entry per line +// else all in one line with +function printEntries(entries, onePerLineFlag = flags.has("1")) { + if (onePerLineFlag) { + entries.forEach((e) => console.log(e)); + } else { + console.log(entries.join("\t")); + } +} + +printEntries(getPathEntries(paths[0])); From 98c1c435f4ded760ba1951ccb733d655b58323a9 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Thu, 23 Jul 2026 19:33:39 +0100 Subject: [PATCH 12/22] ls done --- implement-shell-tools/ls/ls.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 9441b9844..7fc3286c1 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,6 +1,4 @@ -import { dir } from "node:console"; import fs from "node:fs"; -import path from "node:path"; import process from "node:process"; const args = process.argv.slice(2); @@ -41,8 +39,28 @@ function printEntries(entries, onePerLineFlag = flags.has("1")) { if (onePerLineFlag) { entries.forEach((e) => console.log(e)); } else { - console.log(entries.join("\t")); + // if join on empty entries arr, add extra blank line + if (entries.length !== 0) { + console.log(entries.join("\t")); + } } } -printEntries(getPathEntries(paths[0])); +// this is needed to group files at the top and folers at the bottom when giving mutiple path arguements +// to argv e.g. node ls.js sample-files/* +const fileArgs = paths.filter((p) => !fs.statSync(p).isDirectory()); +const dirArgs = paths.filter((p) => fs.statSync(p).isDirectory()); + +// First print all plain file arguments together, as one group +if (fileArgs.length > 0) { + printEntries(fileArgs); +} + +// Then print each directory's listing, with headers if needed +dirArgs.forEach((path, index) => { + if (paths.length > 1) { + if (index > 0 || fileArgs.length > 0) console.log(""); + console.log(`${path}:`); + } + printEntries(getPathEntries(path)); +}); From 5e36e1fccc18b08a5495f9ede132c9ed0b0cfa4b Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 25 Jul 2026 15:06:40 +0100 Subject: [PATCH 13/22] fix: cat line numbering is per file, not on the concatanation of files --- implement-shell-tools/cat/cat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 6a1e74345..ff592b5f0 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -26,9 +26,9 @@ if (paths.length === 0) { } // starting file number, if lines need to be prepended -let lineNum = 1; for (const path of paths) { + let lineNum = 1; let file; try { // using sync as it's a simple short program From 1a27139ec3825d17d0423ad30be9defb5b97308e Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 25 Jul 2026 17:14:25 +0100 Subject: [PATCH 14/22] mode switching arg parser --- implement-shell-tools/ls/ls.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 7fc3286c1..2113bcc99 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -6,18 +6,23 @@ const args = process.argv.slice(2); const flags = new Set(); let paths = []; -for (const arg of args) { - if (arg.startsWith("-") && arg !== "-") { +let isFlag = true; +for (let i = 0; i < args.length; i++) { + if (isFlag && args[i] === "--") { + isFlag = false; + } else if (isFlag && args[i].startsWith("-") && args[i] !== "-") { // capture the flags without the - // supports combined flags like -1a - for (const ch of arg.slice(1)) { + for (const ch of args[i].slice(1)) { flags.add(ch); } } else { - paths.push(arg); + paths.push(args[i]); } } +console.log(paths); + if (paths.length === 0) { paths.push("."); } From 667e3bbf6c753104f27502ce2aa346f78b9f9932 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 27 Jul 2026 08:21:57 +0100 Subject: [PATCH 15/22] clean up --- implement-shell-tools/ls/ls.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 2113bcc99..ddbc14e13 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -7,22 +7,20 @@ const flags = new Set(); let paths = []; let isFlag = true; -for (let i = 0; i < args.length; i++) { - if (isFlag && args[i] === "--") { +for (const arg of args) { + if (isFlag && arg === "--") { isFlag = false; - } else if (isFlag && args[i].startsWith("-") && args[i] !== "-") { + } else if (isFlag && arg.startsWith("-") && arg !== "-") { // capture the flags without the - // supports combined flags like -1a - for (const ch of args[i].slice(1)) { + for (const ch of arg.slice(1)) { flags.add(ch); } } else { - paths.push(args[i]); + paths.push(arg); } } -console.log(paths); - if (paths.length === 0) { paths.push("."); } From c7449be1d9ce405333e1a8318af339ba1f983c2a Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 27 Jul 2026 09:21:45 +0100 Subject: [PATCH 16/22] Parse optoins using commander --- implement-shell-tools/wc/package-lock.json | 25 ++++++++++++++++++++++ implement-shell-tools/wc/package.json | 16 ++++++++++++++ implement-shell-tools/wc/wc.js | 14 ++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 implement-shell-tools/wc/package-lock.json create mode 100644 implement-shell-tools/wc/package.json create mode 100644 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/wc/package-lock.json b/implement-shell-tools/wc/package-lock.json new file mode 100644 index 000000000..11fb6ab6f --- /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": "^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==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + } + } +} diff --git a/implement-shell-tools/wc/package.json b/implement-shell-tools/wc/package.json new file mode 100644 index 000000000..489705b8d --- /dev/null +++ b/implement-shell-tools/wc/package.json @@ -0,0 +1,16 @@ +{ + "name": "wc", + "version": "1.0.0", + "description": "You should already be familiar with the `wc` command line tool.", + "main": "wc.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "module", + "dependencies": { + "commander": "^15.0.0" + } +} diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 000000000..9919c1d26 --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,14 @@ +import { program } from "commander"; + +program + .name("wc") + .description("Mimics the wc command line tool") + .option("-w") + .option("-c") + .option("-l"); + +program.parse(); + +const options = program.opts(); + +console.log(options); From 45766e070bf45ecc94d75f7cb7d820e95b13efe3 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 27 Jul 2026 09:35:32 +0100 Subject: [PATCH 17/22] parse file arguments --- implement-shell-tools/wc/wc.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 9919c1d26..e71d39c80 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -1,14 +1,18 @@ import { program } from "commander"; +import fs from "node:fs"; +import process from "node:process"; program .name("wc") .description("Mimics the wc command line tool") .option("-w") .option("-c") - .option("-l"); + .option("-l") + .argument("", "file to process"); program.parse(); const options = program.opts(); +const files = program.args; -console.log(options); +console.log(files); From 2c3458fcbae16c0d9fa4c33733c94aee0d27e3e8 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 27 Jul 2026 09:58:23 +0100 Subject: [PATCH 18/22] handle no flag arguments --- implement-shell-tools/wc/wc.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index e71d39c80..fa545de5e 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -15,4 +15,19 @@ program.parse(); const options = program.opts(); const files = program.args; +console.log(options); console.log(files); + +// if no -lwc flags are supplied, wc prints +// lines, words, bytes of each file +// whereas if any flags are supplied only those +// values are printed +if (Object.keys(options).length === 0) { + options.l = options.w = options.c = true; +} + +/* +array of objects with data [{l: 2, w: 12: c: 123, file: 'sample-files/1.txt}, ...] +For each item, create a temp string +If options.l, append to temp string the w value +*/ From 3c8e345452bd7a2374c830c724bef40267d5a7a4 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 27 Jul 2026 10:26:29 +0100 Subject: [PATCH 19/22] shows error message if trying to read a dictory --- implement-shell-tools/wc/wc.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index fa545de5e..16c97ef88 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -1,5 +1,5 @@ import { program } from "commander"; -import fs from "node:fs"; +import fs, { chownSync } from "node:fs"; import process from "node:process"; program @@ -13,10 +13,10 @@ program program.parse(); const options = program.opts(); -const files = program.args; +const paths = program.args; console.log(options); -console.log(files); +console.log(paths); // if no -lwc flags are supplied, wc prints // lines, words, bytes of each file @@ -27,7 +27,24 @@ if (Object.keys(options).length === 0) { } /* -array of objects with data [{l: 2, w: 12: c: 123, file: 'sample-files/1.txt}, ...] -For each item, create a temp string -If options.l, append to temp string the w value +for each path: + if path is not a directory, show error message + else: + create a temporary array + if l in options: + push line count to temp arr + if w in options: + push word count into temp arr + if c in options: + push byte count into data + + join the array with path and print */ + +for (const path of paths) { + if (fs.statSync(path).isDirectory()) { + console.log(`wc: ${path}: read: Is a directory`); + } else { + console.log(`${path} is a file`); + } +} From 1a80d5b5458f75f15a63b1651e3a05a3adbb4725 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 27 Jul 2026 11:24:00 +0100 Subject: [PATCH 20/22] prints lwc for one or more files --- implement-shell-tools/wc/wc.js | 53 ++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 16c97ef88..83c33d995 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -8,16 +8,13 @@ program .option("-w") .option("-c") .option("-l") - .argument("", "file to process"); + .argument("", "files to process"); program.parse(); const options = program.opts(); const paths = program.args; -console.log(options); -console.log(paths); - // if no -lwc flags are supplied, wc prints // lines, words, bytes of each file // whereas if any flags are supplied only those @@ -26,25 +23,43 @@ if (Object.keys(options).length === 0) { options.l = options.w = options.c = true; } -/* -for each path: - if path is not a directory, show error message - else: - create a temporary array - if l in options: - push line count to temp arr - if w in options: - push word count into temp arr - if c in options: - push byte count into data - - join the array with path and print -*/ +// keeps track of total values for each of the data +// incrementally updated in the loop below +const totals = { l: 0, w: 0, c: 0 }; for (const path of paths) { if (fs.statSync(path).isDirectory()) { console.log(`wc: ${path}: read: Is a directory`); } else { - console.log(`${path} is a file`); + let outputStr = ""; + const file = fs.readFileSync(path, "utf-8"); + if (options.l) { + const lines = file.split("\n"); + // exclude trailing empty line from count + if (lines.at(-1) === "") { + lines.pop(); + } + const lineCount = lines.length; + totals.l += lineCount; + outputStr += `\t${lineCount}`; + } + + if (options.w) { + // real wc splits not just on " ", but on white spaces more generally + const words = file.split(/\s+/).filter(Boolean); + const wordCount = words.length; + totals.w += wordCount; + outputStr += `\t${wordCount}`; + } + + if (options.c) { + file.size; + const byteCount = fs.statSync(path).size; + totals.c += byteCount; + outputStr += `\t${byteCount}`; + } + + outputStr += ` ${path}`; + console.log(outputStr); } } From bdfbbbabcc4dee82e5efcd9afea5994b98c57506 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 27 Jul 2026 11:29:34 +0100 Subject: [PATCH 21/22] print totals in bottom row if more than one file --- implement-shell-tools/wc/wc.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 83c33d995..14bb763ee 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -27,10 +27,12 @@ if (Object.keys(options).length === 0) { // incrementally updated in the loop below const totals = { l: 0, w: 0, c: 0 }; +let fileCount = 0; for (const path of paths) { if (fs.statSync(path).isDirectory()) { console.log(`wc: ${path}: read: Is a directory`); } else { + fileCount++; let outputStr = ""; const file = fs.readFileSync(path, "utf-8"); if (options.l) { @@ -63,3 +65,7 @@ for (const path of paths) { console.log(outputStr); } } + +if (fileCount > 1) { + console.log(`\t${totals.l}\t${totals.w}\t${totals.c} total`); +} From 0b3d37dcd5d8f9124a05824b33df38ce1d6e9550 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Mon, 27 Jul 2026 11:34:49 +0100 Subject: [PATCH 22/22] correctly print out totals, includning flags --- implement-shell-tools/wc/wc.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 14bb763ee..3cb4c8cbc 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -66,6 +66,10 @@ for (const path of paths) { } } +// if there's more than one file, print out a total +// if a flag is not selected, then the value for that flag is 0 +// filter out anything with a total of 0 if (fileCount > 1) { - console.log(`\t${totals.l}\t${totals.w}\t${totals.c} total`); + const totalsArr = Object.values(totals).filter((elem) => elem !== 0); + console.log(`\t${totalsArr.join("\t")} total`); }