Birmingham | 2026-MAR-SDC | Joy Opachavalit | Sprint 3 | Implement shell tools#408
Birmingham | 2026-MAR-SDC | Joy Opachavalit | Sprint 3 | Implement shell tools#408enjoy15 wants to merge 7 commits intoCodeYourFuture:mainfrom
Conversation
illicitonion
left a comment
There was a problem hiding this comment.
Generally looking good, but it looks like you're slightly over-fitting to the examples in the README.md - give some thought to other command lines a user may try to run, and how your implementations may be surprising to them.
| let lineNumber = 1; | ||
|
|
||
| files.forEach((file) => { | ||
| const filePath = path.resolve(file); |
There was a problem hiding this comment.
What is this line doing? What would break if you removed it and just used file instead of filePath?
There was a problem hiding this comment.
I have removed the unnecessary use of path.resolve and now directly use file. The code works as expected without it, simplifying the implementation.
| if (options.numberNonEmpty && line.trim()) { | ||
| console.log(`${lineNumber}\t${line}`); | ||
| lineNumber++; | ||
| } else if (options.numberLines) { | ||
| console.log(`${lineNumber}\t${line}`); | ||
| lineNumber++; | ||
| } else { | ||
| console.log(line); |
There was a problem hiding this comment.
The three branches here look quite similar and repetitive. In general, if you have multiple similar branches, it's more clear to extract the differences into variables, and then run the same code, i.e. so you'd only have one call to console.log which looks more like console.log(`${prefix}${line}\n`) where prefix may be set differently based on options (including potentially an empty string).
This way it's easier for someone reading the code to see what's the same / different in each case, and also avoids the hazard that someone updates one of the branches but forgets to update the other ones.
There was a problem hiding this comment.
I have refactored the code to reduce repetition. Now, the differences are handled using a prefix variable, and there is only one console.log statement. This makes the code cleaner and easier to maintain.
| } | ||
| }); | ||
| } catch (err) { | ||
| console.error(`cat: ${file}: No such file or directory`); |
There was a problem hiding this comment.
What exit code will your program have if something went wrong? What exit code should it have?
There was a problem hiding this comment.
I have added proper exit codes. The program now exits with process.exit(1) when an error occurs, ensuring it signals failure appropriately.
| } | ||
| }); | ||
| } catch (err) { | ||
| console.error(`cat: ${file}: No such file or directory`); |
There was a problem hiding this comment.
Are you sure the problem here is that the file didn't exist? What would happen e.g. if you didn't have permission to read the file?
In general specific error messages are good, but misleadingly specific error messages are a problem - if we're not sure what went wrong (or if we have more information about what went wrong), we should present that information, rather than guessing. And if we are guessing, we should make it clear we're not sure what the problem exactly was and that this is a guess.
There was a problem hiding this comment.
I have improved the error handling to differentiate between file-not-found (ENOENT) and permission errors (EACCES). The error messages now provide more accurate information about what went wrong.
| if (arg === '-l') { | ||
| options.lines = true; | ||
| } else if (arg === '-w') { | ||
| options.words = true; | ||
| } else if (arg === '-c') { | ||
| options.bytes = true; | ||
| } else { | ||
| files.push(arg); | ||
| } |
There was a problem hiding this comment.
What happens if someone specifies more than one of these flags? What should happen?
Given the test cases we gave you in the README file, it's ok if your implementation doesn't do the same thing as the real wc does, though that would be ideal, but in general ignoring user input is bad - so if someone asks for both -l and -c and you ignore one of them, that can be confusing. Either showing both, or giving an error, is probably preferable.
There was a problem hiding this comment.
I have updated the code to handle multiple flags. If multiple flags are specified, the program now displays all the requested results together, ensuring user input is respected.
There was a problem hiding this comment.
This fixed some problems, but introduced some new ones too :)
What happens if you wc /some/file with no flags? What does your program do?
Also, what happens if you wc -l /some/file /some/other/file? What does your program do? What's different between them?
There was a problem hiding this comment.
Thank you for pointing this out! Here's how the program currently behaves:
If no flags are provided (e.g., wc /some/file), the program defaults to displaying all metrics (lines, words, and bytes) for the file. This behavior is consistent with the standard wc command.
If multiple files are provided with a single flag (e.g., wc -l /some/file /some/other/file), the program outputs the line count for each file on separate lines. However, it does not provide a total count across all files, which I’ve made the updates to align with the standard wc behavior.
| } | ||
|
|
||
| files.forEach((file) => { | ||
| const filePath = path.resolve(file); |
There was a problem hiding this comment.
Again, why do you need the path.resolve here?
There was a problem hiding this comment.
I have removed the unnecessary use of path.resolve and now directly use the file path provided by the user. This simplifies the code without affecting functionality.
| } else if (arg === '-a') { | ||
| options.all = true; | ||
| } else { | ||
| directories = [arg]; |
There was a problem hiding this comment.
This meets the requirements for the examples listed in the README.md, but feels like it risks being confusing to users. If someone specified ls /some/path /some/other/path what do you think they would expect to happen? What does your code actually do? (See also the comment about wc options for a similar question)
There was a problem hiding this comment.
I have updated the code to handle multiple directories. The program now lists files for all specified directories, making it more intuitive for users.
illicitonion
left a comment
There was a problem hiding this comment.
This is looking much better! Just a couple of things to look at still :)
| const lines = data.split('\n'); | ||
|
|
||
| lines.forEach((line) => { | ||
| const prefix = options.numberNonEmpty && line.trim() ? `${lineNumber}\t` : options.numberLines ? `${lineNumber}\t` : ''; |
There was a problem hiding this comment.
Nested ternary operators are generally quite hard to read (and also, two of the conditions here lead to the same result, which is duplication) - I'd recommend rewriting this without a ternary.
There was a problem hiding this comment.
Thank you for pointing this out! I have refactored the code to replace the nested ternary operator with a more readable if-else structure. This change eliminates the duplication and improves clarity by explicitly handling the conditions for setting the prefix.
| if (arg === '-l') { | ||
| options.lines = true; | ||
| } else if (arg === '-w') { | ||
| options.words = true; | ||
| } else if (arg === '-c') { | ||
| options.bytes = true; | ||
| } else { | ||
| files.push(arg); | ||
| } |
There was a problem hiding this comment.
This fixed some problems, but introduced some new ones too :)
What happens if you wc /some/file with no flags? What does your program do?
Also, what happens if you wc -l /some/file /some/other/file? What does your program do? What's different between them?
| if (options.numberNonEmpty && line.trim()) { | ||
| prefix = `${lineNumber}\t`; | ||
| } else if (options.numberLines) { | ||
| prefix = `${lineNumber}\t`; | ||
| } | ||
| console.log(`${prefix}${line}`); | ||
| if (prefix) lineNumber++; |
There was a problem hiding this comment.
This works and is clear to read, but I may consider rewriting it as something like:
| if (options.numberNonEmpty && line.trim()) { | |
| prefix = `${lineNumber}\t`; | |
| } else if (options.numberLines) { | |
| prefix = `${lineNumber}\t`; | |
| } | |
| console.log(`${prefix}${line}`); | |
| if (prefix) lineNumber++; | |
| const shouldNumberLine = options.numberLines || (options.numberNonEmpty && line.trim()); | |
| if (shouldNumberLine) { | |
| prefix = `${lineNumber}\t`; | |
| lineNumber++; | |
| } | |
| console.log(`${prefix}${line}`); |
Two reasons for this:
- It shows that both of the "should number line" cases are handled the same.
- The "should you increment the line count" is really based on "whether you should number the line", not "whether there's a prefix". I can imagine in the future we may add prefixes for different reasons not just line numbering, at which point
if (prefix) lineNumber++;would now be incorrect. We generally want our conditions to be based on the underlying reason something should be done, not something else we decided to do for the same reason.
There was a problem hiding this comment.
I’ve applied this refactor now. I introduced a shouldNumberLine condition and based both prefix assignment and lineNumber increment on that condition, so the logic is clearer and tied to the underlying behavior rather than checking prefix.
| const bytes = Buffer.byteLength(data, 'utf8'); | ||
|
|
||
| const results = []; | ||
| if (options.lines || (!options.lines && !options.words && !options.bytes)) results.push(lines); |
There was a problem hiding this comment.
This works but it doesn't read very well and is quite brittle - imagine if in the future you added a new option, you'd need to update all of these places where you're checking if no other options are specified.
Right now your options.lines perfectly mirrors the exact flags passed. But this doesn't have to be the case. Flags are just ways of configuring our program, but our program doesn't have to be tightly tied to the flags it exposes to users. Try making this line just be if (options.lines) { and see if you can initialise options.lines when parsing the flags to always have the correct answer for "should I show lines".
There was a problem hiding this comment.
Thanks — updated now. I simplified flag handling as suggested, fixed -l counting logic, and aligned output formatting. Verified against all README wc commands.
Learners, PR Template
Self checklist
Changelist
Completed all exercises