diff --git a/.gitignore b/.gitignore index 3c3629e6..9be2500d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ + +prep node_modules +.venv diff --git a/implement-shell-tools/.gitignore b/implement-shell-tools/.gitignore new file mode 100644 index 00000000..538b43a8 --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1,2 @@ +prep/ +prep/ diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 00000000..3579f558 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,50 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +//here we Configure the program: what flags and arguments it accepts +// .option("short, long", "description") +//The < > brackets mean "required" + +program + .name("cat") + .description("concatenate and print files") + .option("-n, --number", "Number all output lines") + .option("-b, --numberNonBlank", "Number non-blank output lines") + .argument("", "The file paths to process"); + +// here Parse command line arguments (reads process.argv and interprets it) +program.parse(); + +// Shared counter across all files for -n and -b flags +let lineNumber = 0; + + +// Process each file +for (const path of program.args) { + const hasNumberFlag = program.opts().number; // True if user used -n flag + const shouldNumberNonBlank = program.opts().numberNonBlank; + + //read the file for each argument + const content = await fs.readFile(path, "utf-8"); + const lines = content.split("\n"); // Split into array of lines + + // Output with or without line numbers + if (hasNumberFlag) { + // Add line numbers to each line + const numberedLines = lines.map((line) => { + lineNumber = lineNumber + 1; + return `${lineNumber} ${line}`; // Format: " 1 Hello" + }); + console.log(numberedLines.join("\n")); // Join back with newlines + } else if (shouldNumberNonBlank) { + + const numberedLines = lines.map((line) => { + return line.trim() === "" ? line : `${++lineNumber} ${line}`; + }); + console.log(numberedLines.join("\n")); + } else { + // Just print the file as-is + console.log(content); + } +} + diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 00000000..17338728 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,47 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +//config the program +program + .name("ls") + .description("list directory contents") + .option("-1, --one", "Force output to be one entry per line") + .option( + "-a, --all", + "shows all the files including the hidden ones which start with a dot" + ) + .argument("[directory]", "Directory to list", "."); // "." means current directory + +//interpret the program +program.parse(); + +// Get the directory argument (first argument in program.args array) +// If no argument provided, default to current directory "." +const directory = program.args[0] || "."; + +//read the directory to get array of filenames +const files = await fs.readdir(directory); + +//check for flags +const hasAflag = program.opts().all; +const hasOneFlag = program.opts().one; + +// Filter the files array BEFORE looping +// If hasAflag is true, keep all files +// If hasAflag is false, remove files that start with "." + +const fileToShow = hasAflag +? files +: files.filter(file => !file.startsWith(".")) + + +if (hasOneFlag) { + + for (const file of fileToShow) { + console.log(file); + } +} else { + // print horizontally + console.log(fileToShow.join(" ")); +} + diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 00000000..3dbc1ca5 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 00000000..d79a48eb --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,66 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +//config the program +program + .name("wc") + .description( + "The wc utility displays the number of lines, words, and bytes contained in each input file, or standard input" + ) + .option("-l, --lines", "count lines only") + .option("-w, --words", "count words only") + .option("-c, --bytes", "count bytes only") + .argument("", "The file paths to process"); + +//interpret the program +program.parse(); + +//initialise totals +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; + +//check for flags +const hasLineFlag = program.opts().lines; +const hasWordFlag = program.opts().words; +const hasBytesFlag = program.opts().bytes; + +// create output format function to avoid repetition +function formatOutput(lines, words, bytes, path) { + if (hasLineFlag) { + console.log(`${lines} ${path}`); + } else if (hasWordFlag) { + console.log(`${words} ${path}`); + } else if (hasBytesFlag) { + console.log(`${bytes} ${path}`); + } else { + console.log(`${lines} ${words} ${bytes} ${path}`); + } +} + +//process each file + +for (const path of program.args) { + //read the file + const content = await fs.readFile(path, "utf-8"); + + //count lines + const lines = content.split("\n").length - 1; + + //count words (split by any whitespace) + const words = content.split(/\s+/).filter((word) => word.length > 0).length; + + //count bytes correctly especially important for non-ASCII characters + const bytes = Buffer.byteLength(content, "utf-8"); + + //Add to totals + totalLines += lines; + totalWords += words; + totalBytes += bytes; + + formatOutput(lines, words, bytes, path); +} + +if (program.args.length > 1) { + formatOutput(totalLines, totalWords, totalBytes, "total"); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..ada06fac --- /dev/null +++ b/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "Module-Tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "commander": "^14.0.2" + } + }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..76dcd3f7 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "commander": "^14.0.2" + } +}