From c874ceb636d70d32f24a60083e27edd0218d44ba Mon Sep 17 00:00:00 2001 From: Priscilla Date: Tue, 29 Jul 2025 05:56:55 +0100 Subject: [PATCH 1/5] 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 00000000..c5f11e3b --- /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 00000000..89105a83 --- /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/5] 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 c5f11e3b..0e99e80a 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 00000000..a46e9346 --- /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 89105a83..5dee1152 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/5] 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 0e99e80a..83a03e22 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/5] 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 00000000..506269c8 --- /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 00000000..c663db97 --- /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 00000000..1e742d85 --- /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/5] 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 00000000..f09a8e4b --- /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 00000000..092a4c2b --- /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 00000000..79979331 --- /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" + } +}