London| Elhadj Abdoul Diallo| Module-Tools | WEEK3 - Implement-shell-tools#25
London| Elhadj Abdoul Diallo| Module-Tools | WEEK3 - Implement-shell-tools#25eediallo wants to merge 64 commits into
Conversation
…ng and file path arguments
…t and improve line numbering logic
…ed readability and maintainability
…idden files in ls command
…nd character counts for each
…dling and streamline output logic
ehwus
left a comment
There was a problem hiding this comment.
cat
You've produced a readable and working replica of the cat shell tool here, and it shows a solid understanding of node fundamentals and using commander to build scripts. There are a few tweaks that we could make to get this to be as efficient as cat is, and I would focus on:
- Learning about operating on files in a memory efficient manner
- Why and when to await the response of promises
- JavaScript array iteration - which to use when and why
Overall though this is a really solid effort and it was great to see you reach such parity with the cat tool with your own code.
ls
Once again this is a good implementation of ls and the code is clear and readable. There are a few flags for efficiency and clarity, but overall a very good effort.
wc
Once again this largely replicates wc well barring a few little logic tweaks that I have raised, but parts of the code feel complicated to follow and we have also fallen into some of the previously discussed patterns surrounding reading from the filesystem. It feels like it doesn't have the readability of the other two scripts, so I'm curious what the difference was when you wrote it - did you find it a harder problem to solve?
Overall
This is a great demonstration of your knowledge of shell toolings and how to replicate this functionality well in Node, which you were able to do successfully. Keep tradeoffs in mind, particularly when working with the file system, and make sure that you have a good understanding of when to use each array function and when it's useful to await promises.
| .argument("<paths...>", "The file paths to process") | ||
| .parse(process.argv); | ||
|
|
||
| let args = program.args; |
There was a problem hiding this comment.
nitpick: Are we ever reassigning this let?
There was a problem hiding this comment.
we are not. It makes sense to use the const keyword here.
| .parse(process.argv); | ||
|
|
||
| let args = program.args; | ||
| //console.log(args) |
There was a problem hiding this comment.
nitpick: It's good practice to leave these debugging comments out of code, but good to see where you were checking your inputs as you went along
There was a problem hiding this comment.
ok. going to remove it. Thanks
| let args = program.args; | ||
| //console.log(args) | ||
|
|
||
| const nOption = program.opts().number; |
There was a problem hiding this comment.
suggestion: I appreciate here that cat uses these single letters to denote functionality, but you could make your own life easier here and name them something that's more clear for your own code, i.e. displayLineNumber
There was a problem hiding this comment.
will do. Thanks. I was actually struggling to find meaning names for these options.
There was a problem hiding this comment.
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
|
|
||
| async function readAndPrintFileContent(path) { | ||
| try { | ||
| const content = await fs.readFile(path, { encoding: "utf-8" }); |
There was a problem hiding this comment.
question: What does this do to the file from the filesystem? Could we do this more memory efficiently?
There was a problem hiding this comment.
It's reading the content of the file(path) provided through the CLI asyncronously which means it won't prevent other tasks from executing. Could we do this more memory efficiently? I am not 100% sure but I know if I was reading a large file, I would have used Stream to read it in chuncks instead of loading it once.
There was a problem hiding this comment.
You're along the right lines here, text files might be absolutely massive (e.g. logs) so making sure that our programs operate on them efficiently is really important, especially as we're processing line by line anyway
| } | ||
|
|
||
| function extractLinesFromContent(content) { | ||
| const lines = content.split("\n"); |
There was a problem hiding this comment.
question: Why could this line of code make the situation that we've introduced on line 22 worse?
There was a problem hiding this comment.
I am not sure. Could you please advice what would be the best practice here?
There was a problem hiding this comment.
If we've loaded the entire file into memory on line 22, what are we doing here?
There was a problem hiding this comment.
We're copying the entirety of the file into memory again in an array, so we've doubled our problem
There was a problem hiding this comment.
OK. I see. Googling to see how to avoid this.
There was a problem hiding this comment.
I have refactored it by removing the extractLinesContent function and extracting lines directly in readAndPrintFileContent function. Would this solve the problem?
| * @returns {number[]|number} - An array of sums for each column of numbers if there are multiple files, | ||
| * or 0 if there is only one file. | ||
| */ | ||
| function aggregateFileData(fileCounts) { |
There was a problem hiding this comment.
question: Please could you talk me through this function line by line?
There was a problem hiding this comment.
Sure the aggregatedFilesData is an array with three possible values: sum of lines, chars or words. The following code:
if (aggregatedFilesData !== 0 && lineOption && !(wordOption || charOption)) {
console.log(`${aggregatedFilesData[0]} total`);
return;
}whether aggregatedFilesData is not 0 and line option is provided but not the rest of the options(chars and words).if it is true, it means only sum of lines is available on aggregatedFilesData array therefore the sum is displayed with total in front of it.
it is the same for chars option in the following code:
if (aggregatedFilesData !== 0 && charOption && !(wordOption || lineOption)) {
console.log(`${aggregatedFilesData[0]} total`);
return;
}Here:
if (
aggregatedFilesData !== 0 &&
((lineOption && charOption) || (lineOption && wordOption))
) {
console.log(`${aggregatedFilesData[0]} ${aggregatedFilesData[1]} total`);
return;
}This code checks if two options are provided, it could be line option and char option or line and word option. if it is true, I know the aggregatedFilesData array contains two values which are then displayed alongside the total string.
The return keyword in each statement ensure that the execution does not continue.
if the bove conditions are false: the sum of each option is display if aggregatedFilesData is not 0, hence the code below: aggregatedFilesData !== 0
? console.log(
${aggregatedFilesData[0]} ${aggregatedFilesData[1]} ${aggregatedFilesData[2]} total
)
: null;
| const wordOption = program.opts().word; | ||
|
|
||
| async function countLinesWordsCharsInFile(path) { | ||
| const content = await fs.readFile(path, { encoding: "utf-8" }); |
There was a problem hiding this comment.
question: What are the tradeoffs of reading files in this way?
There was a problem hiding this comment.
if the file was large, that would caused high memory consumption and possible out of memory errors.
| async function countLinesWordsCharsInFile(path) { | ||
| const content = await fs.readFile(path, { encoding: "utf-8" }); | ||
|
|
||
| const lines = removeEndEmptyLine(content.split("\n")); |
There was a problem hiding this comment.
question: These are clever ways of getting the counts, but do we always need to do all of this work?
There was a problem hiding this comment.
maybe not. may I could have write the code instead of creating a separate function for it.
There was a problem hiding this comment.
I have refactore this code too to avoid copying the entire array inot memory again.
| const numberOfWords = words.length; | ||
| const numberOFChars = chars.length; | ||
|
|
||
| if (lineOption) { |
There was a problem hiding this comment.
Right. I've not implemented this one. I am going to add the functionality
| } | ||
|
|
||
| async function processFilesAndDisplayCounts() { | ||
| const fileCounts = await Promise.all(args.map(countLinesWordsCharsInFile)); |
There was a problem hiding this comment.
praise: awaiting and using Promise.all with a map here makes a lot of sense and makes sure we can process as many files as are needed, great stuff
… function and handling line extraction directly in readAndPrintFileContent
…on when line and character options are not selected
… improved clarity and remove unnecessary whitespace

Learners, PR Template
Self checklist