From 276fc2be1d76d7ee93ca1a78f2f3d80722ee4542 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Sat, 21 Mar 2026 08:46:45 +0000 Subject: [PATCH 1/4] implementing a logic that behive as cat command --- implement-shell-tools/cat/cat.js | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 implement-shell-tools/cat/cat.js diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 000000000..7c88bd963 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,40 @@ +import process from "node:process"; +import { promises as fs } from "node:fs"; + +// THis will give an array without the path to node and to the file. +const argv = process.argv.slice(2); + +//Get line numbers. +const showNumbers = argv.includes("-n"); +const showNonBlankNumbers = argv.includes("-b"); + +//filter the - from the array argv as it's a flag. +const filePaths = argv.filter((arg) => !arg.startsWith("-")); +let counterLines = 1; + +for (const path of filePaths) { + try { + const content = await fs.readFile(path, "utf-8"); + + //split the text at the new line character. + const splitLines = content.split("\n"); + + splitLines.forEach((line) => { + if (showNumbers) { + console.log(`${counterLines++} ${line}`); + } else if (showNonBlankNumbers) { + // increment and show numbers only if the line is not empty. + if (line.trim() !== "") { + console.log(`${counterLines++} ${line}`); + } else { + // print empty lines + console.log(line); + } + } else { + console.log(line); + } + }); + } catch (error) { + console.log(`Could not read: ${path}`); + } +} From 8675a8b2fca34151e3d2c70721e021bdb15e9417 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Tue, 24 Mar 2026 14:24:42 +0000 Subject: [PATCH 2/4] Solving The exercise for ls command. --- implement-shell-tools/ls/ls.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 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..18b2116e0 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,28 @@ +import fs from "node:fs"; +import process from "node:process"; + +// This will give an array without the path to node and to the file. +const argv = process.argv.slice(2); + +// filter the flag to find the target folder. +const filePaths = argv.filter((arg) => !arg.startsWith("-")); +const showHiddenFiles = argv.includes("-a"); + +// if no folder provide we use the current one +const target = filePaths[0] || "."; +// read the file. +const files = fs.readdirSync(target); + +// save the result into the variable. +let filteredFIles = files; +if (!showHiddenFiles) { + filteredFIles = files.filter((file) => !file.startsWith(".")); +} else { + // we use spread operator to merge the paths. + filteredFIles = [".", "..", ...files]; +} + +// Print using -1 . +filteredFIles.forEach((file) => { + console.log(file); +}); From 99cb233b094f5a4c53bbdeaec48c7964a09e2570 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Thu, 26 Mar 2026 11:00:56 +0000 Subject: [PATCH 3/4] feat: create a script that behave like wc command. --- implement-shell-tools/wc/wc.js | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 000000000..2860444ae --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,40 @@ +import process, { exit } from "node:process"; +import fs from "node:fs"; + +const argv = process.argv.slice(2); + +const showWords = argv.includes("-w"); +const showLines = argv.includes("-l"); +const showBytes = argv.includes("-c"); +const showCharacters = argv.includes("-m"); + +// filter flags, and getting the string of filename +const filePaths = argv.filter((arg) => !arg.startsWith("-")); + +const noFlags = !showLines && !showCharacters && !showWords && !showCharacters; +if (!filePaths) { + console.error("PLease provide a file path"); + process.exit(1); +} +// loop trough the array to get each file path. +filePaths.forEach((filePath) => { + const content = fs.readFileSync(filePath, "utf-8"); + + const lines = content.split("\n").length - 1; + const words = content + .trim() + .split(/\s+/) + .filter((word) => word != "").length; + // here I used Buffer.byteLength even if characters and bytes can be the same number .length, however sometimes an emoji or special characters can be heavier 2b or 4b + const bytes = Buffer.byteLength(content); + const characters = content.length; + + let output = ""; + + if (showLines || noFlags) output += `${lines} `; + if (showWords || noFlags) output += `${words} `; + if (showBytes || noFlags) output += `${bytes} `; + if (showCharacters || noFlags) output += `${characters} `; + + console.log(`${output} ${filePath}`); +}); From e1d7c80532a5eec8dc7b17dfb87eb254678b0fd0 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Sat, 11 Apr 2026 09:30:48 +0100 Subject: [PATCH 4/4] FIx: Implement the missing logics. --- implement-shell-tools/.gitignore | 1 + implement-shell-tools/cat/cat.js | 30 +++++++++++++++++++----------- implement-shell-tools/ls/ls.js | 4 ++++ implement-shell-tools/wc/wc.js | 28 +++++++++++++++++----------- 4 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 implement-shell-tools/.gitignore diff --git a/implement-shell-tools/.gitignore b/implement-shell-tools/.gitignore new file mode 100644 index 000000000..1d17dae13 --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1 @@ +.venv diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 7c88bd963..4c9b552a7 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -10,6 +10,16 @@ const showNonBlankNumbers = argv.includes("-b"); //filter the - from the array argv as it's a flag. const filePaths = argv.filter((arg) => !arg.startsWith("-")); + +const flagsUsed = argv.filter((arg) => arg.startsWith("-")); +const supportedFlags = ["-n", "-b"]; +for (const flag of flagsUsed) { + if (!supportedFlags.includes(flag)) { + console.error(`Invalid option try 'node cat.js --help' for more info.`); + process.exit(1); + } +} + let counterLines = 1; for (const path of filePaths) { @@ -20,21 +30,19 @@ for (const path of filePaths) { const splitLines = content.split("\n"); splitLines.forEach((line) => { - if (showNumbers) { - console.log(`${counterLines++} ${line}`); - } else if (showNonBlankNumbers) { - // increment and show numbers only if the line is not empty. + let prefix = ""; + + if (showNonBlankNumbers) { if (line.trim() !== "") { - console.log(`${counterLines++} ${line}`); - } else { - // print empty lines - console.log(line); + prefix = `${counterLines++} `; } - } else { - console.log(line); + } else if (showNumbers) { + prefix = `${counterLines++} `; } + console.log(`${prefix}${line}`); }); } catch (error) { - console.log(`Could not read: ${path}`); + console.error(`Could not read: ${path}`); + process.exit(1); } } diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 18b2116e0..e1d3b4b75 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -8,6 +8,10 @@ const argv = process.argv.slice(2); const filePaths = argv.filter((arg) => !arg.startsWith("-")); const showHiddenFiles = argv.includes("-a"); +if (filePaths.length > 1) { + console.error("Error: This version only supports one directory path a time."); + process.exit(1); +} // if no folder provide we use the current one const target = filePaths[0] || "."; // read the file. diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 2860444ae..44e1a1429 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -3,16 +3,22 @@ import fs from "node:fs"; const argv = process.argv.slice(2); -const showWords = argv.includes("-w"); -const showLines = argv.includes("-l"); -const showBytes = argv.includes("-c"); -const showCharacters = argv.includes("-m"); +let showWords = argv.includes("-w"); +let showLines = argv.includes("-l"); +let showBytes = argv.includes("-c"); +let showCharacters = argv.includes("-m"); // filter flags, and getting the string of filename const filePaths = argv.filter((arg) => !arg.startsWith("-")); - -const noFlags = !showLines && !showCharacters && !showWords && !showCharacters; -if (!filePaths) { +// if no flags enable all. +if (!showLines && !showCharacters && !showWords && !showBytes) { + showLines = true; + showCharacters = true; + showWords = true; + showBytes = true; +} +// fix bug .length ===0 will be true if nothing provided, instead !filePath will return empty array which result true. +if (filePaths.length === 0) { console.error("PLease provide a file path"); process.exit(1); } @@ -31,10 +37,10 @@ filePaths.forEach((filePath) => { let output = ""; - if (showLines || noFlags) output += `${lines} `; - if (showWords || noFlags) output += `${words} `; - if (showBytes || noFlags) output += `${bytes} `; - if (showCharacters || noFlags) output += `${characters} `; + if (showLines) output += `${lines} `; + if (showWords) output += `${words} `; + if (showBytes) output += `${bytes} `; + if (showCharacters) output += `${characters} `; console.log(`${output} ${filePath}`); });