From 7cb4e6e54dc4653e92ec1b379b383f5b2e9e1c83 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sat, 21 Mar 2026 17:51:33 +0000 Subject: [PATCH 01/11] completed case 1 and 2 in cat exercises --- implement-shell-tools/cat/customCat.js | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 implement-shell-tools/cat/customCat.js diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js new file mode 100644 index 000000000..c2e2ceb77 --- /dev/null +++ b/implement-shell-tools/cat/customCat.js @@ -0,0 +1,29 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("custom-cat") + .description("my-own-version-of-cat") + .option("-n, --line", "Adding a number before each roll") + .argument("", "The file path to process"); + +program.parse(); + +const options = program.opts(); + +const argv = program.args; +if (argv.length != 1) { + console.log(`We need exactly 1 path to process but we got ${argv.length}`); + process.exit(1); +} + +const path = argv[0]; + +const context = await fs.readFile(path, "utf-8"); +const contextArray = context.trimEnd().split("\n"); +contextArray.forEach((string, index) => { + options.line + ? console.log(` ${index + 1} ${string}`) + : console.log(`${string}`); +}); From 37aa677deaab16d953217350430950763467dac3 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sat, 21 Mar 2026 18:25:45 +0000 Subject: [PATCH 02/11] added package.json --- implement-shell-tools/package-lock.json | 21 +++++++++++++++++++++ implement-shell-tools/package.json | 6 ++++++ 2 files changed, 27 insertions(+) create mode 100644 implement-shell-tools/package-lock.json create mode 100644 implement-shell-tools/package.json diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 000000000..8cd4b3de8 --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,21 @@ +{ + "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==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 000000000..402b2a4e9 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "commander": "^14.0.3" + }, + "type": "module" +} From 89daa4f5ad0cd93a574534d15ec11935372b05c6 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sat, 21 Mar 2026 19:05:29 +0000 Subject: [PATCH 03/11] working on case 5 in cat --- implement-shell-tools/cat/customCat.js | 41 +++++++++++++++++++------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js index c2e2ceb77..79be256aa 100644 --- a/implement-shell-tools/cat/customCat.js +++ b/implement-shell-tools/cat/customCat.js @@ -6,24 +6,43 @@ program .name("custom-cat") .description("my-own-version-of-cat") .option("-n, --line", "Adding a number before each roll") - .argument("", "The file path to process"); + .option( + "-b, --nonBlank", + "Only adding a number before roll that is non blank", + ) + .argument("", "The file path to process"); program.parse(); const options = program.opts(); const argv = program.args; -if (argv.length != 1) { - console.log(`We need exactly 1 path to process but we got ${argv.length}`); +if (argv.length < 1) { + console.log( + `We need at least 1 path of file to process but we got ${argv.length}`, + ); process.exit(1); } -const path = argv[0]; +const paths = argv; -const context = await fs.readFile(path, "utf-8"); -const contextArray = context.trimEnd().split("\n"); -contextArray.forEach((string, index) => { - options.line - ? console.log(` ${index + 1} ${string}`) - : console.log(`${string}`); -}); +let count = 1; + +for (let path of paths) { + const context = await fs.readFile(path, "utf-8"); + if (options.line && options.nonBlank) { + context + .trimEnd() + .split("\n") + .forEach((string) => { + if (string != null || string != "") { + console.log(` ${count} ${string}`); + count++; + } else { + console.log(string); + } + }); + } else { + console.log(context.trimEnd()); + } +} From b33e992f34ed531e4fba5214d7dff9e6ca7ddc75 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sun, 22 Mar 2026 09:33:19 +0000 Subject: [PATCH 04/11] completed case 5 in cat exercises --- implement-shell-tools/cat/customCat.js | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js index 79be256aa..e77246f21 100644 --- a/implement-shell-tools/cat/customCat.js +++ b/implement-shell-tools/cat/customCat.js @@ -30,18 +30,21 @@ let count = 1; for (let path of paths) { const context = await fs.readFile(path, "utf-8"); - if (options.line && options.nonBlank) { - context - .trimEnd() - .split("\n") - .forEach((string) => { - if (string != null || string != "") { - console.log(` ${count} ${string}`); - count++; - } else { - console.log(string); - } - }); + const lines = context.trimEnd().split("\n"); + if (options.nonBlank) { + lines.forEach((line) => { + if (line.length != 0) { + console.log(` ${count} ${line}`); + count++; + } else { + console.log(line); + } + }); + } else if (options.line) { + lines.forEach((line) => { + console.log(` ${count} ${line}`); + count++; + }); } else { console.log(context.trimEnd()); } From c06e9118966d0d0c0079ba7db23364fdad2600d2 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sun, 22 Mar 2026 17:46:14 +0000 Subject: [PATCH 05/11] completed ls exercises --- implement-shell-tools/ls/customLs.js | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 implement-shell-tools/ls/customLs.js diff --git a/implement-shell-tools/ls/customLs.js b/implement-shell-tools/ls/customLs.js new file mode 100644 index 000000000..cb17e4596 --- /dev/null +++ b/implement-shell-tools/ls/customLs.js @@ -0,0 +1,50 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("Custom-ls") + .description("Custom-ls-that-works-like-ls") + .option("-1, --oneFile", "Showing one file per line") + .option("-a, --showHidden", "Showing hidden files") + .argument("[path]", "The file path to process"); + +program.parse(); + +const argumentsArray = program.args; + +const path = argumentsArray[0] || "./"; + +try { + const files = await fs.readdir(path); + + const sortedFiles = files.sort((a, b) => a.localeCompare(b)); + + const options = program.opts(); + + let renderingFiles = []; + + if (options.showHidden) { + renderingFiles = sortedFiles; + } else { + renderingFiles = sortedFiles.filter((file) => !/^\./.test(file)); + } + + if (options.oneFile) { + for (let file of renderingFiles) { + console.log(file); + } + } else { + console.log(renderingFiles.join(" ")); + } +} catch (error) { + const state = await fs.stat(path).catch(() => null); + if (state && state.isFile()) { + console.log(path); + } else { + console.log( + `Can't access to this path: ${path} - No such file or directory`, + ); + process.exit(1); + } +} From 76bfe0776200a0548deed396d9f187a932741120 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sun, 22 Mar 2026 17:47:06 +0000 Subject: [PATCH 06/11] updated variable names --- implement-shell-tools/cat/customCat.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js index e77246f21..2ac037b44 100644 --- a/implement-shell-tools/cat/customCat.js +++ b/implement-shell-tools/cat/customCat.js @@ -16,19 +16,19 @@ program.parse(); const options = program.opts(); -const argv = program.args; -if (argv.length < 1) { +const argumentArray = program.args; +if (argumentArray.length < 1) { console.log( - `We need at least 1 path of file to process but we got ${argv.length}`, + `We need at least 1 path of file to process but we got ${argumentArray.length}`, ); process.exit(1); } -const paths = argv; +const pathsArray = argumentArray; let count = 1; -for (let path of paths) { +for (let path of pathsArray) { const context = await fs.readFile(path, "utf-8"); const lines = context.trimEnd().split("\n"); if (options.nonBlank) { From bf678a034015a2b8152e1b618d2631a760eb8f48 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sun, 22 Mar 2026 18:35:18 +0000 Subject: [PATCH 07/11] working on wc exercises --- implement-shell-tools/wc/customWc.js | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 implement-shell-tools/wc/customWc.js diff --git a/implement-shell-tools/wc/customWc.js b/implement-shell-tools/wc/customWc.js new file mode 100644 index 000000000..871ad98e6 --- /dev/null +++ b/implement-shell-tools/wc/customWc.js @@ -0,0 +1,29 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("Custom-wc") + .description("Custom-wc-that-works-like-wc") + .argument("", "Path of file to process"); + +program.parse(); + +const argumentArray = program.args; +if (argumentArray.length === 0) { + console.log(`We need at least one file path to process`); + process.exit(1); +} + +const pathArray = argumentArray; + +let numberOfLines = 0; + +let totalOfLines = 0; + +for (let path of pathArray) { + const file = await fs.readFile(path, "utf-8"); + const lines = file.trimEnd().split("\n"); + numberOfLines = lines.length; + console.log(lines); +} From fda1073cdfe4bc312e5f7b01642ee46c864a901e Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Mon, 23 Mar 2026 13:53:38 +0000 Subject: [PATCH 08/11] finished logic for multiple files, working on flags --- implement-shell-tools/wc/customWc.js | 42 ++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/implement-shell-tools/wc/customWc.js b/implement-shell-tools/wc/customWc.js index 871ad98e6..631fa9755 100644 --- a/implement-shell-tools/wc/customWc.js +++ b/implement-shell-tools/wc/customWc.js @@ -5,7 +5,7 @@ import process from "node:process"; program .name("Custom-wc") .description("Custom-wc-that-works-like-wc") - .argument("", "Path of file to process"); + .argument("", "Path of file to process"); program.parse(); @@ -16,14 +16,46 @@ if (argumentArray.length === 0) { } const pathArray = argumentArray; +const path = pathArray[0]; -let numberOfLines = 0; +function padStartNumbers(...args) { + if (args.length != 3) { + throw new Error( + "It takes 3 arguments in order as numberOfLines, numberOfWords, numberOfCharacters", + ); + } + const space = [3, 4, 4]; + const numberStringArray = []; + for (let index = 0; index < args.length; index++) { + numberStringArray.push(String(args[index]).padStart(space[index], " ")); + } + return numberStringArray.join(""); +} let totalOfLines = 0; +let totalOfWords = 0; +let totalOfCharacters = 0; for (let path of pathArray) { + let numberOfLines = 0; + let numberOfWords = 0; + let numberOfCharacters = 0; + const file = await fs.readFile(path, "utf-8"); - const lines = file.trimEnd().split("\n"); - numberOfLines = lines.length; - console.log(lines); + numberOfCharacters = file.length; + numberOfLines = file.split("\n").length - 1; + const words = file.match(/\S+/g); + numberOfWords = words ? words.length : 0; + console.log( + `${padStartNumbers(numberOfLines, numberOfWords, numberOfCharacters)} ${path}`, + ); + totalOfLines += numberOfLines; + totalOfWords += numberOfWords; + totalOfCharacters += numberOfCharacters; +} + +if (pathArray.length > 1) { + console.log( + `${padStartNumbers(totalOfLines, totalOfWords, totalOfCharacters)} total`, + ); } From 6fe53af2d901aac9e64c250e0326a59be4a7d072 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Mon, 23 Mar 2026 16:47:31 +0000 Subject: [PATCH 09/11] finished the logic but it could be simplified --- implement-shell-tools/wc/customWc.js | 49 +++++++++++++++++++++------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/implement-shell-tools/wc/customWc.js b/implement-shell-tools/wc/customWc.js index 631fa9755..edf9f812d 100644 --- a/implement-shell-tools/wc/customWc.js +++ b/implement-shell-tools/wc/customWc.js @@ -5,6 +5,9 @@ import process from "node:process"; program .name("Custom-wc") .description("Custom-wc-that-works-like-wc") + .option("-l, --lines", "Counting lines in the file") + .option("-w, --words", "Counting words in the file") + .option("-c, --characters", "Counting characters in the file") .argument("", "Path of file to process"); program.parse(); @@ -16,14 +19,9 @@ if (argumentArray.length === 0) { } const pathArray = argumentArray; -const path = pathArray[0]; +const options = program.opts(); function padStartNumbers(...args) { - if (args.length != 3) { - throw new Error( - "It takes 3 arguments in order as numberOfLines, numberOfWords, numberOfCharacters", - ); - } const space = [3, 4, 4]; const numberStringArray = []; for (let index = 0; index < args.length; index++) { @@ -46,16 +44,43 @@ for (let path of pathArray) { numberOfLines = file.split("\n").length - 1; const words = file.match(/\S+/g); numberOfWords = words ? words.length : 0; - console.log( - `${padStartNumbers(numberOfLines, numberOfWords, numberOfCharacters)} ${path}`, - ); + + if (pathArray.length > 1) { + if (options.lines) { + console.log(`${padStartNumbers(numberOfLines)} ${path}`); + } + if (options.words) { + console.log(`${padStartNumbers(numberOfWords)} ${path}`); + } + if (options.characters) { + console.log(`${padStartNumbers(numberOfCharacters)} ${path}`); + } + } else if (options.lines) { + console.log(`${numberOfLines} ${path}`); + } else if (options.words) { + console.log(`${numberOfWords} ${path}`); + } else if (options.characters) { + console.log(`${numberOfCharacters} ${path}`); + } else { + console.log( + `${padStartNumbers(numberOfLines, numberOfWords, numberOfCharacters)} ${path}`, + ); + } totalOfLines += numberOfLines; totalOfWords += numberOfWords; totalOfCharacters += numberOfCharacters; } if (pathArray.length > 1) { - console.log( - `${padStartNumbers(totalOfLines, totalOfWords, totalOfCharacters)} total`, - ); + if (options.lines) { + console.log(`${padStartNumbers(totalOfLines)} total`); + } else if (options.words) { + console.log(`${padStartNumbers(totalOfWords)} total`); + } else if (options.characters) { + console.log(`${padStartNumbers(totalOfCharacters)} total`); + } else { + console.log( + `${padStartNumbers(totalOfLines, totalOfWords, totalOfCharacters)} total`, + ); + } } From ae796c400e42e3b733f4e15526b3bf5014e22427 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Tue, 24 Mar 2026 12:56:02 +0000 Subject: [PATCH 10/11] completed wc exercises --- implement-shell-tools/wc/customWc.js | 45 +++++++++++++--------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/implement-shell-tools/wc/customWc.js b/implement-shell-tools/wc/customWc.js index edf9f812d..037eb64d9 100644 --- a/implement-shell-tools/wc/customWc.js +++ b/implement-shell-tools/wc/customWc.js @@ -30,6 +30,8 @@ function padStartNumbers(...args) { return numberStringArray.join(""); } +const totalRowNumbers = []; + let totalOfLines = 0; let totalOfWords = 0; let totalOfCharacters = 0; @@ -40,31 +42,27 @@ for (let path of pathArray) { let numberOfCharacters = 0; const file = await fs.readFile(path, "utf-8"); - numberOfCharacters = file.length; numberOfLines = file.split("\n").length - 1; const words = file.match(/\S+/g); numberOfWords = words ? words.length : 0; + numberOfCharacters = file.length; - if (pathArray.length > 1) { - if (options.lines) { - console.log(`${padStartNumbers(numberOfLines)} ${path}`); - } - if (options.words) { - console.log(`${padStartNumbers(numberOfWords)} ${path}`); - } - if (options.characters) { - console.log(`${padStartNumbers(numberOfCharacters)} ${path}`); - } - } else if (options.lines) { - console.log(`${numberOfLines} ${path}`); - } else if (options.words) { - console.log(`${numberOfWords} ${path}`); - } else if (options.characters) { - console.log(`${numberOfCharacters} ${path}`); - } else { + const rowNumbers = []; + + if (options.lines) rowNumbers.push(numberOfLines); + if (options.words) rowNumbers.push(numberOfWords); + if (options.characters) rowNumbers.push(numberOfCharacters); + + if (rowNumbers.length === 0) { console.log( `${padStartNumbers(numberOfLines, numberOfWords, numberOfCharacters)} ${path}`, ); + } else { + if (pathArray.length === 1 && rowNumbers.length === 1) { + console.log(`${rowNumbers[0]} ${path}`); + } else { + console.log(`${padStartNumbers(...rowNumbers)} ${path}`); + } } totalOfLines += numberOfLines; totalOfWords += numberOfWords; @@ -72,12 +70,11 @@ for (let path of pathArray) { } if (pathArray.length > 1) { - if (options.lines) { - console.log(`${padStartNumbers(totalOfLines)} total`); - } else if (options.words) { - console.log(`${padStartNumbers(totalOfWords)} total`); - } else if (options.characters) { - console.log(`${padStartNumbers(totalOfCharacters)} total`); + if (options.lines) totalRowNumbers.push(totalOfLines); + if (options.words) totalRowNumbers.push(totalOfWords); + if (options.characters) totalRowNumbers.push(totalOfCharacters); + if (totalRowNumbers.length > 0) { + console.log(`${padStartNumbers(...totalRowNumbers)} total`); } else { console.log( `${padStartNumbers(totalOfLines, totalOfWords, totalOfCharacters)} total`, From 06aacde8a87e9e8c2ae6c1dad35c4e300569db7f Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Tue, 24 Mar 2026 14:02:23 +0000 Subject: [PATCH 11/11] improved logic for sorting the file order and added function for formatting directory --- implement-shell-tools/ls/customLs.js | 34 +++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/implement-shell-tools/ls/customLs.js b/implement-shell-tools/ls/customLs.js index cb17e4596..76903f62e 100644 --- a/implement-shell-tools/ls/customLs.js +++ b/implement-shell-tools/ls/customLs.js @@ -15,31 +15,53 @@ const argumentsArray = program.args; const path = argumentsArray[0] || "./"; +async function formatFileName(base, fileName) { + try { + const fullPath = `${base.endsWith("/") ? base : base + "/"}${fileName}`; + const stats = await fs.stat(fullPath); + if (stats.isDirectory()) { + return `\x1b[1;34m${fileName}\x1b[0m`; + } + return fileName; + } catch (error) { + return fileName; + } +} + try { const files = await fs.readdir(path); - const sortedFiles = files.sort((a, b) => a.localeCompare(b)); + const sortedFiles = files.sort((a, b) => { + const cleanA = a.replace(/^\./, ""); + const cleanB = b.replace(/^\./, ""); + return cleanA.localeCompare(cleanB, undefined, { sensitivity: "base" }); + }); const options = program.opts(); let renderingFiles = []; if (options.showHidden) { - renderingFiles = sortedFiles; + renderingFiles = [".", "..", ...sortedFiles]; } else { renderingFiles = sortedFiles.filter((file) => !/^\./.test(file)); } if (options.oneFile) { for (let file of renderingFiles) { - console.log(file); + console.log(await formatFileName(path, file)); } } else { - console.log(renderingFiles.join(" ")); + const formatted = await Promise.all( + renderingFiles.map((fileName) => { + return formatFileName(path, fileName); + }), + ); + console.log(formatted.join(" ")); } } catch (error) { - const state = await fs.stat(path).catch(() => null); - if (state && state.isFile()) { + const stats = await fs.stat(path).catch(() => null); + if (stats && stats.isFile()) { console.log(path); } else { console.log(