diff --git a/implement-shell-tools/.gitignore b/implement-shell-tools/.gitignore new file mode 100644 index 000000000..84e210b2a --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1,5 @@ +# Node modules +node_modules/ + +# Package lock file (optional) +package-lock.json \ No newline at end of file diff --git a/implement-shell-tools/cat/file.js b/implement-shell-tools/cat/file.js new file mode 100644 index 000000000..154904968 --- /dev/null +++ b/implement-shell-tools/cat/file.js @@ -0,0 +1,54 @@ +//On macOS (BSD cat), the -n option resets numbering for each file, so multiple files start again at 1, +//when you run cat -n sample-files/*.txt +//This Node.js script mimics GNU cat, where -n continues numbering across all files instead of restarting. +import { program } from "commander"; +import {promises as fs} from "node:fs" + + //configuring the program here to run flags. + +program + .name("cat") + .description("Concatenate and print files") + .option("-n, --number", "Number all output lines") + .option("-b, --number-nonblank", "Number non-blank output lines") + .argument("", "File paths to display") + .parse(process.argv);//Parse command line arguments (reads process.argv and interprets it) + + +//Constants +const paddingWidth = 6; // width for line number padding + +// Helper function to format a line with numbering +function formatLine(line, lineNumber, numberAll, numberNonBlank) { + const numbered =`${lineNumber.toString().padStart(paddingWidth)} ${line}`; + if (numberAll) { + return numbered + } + if (numberNonBlank) { + return line.trim() === "" ? line : numbered; + } + return line; + } +const options = program.opts(); +const files = program.args; // Array of file paths passed as arguments + +let lineNumber = 1; // shared across all files + +for (const file of files) { + const content = await fs.readFile(file, "utf-8"); + let lines = content.split("\n"); + // Remove trailing empty line if file ends with newline + if (lines.length > 0 && lines[lines.length - 1] === "") { + lines.pop(); + } + + for (const line of lines) { + const formatted = formatLine(line, lineNumber, options.number, options.numberNonblank); + console.log(formatted); + + // Increment line number if numbering applies + if (options.number || (options.numberNonblank && line.trim() !== "")) { + lineNumber++; + } + } +} \ No newline at end of file diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 000000000..449835aed --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,39 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +//configuring +program + .name("ls") + .description("list directory contents") + .option("-1, --one", "Outputs are printed one entry per line") + .option("-a, --all","Show all files including hidden files that start with a .") + .argument("[directory]", "Directory to list", "."); // "." means current directory + +//interpret the program +program.parse(); +const options = program.opts(); +const directory = program.args[0] || "."; //get dir arg- 1st arg in program.args array || if no arg default to current dir + + +let files = await fs.readdir(directory); //read the dir to get array of filenames + +//Handle -a (include hidden files) +// Node's fs.readdir() does not include the special directory entries "." (current dir) +// and ".." (parent dir). The real `ls -a` command shows them, so we add them manually here +// to match the behavior of `ls -a`. +if (options.all) { + + files.unshift(".."); + files.unshift("."); +} else { + files = files.filter(name => !name.startsWith(".")); +} + +if (options.one) { // Print each file on its own line + for (const file of files) { + console.log(file); + } +} +else { + console.log(files.join(" "));// Default: join with spaces (like ls without -1) +} \ No newline at end of file diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 000000000..aff578330 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "commander": "^14.0.2" + } +} diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 000000000..fda14b91b --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,61 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +//configuring +program + .name("wc") + .description("Print newline, word, and byte counts for each file") + .option("-l, --lines", "Print the newline counts") + .option("-w, --words", "Print the word counts") + .option("-c, --bytes", "Print the byte counts") + .argument("", "File paths to process") +//Interpret the program + program.parse() +const options = program.opts() +const files = program.args //Interpreting parsed data + + +//helper function to calculate all counts +async function getCounts(file){ + const content = await fs.readFile(file, "utf-8"); + const lineCount = content.split("\n").length -1;//split("\n") returns one more element than the actual number so length-1, + const wordCount = content.trim().split(/\s+/).length; + const byteCount = Buffer.byteLength(content, "utf-8"); //Calculate how many bytes the string uses in UTF-8 (important because some characters use more than 1 byte) + return { lineCount, wordCount, byteCount }; +} +//helper to remove duplicated output logic +function formatOutput(lines, words, bytes, filename, options) { + let formattedOutput = ""; + if (options.lines) formattedOutput += `${lines} `; + if (options.words) formattedOutput+= `${words} `; + if (options.bytes) formattedOutput += `${bytes} `; + if (!options.lines && !options.words && !options.bytes) { // default: print all three + formattedOutput += `${lines} ${words} ${bytes} `; + } + return formattedOutput + filename; + } + //Initiating totals +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; + +for (const file of files) { + const { lineCount, wordCount, byteCount } = await getCounts(file); + + totalLines += lineCount; + totalWords += wordCount; + totalBytes += byteCount; + + const formatted = formatOutput(lineCount, wordCount, byteCount, file, options); + console.log(formatted); +} +//Print totals if multiple files +if (files.length > 1) { + const formattedTotals = formatOutput(totalLines, totalWords, totalBytes, "total", options); + console.log(formattedTotals); +} + + + + + \ No newline at end of file