From d3fa55faf48b76ae54259231336bd980767bc136 Mon Sep 17 00:00:00 2001 From: nadika Date: Thu, 13 Mar 2025 10:16:49 +0000 Subject: [PATCH 1/9] Implement ls command --- implement-shell-tools/ls/ls.js | 75 ++++++++++++++++++++++++++++++++++ 1 file changed, 75 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 00000000..e60a2e3b --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,75 @@ +// Getting the 'promises' part of the 'fs' module and calling it 'fs' to make it easier to use +import { promises as fs } from "fs"; +// Getting the 'resolve' function from 'path' module to turn relative path (from argument) into full path (absolute path) +import { resolve } from "path"; +// Importing the 'program' object from 'commander' to help build command-line interfaces, handle user inputs. +import { program } from "commander"; +// Importing the 'chalk' module to add colors and styles to text in the console +import chalk from "chalk"; + +// Function to list contents of a directory. +async function listDirectoryFiles(directoryPath, showHiddenFiles = false, oneFilePerLine = false) { + try { + // Function call from the fs module. It reads contents of a directory, returns an array of file names (files) + let files = await fs.readdir(directoryPath); + // Array stores current directory, parent directory) + let dotDirectories = []; + // Array stores the names of the files and directories + let displayFiles = []; + // Ensure '.', '..' are on the top of list when we list hidden files + if (showHiddenFiles) { + dotDirectories = [".", ".."]; + } + // Loop through files and add to displayFiles, including hidden files if -a flag is set + for (const file of files) { + if (!file.startsWith(".") || showHiddenFiles) { + displayFiles.push(file); + } + } + // Sort files without leading dots for hidden files + displayFiles.sort((a, b) => { + const fileNameA = a.startsWith(".") ? a.slice(1) : a; + const fileNameB = b.startsWith(".") ? b.slice(1) : b; + // Compares two filenames, considering numbers and ignoring case sensitivity + return fileNameA.localeCompare(fileNameB, "en", { numeric: true, sensitivity: "base" }); + }); + // Merging two arrays into one + let sortedFiles = [...dotDirectories, ...displayFiles]; + + const allFiles = sortedFiles.map(file => { + // Creating the absolute path for the file by combining directoryPath and file name + const absoluteFilePath = resolve(directoryPath, file); + // Check if the file is a directory; if yes, color it blue, otherwise keep it normal. + return fs.stat(absoluteFilePath).then(stats => + stats.isDirectory() ? chalk.blue.bold(file) : file + ); + }); + // Array with style directory in bold and blue + const styledAllFiles = await Promise.all(allFiles); + // Print files either one per line or in a single line separated by spaces + console.log(oneFilePerLine ? styledAllFiles.join("\n") : styledAllFiles.join(" ")); + // If an error occurs while reading the directory, console the error message + } catch (error) { + console.error(`Error reading directory: ${error.message}`); + } +} +// Configure the CLI tool with name, description, arguments, and options +// 'program' is from the Commander package and handles command-line arguments +program + .name("ls command") + .description("Implementing ls command (-1, -a flags) functionality") + .argument("[path]", "Path to list", ".") + .option("-a", "List hidden files, directories") + .option("-1", "List one file, directory per line") + // Read and process command-line arguments, process.argv - an array containing the command-line arguments + .parse(process.argv); + +// Get user inputs from command-line options and arguments +const directory = program.args[0] || "."; +const showHiddenFiles = program.opts().a; +const oneFilePerLine = program.opts()["1"]; + +// Resolve the absolute path of the directory +const absoluteDirectoryPath = resolve(directory); +// Call the function to list the files in the directory (with options) +await listDirectoryFiles(absoluteDirectoryPath, showHiddenFiles, oneFilePerLine); \ No newline at end of file From ff16c210c97fcd481962a63222f84d5632749f33 Mon Sep 17 00:00:00 2001 From: nadika Date: Thu, 13 Mar 2025 16:22:44 +0000 Subject: [PATCH 2/9] Implement cat command --- implement-shell-tools/cat/cat.js | 68 ++++++++++++++++++++++++++++++++ 1 file changed, 68 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 00000000..dd0fc88e --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,68 @@ +import { promises as fs } from "fs"; +import { program } from "commander"; +// For handling "*.txt" +import * as glob from "glob"; + +// Set up program +program + .name("Cat command") + .description("Implementing 'cat' command (-n, -b flags) functionality") + .argument("", "Path") + .option("-n", "Display line numbers") + .option("-b", "Display line numbers only for non-empty lines") + .parse(process.argv) + +// Function to print file contents with the flags +async function displayFileContents(filePath, displayLineNumbers = false, displayNonEmptyLineNumbers = false) { + try { + // Read the file content + let content = await fs.readFile(filePath, "utf-8"); + // Split content into array based on new lines + let arrayContentLines = content.split("\n"); + // If last line is emty, remove it + while (arrayContentLines.length > 0 && arrayContentLines[arrayContentLines.length - 1].trim() === "") { + arrayContentLines.pop(); + } + + // Map through all lines and apply different conditions depend on chosen flag. + arrayContentLines = arrayContentLines.map(line => { + if (displayNonEmptyLineNumbers) { + if (line.trim()) { + return ` ${lineNumber++} ${line}` + } return ""; + } + if (displayLineNumbers) { + return ` ${lineNumber++} ${line}`; + } + return line; + }); + // join lines back to the string and print them + console.log(arrayContentLines.join("\n")); + } + catch (error) { + console.error(`Error reading directory: ${error.message}`); + } +} + +//Get user inputs from command line +const filePaths = program.args; +const displayLineNumbers = program.opts().n; +const displayNonEmptyLineNumbers = program.opts().b; + +// Declare and assign line number for printing line with numbers +let lineNumber = 1; + +const arrayFilePaths = []; + +// Loop through the array with paths to handle "*" +for (const filePath of filePaths) { + // if filePath includes "*", use glob and push files to thr array + if (filePath.includes("*")) { + arrayFilePaths.push(...await glob(filePath)); + } else { + arrayFilePaths.push(filePath); + } +} + +// Handling multiply file processing using Promise.all and map() +await Promise.all(arrayFilePaths.map(filePath => displayFileContents(filePath, displayLineNumbers, displayNonEmptyLineNumbers))); From dc096f169a864ab9483001e4ecd7696cd817148f Mon Sep 17 00:00:00 2001 From: nadika Date: Fri, 14 Mar 2025 12:02:18 +0000 Subject: [PATCH 3/9] Implement wc command --- implement-shell-tools/wc/wc.js | 90 ++++++++++++++++++++++++++++++++++ 1 file changed, 90 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 00000000..a88429e7 --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,90 @@ +// Import required modules +import { program } from "commander"; +import { promises as fs } from "fs"; +import * as glob from "glob"; + +// Set up program +program + .name("wc command") + .description("Implementing 'wc' command (-c, -w, -l flags) functionality") + .argument("", "Path") + .option("-c", "Display the number of bytes (characters)") + .option("-w", "Display the number of words") + .option("-l", "Display the number of lines") + .parse(process.argv); + +// Function to count bytes, words and lines in a file +async function countDisplayNumBytesWordsLines(filePath, numLines = false, numWords = false, numBytes = false) { + try { + // reading file content + let content = await fs.readFile(filePath, "utf-8"); + // Split content into array by lines + let arrayLines = content.split("\n"); + // If the last line is empty - remove it + while (arrayLines.length > 0 && arrayLines[arrayLines.length - 1].trim() === "") { + arrayLines.pop(); + } + + // Get the number of lines + const countNumLines = arrayLines.length; + // Get the number of words using split by any whitespace (spaces, newlines, tabs), filter to remove empty strings + const countNumWords = content.split(/\s+/).filter(word => word !== "").length; + // Get the number of bytes (file size in bytes) + const countNumBytes = (await fs.stat(filePath)).size; + + // Count total of lines, words, bytes + totalLines += countNumLines; + totalWords += countNumWords; + totalBytes += countNumBytes; + + // Formatting output + const output = [ + numLines || showAll ? countNumLines.toString().padStart(3) : null, + numWords || showAll ? countNumWords.toString().padStart(3) : null, + numBytes || showAll ? countNumBytes.toString().padStart(3) : null, + filePath + ].filter(element => element !== null).join(" "); + console.log(output); + } catch (error) { + console.error(`Error reading file: ${filePath} - ${error.message}`); + } +} + +// Get command-line arguments +const filePaths = program.args; +const numLines = program.opts().l; +const numWords = program.opts().w; +const numBytes = program.opts().c; + +// Show all, if no flags +const showAll = !numBytes && !numWords && !numLines; + +const arrayFilePaths = []; + +// Handle "*" +for (const filePath of filePaths) { + if (filePath.includes("*")) { + arrayFilePaths.push(...await glob(filePath)); + } else { + arrayFilePaths.push(filePath); + } +} + +// Initialisation of count total of lines, words, bytes +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; + +await Promise.all(arrayFilePaths.map(filePath => countDisplayNumBytesWordsLines(filePath, numLines, numWords, numBytes))); + +// Formatting and printing total numbers in console +if (arrayFilePaths.length > 1) { + console.log( + [ + numLines || showAll ? totalLines.toString().padStart(3) : null, + numWords || showAll ? totalWords.toString().padStart(3) : null, + numBytes || showAll ? totalBytes.toString().padStart(3) : null, + "total" + ].filter(element => element !== null).join(" ") + ); +} From 2ab48d1b8e82dff1a87ccc639263edb6035f7b1c Mon Sep 17 00:00:00 2001 From: nadika Date: Fri, 14 Mar 2025 12:06:16 +0000 Subject: [PATCH 4/9] Add package.json --- implement-shell-tools/package-lock.json | 465 ++++++++++++++++++++++++ implement-shell-tools/package.json | 18 + 2 files changed, 483 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 00000000..a52151cd --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,465 @@ +{ + "name": "implement-shell-tools", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "implement-shell-tools", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "glob": "^11.0.1" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 00000000..865e14e5 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,18 @@ +{ + "name": "implement-shell-tools", + "version": "1.0.0", + "description": "Your task is to re-implement shell tools you have used.", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "glob": "^11.0.1" + } +} From 509a5dbb3cdd5575fd6def7c9129f9f65121e8f9 Mon Sep 17 00:00:00 2001 From: nadika Date: Sat, 15 Mar 2025 08:48:17 +0000 Subject: [PATCH 5/9] Refactor --- implement-shell-tools/cat/cat.js | 2 +- implement-shell-tools/ls/ls.js | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index dd0fc88e..0b405b35 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -5,7 +5,7 @@ import * as glob from "glob"; // Set up program program - .name("Cat command") + .name("cat command") .description("Implementing 'cat' command (-n, -b flags) functionality") .argument("", "Path") .option("-n", "Display line numbers") diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index e60a2e3b..6ecbdc8a 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -7,6 +7,17 @@ import { program } from "commander"; // Importing the 'chalk' module to add colors and styles to text in the console import chalk from "chalk"; +// Configure the CLI tool with name, description, arguments, and options +// 'program' is from the Commander package and handles command-line arguments +program + .name("ls command") + .description("Implementing ls command (-1, -a flags) functionality") + .argument("[path]", "Path to list", ".") + .option("-a", "List hidden files, directories") + .option("-1", "List one file, directory per line") + // Read and process command-line arguments, process.argv - an array containing the command-line arguments + .parse(process.argv); + // Function to list contents of a directory. async function listDirectoryFiles(directoryPath, showHiddenFiles = false, oneFilePerLine = false) { try { @@ -53,16 +64,6 @@ async function listDirectoryFiles(directoryPath, showHiddenFiles = false, oneFil console.error(`Error reading directory: ${error.message}`); } } -// Configure the CLI tool with name, description, arguments, and options -// 'program' is from the Commander package and handles command-line arguments -program - .name("ls command") - .description("Implementing ls command (-1, -a flags) functionality") - .argument("[path]", "Path to list", ".") - .option("-a", "List hidden files, directories") - .option("-1", "List one file, directory per line") - // Read and process command-line arguments, process.argv - an array containing the command-line arguments - .parse(process.argv); // Get user inputs from command-line options and arguments const directory = program.args[0] || "."; From 8ed136690d2b5bbd2bdb8808e95ae6f467dcce94 Mon Sep 17 00:00:00 2001 From: nadika Date: Tue, 8 Apr 2025 18:25:02 +0100 Subject: [PATCH 6/9] Remove join method, implement print as we go in loop --- implement-shell-tools/cat/cat.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 0b405b35..43f5057c 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -28,16 +28,14 @@ async function displayFileContents(filePath, displayLineNumbers = false, display arrayContentLines = arrayContentLines.map(line => { if (displayNonEmptyLineNumbers) { if (line.trim()) { - return ` ${lineNumber++} ${line}` + console.log(` ${lineNumber++} ${line}`); } return ""; - } - if (displayLineNumbers) { - return ` ${lineNumber++} ${line}`; - } - return line; - }); - // join lines back to the string and print them - console.log(arrayContentLines.join("\n")); + } else if (displayLineNumbers) { + console.log(` ${lineNumber++} ${line}`); + } else{ + console.log(line); + } + }); } catch (error) { console.error(`Error reading directory: ${error.message}`); From 5d98dfdd4ff6edcadaedb9eb1fef8465e4739208 Mon Sep 17 00:00:00 2001 From: nadika Date: Tue, 8 Apr 2025 21:01:20 +0100 Subject: [PATCH 7/9] Change to readline module --- implement-shell-tools/cat/cat.js | 39 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 43f5057c..c07a5a72 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,5 +1,7 @@ -import { promises as fs } from "fs"; +import * as fs from "fs"; import { program } from "commander"; +// For reading file line by line +import * as readline from "readline"; // For handling "*.txt" import * as glob from "glob"; @@ -15,29 +17,29 @@ program // Function to print file contents with the flags async function displayFileContents(filePath, displayLineNumbers = false, displayNonEmptyLineNumbers = false) { try { - // Read the file content - let content = await fs.readFile(filePath, "utf-8"); - // Split content into array based on new lines - let arrayContentLines = content.split("\n"); - // If last line is emty, remove it - while (arrayContentLines.length > 0 && arrayContentLines[arrayContentLines.length - 1].trim() === "") { - arrayContentLines.pop(); - } + // Create a readline interface to read file line by line + const fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' }); + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }); + + // Initialise the line number + let lineNumber = 1; - // Map through all lines and apply different conditions depend on chosen flag. - arrayContentLines = arrayContentLines.map(line => { + // Process each line + for await (const line of rl) { if (displayNonEmptyLineNumbers) { if (line.trim()) { console.log(` ${lineNumber++} ${line}`); - } return ""; + } } else if (displayLineNumbers) { console.log(` ${lineNumber++} ${line}`); - } else{ + } else { console.log(line); - } - }); - } - catch (error) { + } + } + } catch (error) { console.error(`Error reading directory: ${error.message}`); } } @@ -47,9 +49,6 @@ const filePaths = program.args; const displayLineNumbers = program.opts().n; const displayNonEmptyLineNumbers = program.opts().b; -// Declare and assign line number for printing line with numbers -let lineNumber = 1; - const arrayFilePaths = []; // Loop through the array with paths to handle "*" From a1bec5b9acfd9305111254e200cb5f1dd46facb9 Mon Sep 17 00:00:00 2001 From: nadika Date: Tue, 8 Apr 2025 21:51:07 +0100 Subject: [PATCH 8/9] Refactor --- implement-shell-tools/wc/wc.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index a88429e7..ed13cd42 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -20,13 +20,9 @@ async function countDisplayNumBytesWordsLines(filePath, numLines = false, numWor let content = await fs.readFile(filePath, "utf-8"); // Split content into array by lines let arrayLines = content.split("\n"); - // If the last line is empty - remove it - while (arrayLines.length > 0 && arrayLines[arrayLines.length - 1].trim() === "") { - arrayLines.pop(); - } - + // Get the number of lines - const countNumLines = arrayLines.length; + const countNumLines = arrayLines.filter(line => line.trim() !== "").length; // Get the number of words using split by any whitespace (spaces, newlines, tabs), filter to remove empty strings const countNumWords = content.split(/\s+/).filter(word => word !== "").length; // Get the number of bytes (file size in bytes) From a33f39106423cb223673772534fac9de51d4f14c Mon Sep 17 00:00:00 2001 From: nadika Date: Tue, 8 Apr 2025 21:57:35 +0100 Subject: [PATCH 9/9] Refactor --- implement-shell-tools/wc/wc.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index ed13cd42..1bfc6fae 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -8,9 +8,6 @@ program .name("wc command") .description("Implementing 'wc' command (-c, -w, -l flags) functionality") .argument("", "Path") - .option("-c", "Display the number of bytes (characters)") - .option("-w", "Display the number of words") - .option("-l", "Display the number of lines") .parse(process.argv); // Function to count bytes, words and lines in a file