London | 26-SDC-Mar | Ebrahim Beiati-Asl | Sprint 3 | implements-shell-tool#376
London | 26-SDC-Mar | Ebrahim Beiati-Asl | Sprint 3 | implements-shell-tool#376ebrahimbeiati wants to merge 4 commits intoCodeYourFuture:mainfrom
Conversation
This comment has been minimized.
This comment has been minimized.
illicitonion
left a comment
There was a problem hiding this comment.
This generally looks good, but I left a few comments to consider. Well done!
implement-shell-tools/cat/cat.js
Outdated
| if(arg === '-n') { | ||
| options.numberAll = true; | ||
| } else if(arg === '-b') { | ||
| options.numberNonEmpty = true; | ||
| } else { | ||
| filePatterns.push(arg); | ||
| } | ||
| }); | ||
| // -b takes precedence over -n | ||
| if(options.numberNonEmpty) { | ||
| options.numberAll = false; | ||
| } |
There was a problem hiding this comment.
Rather than having two different options which are mutually exclusive, I would probably model this as one option with three possible values: options.numberMode = "off" | "all" | "non-empty". That way you don't need to re-set numberAll if actually numberNonEmpty - you just have one variable you set correctly up-front and which you can check.
implement-shell-tools/cat/cat.js
Outdated
| process.exit(1); | ||
| } | ||
|
|
||
| const files = filePatterns; |
There was a problem hiding this comment.
Why did you call this filePatterns just to rename it to files later? Why not just name it files or fileNames from the start?
implement-shell-tools/cat/cat.js
Outdated
| if(filePatterns.length === 0) { | ||
| console.log("cat: missing file operand"); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const files = filePatterns; | ||
|
|
||
| files.forEach((file) => { | ||
| const resolvedPath = path.resolve(process.cwd(), file); |
There was a problem hiding this comment.
What advantage does path.resolve(process.cwd(), file) have over just using file directly?
implement-shell-tools/ls/ls.js
Outdated
| const names = filtered.map(entry => entry.name); | ||
| console.log(names.join(' ')); | ||
| } | ||
| } catch (error) { |
There was a problem hiding this comment.
This indentation isn't correct - please format all of your code before submitting for review. I'd recommend setting up your IDE to auto-format your code on save to avoid needing to remember to do this.
implement-shell-tools/wc/wc.js
Outdated
| if(options.word) parts.push(counts.words); | ||
| if(options.byte) parts.push(counts.bytes); | ||
| //if no specific count options are provided, print all counts | ||
| if(!options.line && !options.word && !options.byte) { |
There was a problem hiding this comment.
It may be simpler to do this check when setting up options, rather than when consuming it - i.e. in your options parsing to see "if no options were set, set all to true" - that way the rest of your code doesn't need to think about this special case, they can just handle wc foo and wc -l -w -c foo exactly the same.
(This about this as an abstraction boundary - the point of parsing command line flags into an options object is so that the rest of your code doesn't need to worry about the specifics of flags, this is just one more example of a place you can avoid your code needing to know the specifics of flags)
implement-shell-tools/ls/ls.js
Outdated
| const directories = args.filter(arg => arg !== '-a' && arg !== '-1'); | ||
|
|
||
| // If no directory is specified, list the current directory | ||
| if(directories.length === 0) { |
There was a problem hiding this comment.
Can you think how to fold the "no directories" case into the main loop so you don't have to have two separate calls to listDirectory in this function?
implement-shell-tools/cat/cat.js
Outdated
| lines.forEach((line) => { | ||
| if(options.numberNonEmpty) { | ||
| //-b option: number non-empty lines | ||
| if(line.trim()) { | ||
| process.stdout.write( | ||
| `${String(globalLineCounter).padStart(6)}\t${line}\n` | ||
| ); | ||
| globalLineCounter++; | ||
| } else { | ||
| process.stdout.write('\n'); | ||
| } | ||
| } else if(options.numberAll) { | ||
| //-n option: number all lines | ||
| process.stdout.write( | ||
| `${String(globalLineCounter).padStart(6)}\t${line}\n` | ||
| ); | ||
| globalLineCounter++; | ||
| } else { | ||
| //default: just print the line | ||
| process.stdout.write(line + '\n'); | ||
| } | ||
| }); |
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 process.stdout.write which looks more like process.stdout.write(`${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.
illicitonion
left a comment
There was a problem hiding this comment.
Looking really good, great improvements! A few more small things to think about :)
implement-shell-tools/cat/cat.js
Outdated
| let prefix = ""; | ||
|
|
||
| if (options.numberMode === "non-empty") { | ||
| //-b option: number non-empty lines |
There was a problem hiding this comment.
This comment feels redundant - reading the if condition tells me the same information as the comment does
implement-shell-tools/cat/cat.js
Outdated
| if (options.numberMode === "non-empty") { | ||
| //-b option: number non-empty lines | ||
| if (line.trim() !== "") { | ||
| prefix = `${String(globalLineCounter).padStart(6)}\t`; | ||
| globalLineCounter++; | ||
| } | ||
| } else if (options.numberMode === "all") { | ||
| prefix = `${String(globalLineCounter).padStart(6)}\t`; | ||
| globalLineCounter++; |
There was a problem hiding this comment.
There's still some repetition here in setting up the prefix. How about something like:
if (options.numberMode === "all" || (options.numberMode === "non-empty" && line.trim() !== "") {| filtered.forEach((entry) => console.log(entry.name)); | ||
| } else { | ||
| const names = filtered.map((entry) => entry.name); | ||
| console.log(names.join(" ")); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
| console.log(names.join(" ")); | ||
| } | ||
| } catch (error) { | ||
| console.error(`Error reading directory ${dirPath}: ${error.message}`); |
There was a problem hiding this comment.
What exit code should our process terminate with if it hit an error? What does your code do?
implement-shell-tools/ls/ls.js
Outdated
| if (directories.length === 0) { | ||
| directories = [process.cwd()]; | ||
| } | ||
| //If a directory is specified, list that directory |
There was a problem hiding this comment.
This comment seems a bit confusing? I'd remove it
implement-shell-tools/ls/ls.js
Outdated
| if (directories.length > 1 && index < directories.length - 1) | ||
| console.log(""); |
There was a problem hiding this comment.
I'd recommend always using {}s around if conditions, or perhaps sometimes allowing single-line statements (like your above if (directories.length > 1) console.log(`${arg}:`);) - see https://www.blackduck.com/blog/understanding-apple-goto-fail-vulnerability-2.html for an example of why
|
|
||
| printCounts(file, counts, options); | ||
| } catch (error) { | ||
| console.error(`Error reading file ${file}: ${error.message}`); |
There was a problem hiding this comment.
Same question about exit codes
illicitonion
left a comment
There was a problem hiding this comment.
Looks good! Just a few last things :)
| process.stdout.write(`${prefix}${line}\n`); | ||
| }); | ||
| } catch (error) { | ||
| console.error(`cat: ${filePath}: ${error.message}`); |
There was a problem hiding this comment.
This will still exit 0 if e.g. a file didn't exist.
| const stats = fs.statSync(arg); | ||
|
|
||
| if (stats.isDirectory()) { | ||
| //Print header if multiple directories are listed |
There was a problem hiding this comment.
Is this comment useful compared to just reading the code?
| //remove options from args | ||
| let directories = args.filter((arg) => arg !== "-a" && arg !== "-1"); | ||
|
|
||
| // Default to current directory if no directories are specified |
There was a problem hiding this comment.
Is this comment useful compared to just reading the code?
| } | ||
| } | ||
|
|
||
| function main() { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Your wc does too for the same reason.
There was a problem hiding this comment.
Thank you so much for that. I'll write an integration test to find the issue.
Learners, PR Template
Self checklist
Changelist
Exercise for implement-shell-tools