Skip to content

London | Ameneh Keshavarz | Implement-shell-tools| WEEK3 #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
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
40 changes: 40 additions & 0 deletions implement-shell-tools/cat/cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const { promises: fs } = require("fs");
const { program } = require("commander");

program
.name("cat")
.description("Implement a version of the cat program")
.option("-n, --number", "Number all output lines, starting at 1")
.option("-b, --number2", "Number non-empty output lines, starting at 1")
.argument("<paths...>", "The file paths to process")
.parse();

const { number: nOption, number2: bOption } = program.opts();
const paths = program.args;

let lineNumber = 1;
let nonEmptyLineNumber = 1;

function printLinesWithOptions(lines) {
lines.forEach(line => {
if (nOption) {
console.log(`${String(lineNumber++).padStart(6)} ${line}`);
} else if (bOption && line.trim()) {
console.log(`${String(nonEmptyLineNumber++).padStart(6)} ${line}`);
} else {
console.log(line);
}
});
}

async function readFileContent(path) {
try {
const content = await fs.readFile(path, "utf-8");
const lines = content.split("\n");
printLinesWithOptions(lines);
} catch (err) {
console.error(`Error reading file ${path}: ${err.message}`);
}
}

Promise.all(paths.map(readFileContent));
39 changes: 39 additions & 0 deletions implement-shell-tools/ls/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const fs = require('fs');
const { program } = require('commander');

program
.option('-a, --all', 'Show hidden files')

Choose a reason for hiding this comment

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

When -a is set, it should also print the current directory (.) and the parent diractory (..).

➜ ls -1 -a sample-files
.
..
.hidden.txt
1.txt
2.txt
3.txt
dir

How could we modify this solution so that we do print them?

.option('-1', 'Show one file per line')
.argument('[dirPaths...]', 'Directory paths', ['.']) // Support multiple directories
.parse(process.argv);

Choose a reason for hiding this comment

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

What happens when we receive more than one argument? (e.g. node ls.js directory1 directory2)

Copy link
Author

Choose a reason for hiding this comment

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

Thanks, I removed the unnecessary special case for single input and added support for multiple directory arguments like node ls.js dir1 dir2. I also made sure the code handles each path safely and gives clear output.


const { all, '1': onePerLine } = program.opts();
const dirPaths = program.args;

function listDirectoryContents(dirPath, showHidden = false, onePerLine = false) {
fs.readdir(dirPath, (err, files) => {
if (err) {
console.error(`Error reading directory '${dirPath}': ${err.message}`);
return;
}

if (!showHidden) {
files = files.filter(file => !file.startsWith('.'));
}

console.log(`\n${dirPath}:`);
if (onePerLine) {
files.forEach(file => console.log(file));
} else {
console.log(files.join(' '));
}
});
}

dirPaths.forEach(dirPath => {
if (typeof dirPath !== 'string') {
console.error(`Error: Invalid directory path '${dirPath}'`);
return;
}
listDirectoryContents(dirPath, all, onePerLine);
});
20 changes: 20 additions & 0 deletions implement-shell-tools/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions implement-shell-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "commonjs",
"dependencies": {
"commander": "^11.0.0"
}
}

71 changes: 71 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const { promises: fs } = require('fs');
const path = require('path');
const { program } = require('commander');

program
.description('Count lines, words, and characters in the specified files')
.option('-l, --lines', 'Count the lines')
Copy link

@blorente blorente May 15, 2025

Choose a reason for hiding this comment

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

When counting lines, your program counts one more than wc:

➜  wc -l sample-files/3.txt
       5 sample-files/3.txt

➜ node wc.js -l sample-files/3.txt
6 /Users/blorente/code/github.com/CodeYourFuture/Module-Tools/implement-shell-tools/wc/sample-files/3.txt

Why does this happen? How could we fix it so that we print the right number of lines?

Copy link
Author

Choose a reason for hiding this comment

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

I was using data.split('\n').length, which overcounts lines. To match the behavior of wc -l, I updated it to:
const lines = (data.match(/\n/g) || []).length;

.option('-w, --words', 'Count the words')
.option('-c, --characters', 'Count the characters')
.parse(process.argv);

Choose a reason for hiding this comment

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

Your solution prints the full path of each file:

➜ node wc.js -l sample-files/3.txt
6 /Users/blorente/code/github.com/CodeYourFuture/Module-Tools/implement-shell-tools/wc/sample-files/3.txt

Whereas wc only prints the path relative to the directory we run it from:

➜  wc -l sample-files/3.txt
       5 sample-files/3.txt

How would you modify this code to print the relative path?

Copy link
Author

@Ameneh-Keshavarz Ameneh-Keshavarz May 25, 2025

Choose a reason for hiding this comment

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

I was printing the absolute path using path.resolve, which made the output differ from wc. I’ve updated the code to print the original file argument instead, so it now shows the relative path the we type.


const options = program.opts();
const files = program.args;

if (files.length === 0) {
console.error("Error: No files provided.");
process.exit(1);
}

async function countFileStats(filePath, options) {
try {
const data = await fs.readFile(filePath, 'utf8');

const lines = (data.match(/\n/g) || []).length;
const words = data.split(/\s+/).filter(Boolean).length;
const characters = data.length;

const result = { lines, words, characters };
const output = [];
if (options.lines) output.push(result.lines);
if (options.words) output.push(result.words);
if (options.characters) output.push(result.characters);
if (output.length === 0) output.push(result.lines, result.words, result.characters);

return { file: filePath, output: output.join(' '), lines, words, characters };
} catch (err) {
console.error(`Error reading file: ${filePath}`);
return null;
}
}

async function processFiles() {
let totalLines = 0;
let totalWords = 0;
let totalCharacters = 0;

for (const file of files) {
const filePath = path.resolve(file);
const result = await countFileStats(filePath, options);

if (result) {
console.log(result.output, file);
totalLines += result.lines;
totalWords += result.words;
totalCharacters += result.characters;
}
}

if (files.length > 1) {
const totals = [];
if (options.lines) totals.push(totalLines);
if (options.words) totals.push(totalWords);
if (options.characters !== false) totals.push(totalCharacters);

if (totals.length > 0) {
console.log(totals.join(' ') + ' total');
}
}
}

processFiles();