From 7e7ebfd09695d857447a3d8feb1595a0e296858c Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Sat, 28 Mar 2026 02:53:13 +0000 Subject: [PATCH 1/7] cat done --- implement-shell-tools/.gitignore | 1 + implement-shell-tools/cat/customCat.js | 42 ++++++++++++++++++++++++++ implement-shell-tools/package.json | 3 ++ 3 files changed, 46 insertions(+) create mode 100644 implement-shell-tools/.gitignore create mode 100755 implement-shell-tools/cat/customCat.js create mode 100644 implement-shell-tools/package.json diff --git a/implement-shell-tools/.gitignore b/implement-shell-tools/.gitignore new file mode 100644 index 000000000..40b878db5 --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js new file mode 100755 index 000000000..b58d5a214 --- /dev/null +++ b/implement-shell-tools/cat/customCat.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("customCat") + .description("CLI command to concatenate and print files") + .option("-n, --number", "Number all output lines starting, at 1") + .option("-b, --nonBlank", "Number only non-blank lines, starting at 1") + .argument("", "Files to read"); + +program.parse(); + +const argv = program.args; +const options = program.opts(); + +let lineNumber = 1; + +for (const filePath of argv) { + try { + const content = await fs.readFile(filePath, "utf-8"); + const lines = content.split("\n"); + + if (lines[lines.length - 1] === "") lines.pop(); + + lines.forEach((line, index) => { + if (options.nonBlank) { + if (line.trim() !== "") { + process.stdout.write(`${(index + 1).toString().padStart(6)} ${line}\n`); + lineNumber++; + } else process.stdout.write(`${line}\n`); + } else if (options.number) { + process.stdout.write(`${(index + 1).toString().padStart(6)} ${line}\n`); + } else { + process.stdout.write(`${line}\n`); + } + }); + } catch (error) { + console.error(`customCat: ${filePath}: No such file or directory.`); + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 000000000..3dbc1ca59 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} From 30170c8f1292c6683af6a3aa88d08e2f815f1bd0 Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Mon, 30 Mar 2026 21:06:44 +0100 Subject: [PATCH 2/7] feat: implement custom ls with -1 and -a support --- implement-shell-tools/cat/customCat.js | 7 ++-- implement-shell-tools/ls/customLs.js | 53 +++++++++++++++++++++++++ implement-shell-tools/package-lock.json | 20 ++++++++++ implement-shell-tools/package.json | 5 ++- 4 files changed, 80 insertions(+), 5 deletions(-) create mode 100755 implement-shell-tools/ls/customLs.js create mode 100644 implement-shell-tools/package-lock.json diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js index b58d5a214..3f0698cb5 100755 --- a/implement-shell-tools/cat/customCat.js +++ b/implement-shell-tools/cat/customCat.js @@ -15,8 +15,6 @@ program.parse(); const argv = program.args; const options = program.opts(); -let lineNumber = 1; - for (const filePath of argv) { try { const content = await fs.readFile(filePath, "utf-8"); @@ -27,8 +25,9 @@ for (const filePath of argv) { lines.forEach((line, index) => { if (options.nonBlank) { if (line.trim() !== "") { - process.stdout.write(`${(index + 1).toString().padStart(6)} ${line}\n`); - lineNumber++; + process.stdout.write( + `${(index + 1).toString().padStart(6)} ${line}\n`, + ); } else process.stdout.write(`${line}\n`); } else if (options.number) { process.stdout.write(`${(index + 1).toString().padStart(6)} ${line}\n`); diff --git a/implement-shell-tools/ls/customLs.js b/implement-shell-tools/ls/customLs.js new file mode 100755 index 000000000..c17e77b12 --- /dev/null +++ b/implement-shell-tools/ls/customLs.js @@ -0,0 +1,53 @@ +#!/usr/bin/env node + +import process from "node:process"; +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program + .name("customLs") + .description("List contents of a directory") + .option("-1", "Force output to be one entry per line") + .option("-a", "Include hidden files") + .argument("[path...]", "directories to list"); + +program.parse(); + +const options = program.opts(); +const targetPaths = program.args.length > 0 ? program.args : ["."]; + +async function listDir(dirPath, showHeader) { + try { + let contents = await fs.readdir(dirPath); + + if (options.a) { + contents.push(".", ".."); + } else { + contents = contents.filter((name) => !name.startsWith(".")); + } + + contents.sort(); + + if (showHeader) { + process.stdout.write(`${dirPath}:\n`); + } + + if (options["1"]) { + contents.forEach((item) => process.stdout.write(`${item}\n`)); + } else { + process.stdout.write(`${contents.join(" ")}\n`); + } + } catch (error) { + console.error(`customLs: ${dirPath}: ${error.message}`); + process.exit(1); + } +} + +for (let i = 0; i < targetPaths.length; i++) { + const path = targetPaths[i]; + const isMultiplePath = targetPaths.length > 1; + + await listDir(path, isMultiplePath); + + if (isMultiplePath && i < targetPaths.length - 1) process.stdout.write(`\n`); +} diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 000000000..cdf736677 --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "implement-shell-tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "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==", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json index 3dbc1ca59..043047a15 100644 --- a/implement-shell-tools/package.json +++ b/implement-shell-tools/package.json @@ -1,3 +1,6 @@ { - "type": "module" + "type": "module", + "dependencies": { + "commander": "^14.0.3" + } } From 5a74b431a09f5a0c5eb19fbd17a2e88528baf831 Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Wed, 1 Apr 2026 16:35:28 +0100 Subject: [PATCH 3/7] feat: implement custom wc --- .gitignore | 1 + implement-shell-tools/cat/customCat.js | 4 +- implement-shell-tools/ls/customLs.js | 4 +- implement-shell-tools/wc/customWc.js | 69 ++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) create mode 100755 implement-shell-tools/wc/customWc.js diff --git a/.gitignore b/.gitignore index 3c3629e64..0099691d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +demo* diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js index 3f0698cb5..98770c107 100755 --- a/implement-shell-tools/cat/customCat.js +++ b/implement-shell-tools/cat/customCat.js @@ -4,7 +4,7 @@ import { promises as fs } from "node:fs"; import process from "node:process"; program - .name("customCat") + .name("ccat") .description("CLI command to concatenate and print files") .option("-n, --number", "Number all output lines starting, at 1") .option("-b, --nonBlank", "Number only non-blank lines, starting at 1") @@ -36,6 +36,6 @@ for (const filePath of argv) { } }); } catch (error) { - console.error(`customCat: ${filePath}: No such file or directory.`); + console.error(`ccat: ${filePath}: No such file or directory.`); } } diff --git a/implement-shell-tools/ls/customLs.js b/implement-shell-tools/ls/customLs.js index c17e77b12..928566ef4 100755 --- a/implement-shell-tools/ls/customLs.js +++ b/implement-shell-tools/ls/customLs.js @@ -5,7 +5,7 @@ import { program } from "commander"; import { promises as fs } from "node:fs"; program - .name("customLs") + .name("cls") .description("List contents of a directory") .option("-1", "Force output to be one entry per line") .option("-a", "Include hidden files") @@ -38,7 +38,7 @@ async function listDir(dirPath, showHeader) { process.stdout.write(`${contents.join(" ")}\n`); } } catch (error) { - console.error(`customLs: ${dirPath}: ${error.message}`); + console.error(`cls: ${dirPath}: ${error.message}`); process.exit(1); } } diff --git a/implement-shell-tools/wc/customWc.js b/implement-shell-tools/wc/customWc.js new file mode 100755 index 000000000..0281db95e --- /dev/null +++ b/implement-shell-tools/wc/customWc.js @@ -0,0 +1,69 @@ +#!/usr/bin/env node + +import { program } from "commander"; +import process from "node:process"; +import { promises as fs } from "node:fs"; + +program + .name("cwc") + .description("Displays number of lines, words, and bytes in a file") + .option("-l, --lines", "Counts number of newline characters") + .option( + "-w, --words", + "Counts sequence of characters separated by whitespace", + ) + .option("-c, --bytes", "Counts raw size of the files in bytes") + .argument("", "File(s) to read and count"); + +program.parse(); + +const options = program.opts(); +const files = program.args; + +const noFlags = !options.lines && !options.words && !options.bytes; + +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; + +async function countFiles(file) { + try { + const buffer = await fs.readFile(file); + const content = buffer.toString("utf-8"); + + const lineCount = content === "" ? 0 : content.split("\n").length - 1; + const wordCount = content.trim() ? content.trim().split(/\s+/).length : 0; + const byteCount = buffer.length; + + totalLines += lineCount; + totalWords += wordCount; + totalBytes += byteCount; + + let result = ""; + + if (options.lines || noFlags) result += `${String(lineCount).padStart(8)}`; + if (options.words || noFlags) result += `${String(wordCount).padStart(8)}`; + if (options.bytes || noFlags) result += `${String(byteCount).padStart(8)}`; + + process.stdout.write(`${result} ${file}\n`); + } catch (error) { + console.error(`cwc: ${file}: ${error.message}`); + process.exit(1); + } +} + +(async () => { + for (const file of files) { + await countFiles(file); + } + + if (files.length > 1) { + let total = ""; + + if (options.lines || noFlags) total += `${String(totalLines).padStart(8)}`; + if (options.words || noFlags) total += `${String(totalWords).padStart(8)}`; + if (options.bytes || noFlags) total += `${String(totalBytes).padStart(8)}`; + + process.stdout.write(`${total} total\n`); + } +})(); From bd10e7c8bbaed8061c205b15999094926a532b28 Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Mon, 6 Jul 2026 19:16:18 +0100 Subject: [PATCH 4/7] fix: refactor index to use count instead --- implement-shell-tools/cat/customCat.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js index 98770c107..fda55d141 100755 --- a/implement-shell-tools/cat/customCat.js +++ b/implement-shell-tools/cat/customCat.js @@ -1,4 +1,7 @@ #!/usr/bin/env node + + + import { program } from "commander"; import { promises as fs } from "node:fs"; import process from "node:process"; @@ -22,15 +25,18 @@ for (const filePath of argv) { if (lines[lines.length - 1] === "") lines.pop(); - lines.forEach((line, index) => { + let count = 0; + + lines.forEach((line) => { if (options.nonBlank) { - if (line.trim() !== "") { + if (line !== "") { + count++; process.stdout.write( - `${(index + 1).toString().padStart(6)} ${line}\n`, + `${count.toString().padStart(6)} ${line}\n`, ); } else process.stdout.write(`${line}\n`); } else if (options.number) { - process.stdout.write(`${(index + 1).toString().padStart(6)} ${line}\n`); + process.stdout.write(`${(count++ + 1).toString().padStart(6)} ${line}\t`); } else { process.stdout.write(`${line}\n`); } From 1e95febd4a20c9456610877029ff0e8f42a6d6d8 Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Mon, 6 Jul 2026 23:18:47 +0100 Subject: [PATCH 5/7] fix: implement ls to not exit when an error is encountered --- implement-shell-tools/cat/customCat.js | 10 ++++------ implement-shell-tools/ls/customLs.js | 6 +++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js index fda55d141..6779cbc6e 100755 --- a/implement-shell-tools/cat/customCat.js +++ b/implement-shell-tools/cat/customCat.js @@ -1,7 +1,5 @@ #!/usr/bin/env node - - import { program } from "commander"; import { promises as fs } from "node:fs"; import process from "node:process"; @@ -31,12 +29,12 @@ for (const filePath of argv) { if (options.nonBlank) { if (line !== "") { count++; - process.stdout.write( - `${count.toString().padStart(6)} ${line}\n`, - ); + process.stdout.write(`${count.toString().padStart(6)} ${line}\n`); } else process.stdout.write(`${line}\n`); } else if (options.number) { - process.stdout.write(`${(count++ + 1).toString().padStart(6)} ${line}\t`); + process.stdout.write( + `${(count++ + 1).toString().padStart(6)} ${line}\t`, + ); } else { process.stdout.write(`${line}\n`); } diff --git a/implement-shell-tools/ls/customLs.js b/implement-shell-tools/ls/customLs.js index 928566ef4..2cd3eccdd 100755 --- a/implement-shell-tools/ls/customLs.js +++ b/implement-shell-tools/ls/customLs.js @@ -16,6 +16,8 @@ program.parse(); const options = program.opts(); const targetPaths = program.args.length > 0 ? program.args : ["."]; +let hadError = false; + async function listDir(dirPath, showHeader) { try { let contents = await fs.readdir(dirPath); @@ -39,7 +41,7 @@ async function listDir(dirPath, showHeader) { } } catch (error) { console.error(`cls: ${dirPath}: ${error.message}`); - process.exit(1); + hadError = true; } } @@ -51,3 +53,5 @@ for (let i = 0; i < targetPaths.length; i++) { if (isMultiplePath && i < targetPaths.length - 1) process.stdout.write(`\n`); } + +if (hadError) process.exit(1); From 4cb8e57f28e1ceef6aeea1cd264c56d58213255e Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Mon, 6 Jul 2026 23:24:21 +0100 Subject: [PATCH 6/7] fix: Error handling --- implement-shell-tools/wc/customWc.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/wc/customWc.js b/implement-shell-tools/wc/customWc.js index 0281db95e..847bd4032 100755 --- a/implement-shell-tools/wc/customWc.js +++ b/implement-shell-tools/wc/customWc.js @@ -26,6 +26,8 @@ let totalLines = 0; let totalWords = 0; let totalBytes = 0; +let hadError = false; + async function countFiles(file) { try { const buffer = await fs.readFile(file); @@ -48,7 +50,7 @@ async function countFiles(file) { process.stdout.write(`${result} ${file}\n`); } catch (error) { console.error(`cwc: ${file}: ${error.message}`); - process.exit(1); + hadError = true; } } @@ -66,4 +68,6 @@ async function countFiles(file) { process.stdout.write(`${total} total\n`); } + + if (hadError) process.exit(1); })(); From cc3a482a3033249e81c5e601eb1e1b4544f4885a Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Mon, 6 Jul 2026 23:32:12 +0100 Subject: [PATCH 7/7] fix: using tabs rather than space --- implement-shell-tools/cat/customCat.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js index 6779cbc6e..92d5197e1 100755 --- a/implement-shell-tools/cat/customCat.js +++ b/implement-shell-tools/cat/customCat.js @@ -29,11 +29,11 @@ for (const filePath of argv) { if (options.nonBlank) { if (line !== "") { count++; - process.stdout.write(`${count.toString().padStart(6)} ${line}\n`); + process.stdout.write(`${count.toString().padStart(6)}\t${line}\n`); } else process.stdout.write(`${line}\n`); } else if (options.number) { process.stdout.write( - `${(count++ + 1).toString().padStart(6)} ${line}\t`, + `${(count++ + 1).toString().padStart(6)}\t${line}\n`, ); } else { process.stdout.write(`${line}\n`);