Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
58 changes: 58 additions & 0 deletions implement-shell-tools/cat/cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env node
const fs = require("node:fs");
//shared line counter across all files(matches cat -n)
let globalLineCounter = 1;

function printFile(filePath, options) {
try {
const content = fs.readFileSync(filePath, "utf-8");
const lines = content.split("\n");

lines.forEach((line) => {
let prefix = "";

const shouldNumber =
options.numberMode === "all" ||
(options.numberMode === "non-empty" && line.trim() !== "");

if (shouldNumber) {
prefix = `${String(globalLineCounter).padStart(6)}\t`;
globalLineCounter++;
}

process.stdout.write(`${prefix}${line}\n`);
});
} catch (error) {
console.error(`cat: ${filePath}: ${error.message}`);
Copy link
Member

Choose a reason for hiding this comment

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

This will still exit 0 if e.g. a file didn't exist.

}
}

function main() {
Copy link
Member

Choose a reason for hiding this comment

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

This tool behaves slightly differently than built-in cat for the examples given in https://github.com/CodeYourFuture/Module-Tools/blob/main/implement-shell-tools/cat/README.md

Copy link
Member

Choose a reason for hiding this comment

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

Your wc does too for the same reason.

Copy link
Author

Choose a reason for hiding this comment

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

Thank you so much for that. I'll write an integration test to find the issue.

const args = process.argv.slice(2);

const options = {
numberMode: "off",
};

const files = [];
args.forEach((arg) => {
if (arg === "-n") {
options.numberMode = "all";
} else if (arg === "-b") {
options.numberMode = "non-empty";
} else {
files.push(arg);
}
});

if (files.length === 0) {
console.log("cat: missing file operand");
process.exit(1);
}

files.forEach((file) => {
printFile(file, options);
});
}

main();
57 changes: 57 additions & 0 deletions implement-shell-tools/ls/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env node
const fs = require("node:fs");
function listDirectory(dirPath, showAll, onePerLine) {
try {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
const filtered = entries.filter(
(entry) => showAll || !entry.name.startsWith("."),
);

if (onePerLine) {
filtered.forEach((entry) => console.log(entry.name));
} else {
const names = filtered.map((entry) => entry.name);
console.log(names.join(" "));
Copy link
Member

Choose a reason for hiding this comment

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

In cat you used process.stdout.write and here you're using console.log - what's the difference? When would you choose to use one or the other?

Copy link
Author

Choose a reason for hiding this comment

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

We use process.stdout.write when we need full control over spacing and newlines, byte-exact output matters, and to avoid automatic newlines, like in cat.
We use console.log when we just want simple, line‑by‑line output with automatic newlines, like in ls.

}
} catch (error) {
console.error(`Error reading directory ${dirPath}: ${error.message}`);
Copy link
Member

Choose a reason for hiding this comment

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

What exit code should our process terminate with if it hit an error? What does your code do?

process.exitCode = 1;
}
}
function main() {
const args = process.argv.slice(2);
// Check for options
const showAll = args.includes("-a");
const onePerLine = args.includes("-1");
//remove options from args
let directories = args.filter((arg) => arg !== "-a" && arg !== "-1");

// Default to current directory if no directories are specified
Copy link
Member

Choose a reason for hiding this comment

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

Is this comment useful compared to just reading the code?

if (directories.length === 0) {
directories = [process.cwd()];
}
directories.forEach((arg, index) => {
try {
const stats = fs.statSync(arg);

if (stats.isDirectory()) {
//Print header if multiple directories are listed
Copy link
Member

Choose a reason for hiding this comment

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

Is this comment useful compared to just reading the code?

if (directories.length > 1) {
console.log(`${arg}:`)
};
listDirectory(arg, showAll, onePerLine);
//add a blank line between directory listings if there are multiple directories
if (directories.length > 1 && index < directories.length - 1){
console.log("");
}

} else {
console.log(arg); // single file
}
} catch (error) {
console.error(`Error accessing ${arg}: ${error.message}`);
process.exitCode = 1;
}
});
}
main();
74 changes: 74 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env node
const fs = require("node:fs");
// Function to count lines, words, and bytes in a file
function countFileContent(content) {
const lines = content.split("\n").length; // Count lines by splitting on newline characters
const words = content.trim().split(/\s+/).filter(Boolean).length; // Split by whitespace and filter out empty strings
const bytes = Buffer.byteLength(content, "utf8");
return { lines, words, bytes };
}

//print counts in the format of wc according to options
function printCounts(filePath, counts, options) {
const parts = [];
if (options.line) parts.push(counts.lines);
if (options.word) parts.push(counts.words);
if (options.byte) parts.push(counts.bytes);

console.log(parts.join("\t"), filePath);
}

function main() {
const args = process.argv.slice(2);
const options = {
line: false,
word: false,
byte: false,
};

//Separate options from file paths
const files = [];
args.forEach((arg) => {
if (arg === "-l") options.line = true;
else if (arg === "-w") options.word = true;
else if (arg === "-c") options.byte = true;
else files.push(arg);
});

if (files.length === 0) {
console.error("No files specified");
process.exit(1);
}
//If no specific count options are provided, default to all counts (lines, words, bytes)
if (!options.line && !options.word && !options.byte) {
options.line = true;
options.word = true;
options.byte = true;
}

let totalCounts = { lines: 0, words: 0, bytes: 0 };
const multipleFiles = files.length > 1;

files.forEach((file) => {
try {
const content = fs.readFileSync(file, "utf-8");
const counts = countFileContent(content);

// Sum counts for total if multiple files
totalCounts.lines += counts.lines;
totalCounts.words += counts.words;
totalCounts.bytes += counts.bytes;

printCounts(file, counts, options);
} catch (error) {
console.error(`Error reading file ${file}: ${error.message}`);
Copy link
Member

Choose a reason for hiding this comment

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

Same question about exit codes

process.exitCode = 1;
}
});

// If multiple files, print total counts
if (multipleFiles) {
printCounts("total", totalCounts, options);
}
}
main();
Loading