Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions implement-shell-tools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Node modules
node_modules/

# Package lock file (optional)
package-lock.json
56 changes: 56 additions & 0 deletions implement-shell-tools/cat/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//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("<files...>", "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) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea to pull this function out, but you could have taken advantage of this to reduce the duplication of the string formatting code, which appears twice.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thankyou for pointing that out, I have removed teh duplication.

if (numberAll) {
return `${lineNumber.toString().padStart(paddingWidth)} ${line}`;
}
if (numberNonBlank) {
if (line.trim() === "") {
return line; // blank line, no number
}
return `${lineNumber.toString().padStart(paddingWidth)} ${line}`;
}
return line; // no numbering
}
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++;
}
}
}
39 changes: 39 additions & 0 deletions implement-shell-tools/ls/ls.js

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice, cleanly written implementation

Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 6 additions & 0 deletions implement-shell-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "module",
"dependencies": {
"commander": "^14.0.2"
}
}
61 changes: 61 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -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("<files...>", "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 };
}
//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;

let output = "";
if (options.lines) output += `${lineCount} `;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some duplication of your output functions here, is there any way you could reduce this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I refactored the code with a helper function to format outpts.

if (options.words) output += `${wordCount} `;
if (options.bytes) output += `${byteCount} `;
if (!options.lines && !options.words && !options.bytes) {
output += `${lineCount} ${wordCount} ${byteCount} `;
}
output += file;
console.log(output);
}


//Print totals if multiple files
if (files.length > 1) {
let totalOutput = "";
if (options.lines) totalOutput += `${totalLines} `;
if (options.words) totalOutput += `${totalWords} `;
if (options.bytes) totalOutput += `${totalBytes} `;
if (!options.lines && !options.words && !options.bytes) {
totalOutput += `${totalLines} ${totalWords} ${totalBytes} `;
}
totalOutput += "total";
console.log(totalOutput);
}