From c874ceb636d70d32f24a60083e27edd0218d44ba Mon Sep 17 00:00:00 2001 From: Priscilla Date: Tue, 29 Jul 2025 05:56:55 +0100 Subject: [PATCH 1/6] cat exercise --- implement-shell-tools/cat/mycat.js | 14 ++++++++++++++ implement-shell-tools/cat/package.json | 13 +++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 implement-shell-tools/cat/mycat.js create mode 100644 implement-shell-tools/cat/package.json diff --git a/implement-shell-tools/cat/mycat.js b/implement-shell-tools/cat/mycat.js new file mode 100644 index 000000000..c5f11e3b5 --- /dev/null +++ b/implement-shell-tools/cat/mycat.js @@ -0,0 +1,14 @@ +import process from "node:process"; +import { promises as fs } from "node:fs"; + +const argv = process.argv.slice(2); +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 content = await fs.readFile(path, "utf-8"); +process.stdout.write(content); diff --git a/implement-shell-tools/cat/package.json b/implement-shell-tools/cat/package.json new file mode 100644 index 000000000..89105a831 --- /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": "mycat.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} From e2a0265d7668d50c1769104f10ee2da7e0ad8689 Mon Sep 17 00:00:00 2001 From: Priscilla Date: Wed, 30 Jul 2025 06:21:17 +0100 Subject: [PATCH 2/6] using the commander library --- implement-shell-tools/cat/mycat.js | 16 ++++++++++---- implement-shell-tools/cat/package-lock.json | 24 +++++++++++++++++++++ implement-shell-tools/cat/package.json | 5 ++++- 3 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 implement-shell-tools/cat/package-lock.json diff --git a/implement-shell-tools/cat/mycat.js b/implement-shell-tools/cat/mycat.js index c5f11e3b5..0e99e80a2 100644 --- a/implement-shell-tools/cat/mycat.js +++ b/implement-shell-tools/cat/mycat.js @@ -1,14 +1,22 @@ -import process from "node:process"; +import { program } from "commander"; import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("mycat") + .description("Outputs the content of the given file(s), like the cat command") + .argument("", "The file path to read"); -const argv = process.argv.slice(2); -if (argv.length != 1) { +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 content = await fs.readFile(path, "utf-8"); process.stdout.write(content); diff --git a/implement-shell-tools/cat/package-lock.json b/implement-shell-tools/cat/package-lock.json new file mode 100644 index 000000000..a46e9346c --- /dev/null +++ b/implement-shell-tools/cat/package-lock.json @@ -0,0 +1,24 @@ +{ + "name": "cat", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cat", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/cat/package.json b/implement-shell-tools/cat/package.json index 89105a831..5dee1152b 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.0" + } } From 7395ee6019700a405fb8f2eadcaf7b6fe72b49a1 Mon Sep 17 00:00:00 2001 From: Priscilla Date: Wed, 30 Jul 2025 07:16:29 +0100 Subject: [PATCH 3/6] cat implementation with flags --- implement-shell-tools/cat/mycat.js | 48 +++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/implement-shell-tools/cat/mycat.js b/implement-shell-tools/cat/mycat.js index 0e99e80a2..83a03e225 100644 --- a/implement-shell-tools/cat/mycat.js +++ b/implement-shell-tools/cat/mycat.js @@ -5,18 +5,44 @@ import process from "node:process"; program .name("mycat") .description("Outputs the content of the given file(s), like the cat command") - .argument("", "The file path to read"); + .option("-n", "Number all lines") + .option("-b", "Number non-blank lines only") + .argument("", "File paths to display"); 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 options = program.opts(); +const filePaths = program.args; + +let lineNumber = 1; + +for (const path of filePaths) { + try { + const content = await fs.readFile(path, "utf-8"); + const lines = content.trimEnd().split("\n"); -const path = argv[0]; -const content = await fs.readFile(path, "utf-8"); -process.stdout.write(content); + for (const line of lines) { + const isBlank = line.trim() === ""; + + if (options.b) { + // -b: number only non-blank lines + if (!isBlank) { + process.stdout.write(`${String(lineNumber).padStart(6)} ${line}\n`); + lineNumber++; + } else { + process.stdout.write("\n"); + } + } else if (options.n) { + // -n: number all lines + process.stdout.write(`${String(lineNumber).padStart(6)} ${line}\n`); + lineNumber++; + } else { + // no flags + process.stdout.write(line + "\n"); + } + } + } catch (error) { + process.stderr.write(`Error reading file ${path}: ${error.message}\n`); + process.exit(1); + } +} From 996d79fd6459879aa801732b42ef52a797ac3130 Mon Sep 17 00:00:00 2001 From: Priscilla Date: Mon, 4 Aug 2025 17:38:39 +0100 Subject: [PATCH 4/6] ls implementation exercise --- implement-shell-tools/ls/myls.js | 36 ++++++++++++++++++++++ implement-shell-tools/ls/package-lock.json | 24 +++++++++++++++ implement-shell-tools/ls/package.json | 16 ++++++++++ 3 files changed, 76 insertions(+) create mode 100644 implement-shell-tools/ls/myls.js create mode 100644 implement-shell-tools/ls/package-lock.json create mode 100644 implement-shell-tools/ls/package.json diff --git a/implement-shell-tools/ls/myls.js b/implement-shell-tools/ls/myls.js new file mode 100644 index 000000000..506269c82 --- /dev/null +++ b/implement-shell-tools/ls/myls.js @@ -0,0 +1,36 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("myls") + .description("list file(s) in the directory, like the ls command") + .option("-1", "list one file per line") + .option("-a", "include hidden files") + .argument("[directory]", "Directory to list", "."); + +program.parse(); + +const options = program.opts(); +const directory = program.args[0] || "."; + +try { + let files = await fs.readdir(directory); + + // if "-a" is used, include hidden files; those that start with "." + if (options.a) { + files = [".", "..", ...files]; + } + + for (const file of files) { + // if "-a" is not used, skip hidden files; those that start with "." + if (!options.a && file.startsWith(".")) { + continue; + } + + console.log(file); // print file name; one file per line + } +} catch (error) { + console.error(`Error reading directory ${directory}:`, error.message); + process.exit(1); +} \ No newline at end of file diff --git a/implement-shell-tools/ls/package-lock.json b/implement-shell-tools/ls/package-lock.json new file mode 100644 index 000000000..c663db972 --- /dev/null +++ b/implement-shell-tools/ls/package-lock.json @@ -0,0 +1,24 @@ +{ + "name": "ls", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ls", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/ls/package.json b/implement-shell-tools/ls/package.json new file mode 100644 index 000000000..1e742d851 --- /dev/null +++ b/implement-shell-tools/ls/package.json @@ -0,0 +1,16 @@ +{ + "name": "ls", + "version": "1.0.0", + "description": "You should already be familiar with the `ls` command line tool.", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } +} From 5cf24bb5e21a7884aba66a0139ad8019b1cfeb69 Mon Sep 17 00:00:00 2001 From: Priscilla Date: Tue, 5 Aug 2025 07:16:05 +0100 Subject: [PATCH 5/6] wc implementation exercise --- implement-shell-tools/wc/mywc.js | 68 ++++++++++++++++++++++ implement-shell-tools/wc/package-lock.json | 24 ++++++++ implement-shell-tools/wc/package.json | 16 +++++ 3 files changed, 108 insertions(+) create mode 100644 implement-shell-tools/wc/mywc.js create mode 100644 implement-shell-tools/wc/package-lock.json create mode 100644 implement-shell-tools/wc/package.json diff --git a/implement-shell-tools/wc/mywc.js b/implement-shell-tools/wc/mywc.js new file mode 100644 index 000000000..f09a8e4b5 --- /dev/null +++ b/implement-shell-tools/wc/mywc.js @@ -0,0 +1,68 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program + .name("mywc") + .description( + "Counts lines, words, and bytes in files like the Unix wc command" + ) + .option("-l", "counts the number of lines") + .option("-w", "counts words") + .option("-c", "counts bytes") + .argument("", "Files to count"); + +program.parse(); + +const options = program.opts(); +const files = program.args; + +// If there are no given flags, default to counting lines, words and bytes like the Unix wc command +if (!options.l && !options.w && !options.c) { + options.l = true; + options.w = true; + options.c = true; +} + +const showLines = options.l; +const showWords = options.w; +const showBytes = options.c; + +// To support multiple files and a total +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; + +for (const file of files) { + try { + const content = await fs.readFile(file, "utf-8"); + + const lineCount = content.split("\n").length - 1; + const wordCount = content.trim().split(/\s+/).filter(Boolean).length; + const byteCount = Buffer.byteLength(content, "utf-8"); + + totalLines += lineCount; + totalWords += wordCount; + totalBytes += byteCount; + + let output = ""; + if (showLines) output += `${lineCount.toString().padStart(8)}`; + if (showWords) output += `${wordCount.toString().padStart(8)}`; + if (showBytes) output += `${byteCount.toString().padStart(8)}`; + output += ` ${file}`; + + console.log(output); + } catch (err) { + console.error(`Error reading file ${file}: ${err.message}`); + } +} + +// If multiple files were given, show the total +if (files.length > 1) { + let totalOutput = ""; + if (showLines) totalOutput += `${totalLines.toString().padStart(8)}`; + if (showWords) totalOutput += `${totalWords.toString().padStart(8)}`; + if (showBytes) totalOutput += `${totalBytes.toString().padStart(8)}`; + totalOutput += " total"; + + console.log(totalOutput); +} \ No newline at end of file diff --git a/implement-shell-tools/wc/package-lock.json b/implement-shell-tools/wc/package-lock.json new file mode 100644 index 000000000..092a4c2b9 --- /dev/null +++ b/implement-shell-tools/wc/package-lock.json @@ -0,0 +1,24 @@ +{ + "name": "wc", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wc", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/wc/package.json b/implement-shell-tools/wc/package.json new file mode 100644 index 000000000..79979331c --- /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": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } +} From 0353c8e3d7485028df5680dec5282f0900866e96 Mon Sep 17 00:00:00 2001 From: Priscilla Date: Sat, 18 Oct 2025 15:59:33 +0100 Subject: [PATCH 6/6] made corrections to the ls and wc implementation --- implement-shell-tools/ls/myls.js | 21 ++++++++++++++------- implement-shell-tools/wc/mywc.js | 27 ++++++++++----------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/implement-shell-tools/ls/myls.js b/implement-shell-tools/ls/myls.js index 506269c82..067dcb2c6 100644 --- a/implement-shell-tools/ls/myls.js +++ b/implement-shell-tools/ls/myls.js @@ -17,18 +17,25 @@ const directory = program.args[0] || "."; try { let files = await fs.readdir(directory); - // if "-a" is used, include hidden files; those that start with "." + // if "-a" is used, include hidden files; those that start with "." if (options.a) { files = [".", "..", ...files]; } - for (const file of files) { - // if "-a" is not used, skip hidden files; those that start with "." - if (!options.a && file.startsWith(".")) { - continue; - } + // If "-a" is not used, filter hidden files out + files = files.filter((file) => options.a || !file.startsWith(".")); + + // Sort alphabetically + files.sort(); - console.log(file); // print file name; one file per line + if (options["1"]) { + // Print one file per line + for (const file of files) { + console.log(file); + } + } else { + // Print all files on a single line, separated by spaces + console.log(files.join(" ")); } } catch (error) { console.error(`Error reading directory ${directory}:`, error.message); diff --git a/implement-shell-tools/wc/mywc.js b/implement-shell-tools/wc/mywc.js index f09a8e4b5..53ad9e745 100644 --- a/implement-shell-tools/wc/mywc.js +++ b/implement-shell-tools/wc/mywc.js @@ -23,9 +23,14 @@ if (!options.l && !options.w && !options.c) { options.c = true; } -const showLines = options.l; -const showWords = options.w; -const showBytes = options.c; +function calculateOutput(lines, words, bytes, label) { + let output = ""; + if (options.l) output += `${lines.toString().padStart(8)}`; + if (options.w) output += `${words.toString().padStart(8)}`; + if (options.c) output += `${bytes.toString().padStart(8)}`; + if (label) output += ` ${label}`; + return output; +} // To support multiple files and a total let totalLines = 0; @@ -44,13 +49,7 @@ for (const file of files) { totalWords += wordCount; totalBytes += byteCount; - let output = ""; - if (showLines) output += `${lineCount.toString().padStart(8)}`; - if (showWords) output += `${wordCount.toString().padStart(8)}`; - if (showBytes) output += `${byteCount.toString().padStart(8)}`; - output += ` ${file}`; - - console.log(output); + console.log(calculateOutput(lineCount, wordCount, byteCount, file)); } catch (err) { console.error(`Error reading file ${file}: ${err.message}`); } @@ -58,11 +57,5 @@ for (const file of files) { // If multiple files were given, show the total if (files.length > 1) { - let totalOutput = ""; - if (showLines) totalOutput += `${totalLines.toString().padStart(8)}`; - if (showWords) totalOutput += `${totalWords.toString().padStart(8)}`; - if (showBytes) totalOutput += `${totalBytes.toString().padStart(8)}`; - totalOutput += " total"; - - console.log(totalOutput); + console.log(calculateOutput(totalLines, totalWords, totalBytes, "total")); } \ No newline at end of file