generated from CodeYourFuture/Module-Template
-
-
Notifications
You must be signed in to change notification settings - Fork 42
Manchester | 25-SDC-Nov | Geraldine Edwards | Sprint 3 | Implement Shell Tools #242
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
Geraldine-Edwards
wants to merge
10
commits into
CodeYourFuture:main
Choose a base branch
from
Geraldine-Edwards:implement-shell-tools
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
83c381c
Implement 'cat' command alternative with line numbering options
Geraldine-Edwards 3707763
Implement 'ls' command alternative with directory listing options
Geraldine-Edwards a7bdd7a
Refactor line numbering logic in 'cat' command implementation for imp…
Geraldine-Edwards 4b1a8cd
Refactor sorting logic in 'ls' command to improve code clarity and or…
Geraldine-Edwards eace0f9
Implement 'wc' command to count lines, words, and bytes in files
Geraldine-Edwards 13fab23
Refactor 'cat', 'ls', and 'wc' commands for improved readability and …
Geraldine-Edwards adf7c2b
Refactor options in 'wc' command for clarity and consistency.
Geraldine-Edwards 7b1d67d
Add .gitignore file to exclude node_modules, log files, and environme…
Geraldine-Edwards fafae17
Improve comments and enhance output formatting in 'ls' command
Geraldine-Edwards 4902edc
Refactor command options in program args for clarity and update optio…
Geraldine-Edwards File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| node_modules/ | ||
| *.log | ||
| .DS_Store | ||
| .env |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { program } from "commander"; | ||
| import { promises as fs } from "node:fs"; | ||
| import process from "node:process"; | ||
|
|
||
| // configure the CLI program with its name, description, arguments, options, and actions (the help instructions) | ||
| program | ||
| .name("cat") | ||
| .description("An alternative to the 'cat' command") | ||
| .argument("<files...>", "The file(s) to process") | ||
| .option("-n, --number", "Number all output lines") | ||
| .option("-b, --number-nonblank", "Number non-blank output lines") | ||
| .action(async (files, options) => { | ||
| try { | ||
| await newCat(files, options); | ||
| } catch (err) { | ||
| console.error(`Error: ${err.message}`); | ||
| } | ||
| }); | ||
|
|
||
| program.parse(process.argv); | ||
|
|
||
| //helper function to format output | ||
| function printLine(line, lineNumber, padWidth) { | ||
| if (lineNumber !== null) { | ||
| console.log(`${lineNumber.toString().padStart(padWidth, ' ')} ${line}`); | ||
| } else { | ||
| console.log(line); | ||
| } | ||
| } | ||
|
|
||
| async function newCat(files, options) { | ||
| let lineNumber = 1; | ||
| const padWidth = 6; | ||
|
|
||
| for (const file of files) { | ||
| // read each file into a single text string | ||
| try { | ||
| const data = await fs.readFile(file, "utf8"); | ||
| // split that string into an array at \n where each element is a line from the file | ||
| // e.g. lines = ["Line 1", "Line 2", "Line 3"] | ||
| const lines = data.split("\n") | ||
|
|
||
| // remove trailing blank line caused by a trailing newline | ||
| if (lines[lines.length - 1] === "") { | ||
| lines.pop(); | ||
| } | ||
|
|
||
| lines.forEach(line => { | ||
| //line trim: truthy = text, falsy = blank | ||
| if (options.numberNonblank && line.trim()) { | ||
| // number non-blank lines only | ||
| printLine(line, lineNumber++, padWidth); | ||
| } else if (options.number){ | ||
| // number all lines | ||
| printLine(line, lineNumber++, padWidth); | ||
| } else { | ||
| // neither flag, print normally | ||
| printLine(line, null, padWidth) | ||
| } | ||
| }); | ||
| } catch (err) { | ||
| console.error(`Error reading file ${file}: ${err.message}`); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import { program } from "commander"; | ||
| import { promises as fs } from "node:fs"; | ||
| import process from "node:process"; | ||
|
|
||
| // configure the CLI program with its name, description, arguments, options, and actions (the help instructions) | ||
| program | ||
| .name("ls") | ||
| .description("An alternative to the 'ls' command") | ||
| .argument("[directory]", "The directory to list") | ||
| // Commander stores -1 as a string key that is accessed using options['1'] | ||
| .option("-1", "List all files, one per line") | ||
| .option("-a, --all", "Include hidden files (those starting with .) in the listing") | ||
| .action(async (directory, options) => { | ||
| try { | ||
| // default to current directory if none is specified | ||
| const dir = directory || "."; | ||
|
|
||
| await newLs(dir, options['1'], options.all); | ||
| } catch (err) { | ||
| console.error(`Error: ${err.message}`); | ||
| } | ||
| }); | ||
|
|
||
| program.parse(process.argv); | ||
|
|
||
|
|
||
| // filter files based on visibility (includeHidden = true includes all files) | ||
| function filterFiles(entries, includeHidden) { | ||
| return entries.filter(name => | ||
| includeHidden ? true : !name.startsWith(".") | ||
| ); | ||
| } | ||
|
|
||
| // sort entries: directories first, then files, | ||
| function sortEntries(entries) { | ||
| const dirs = entries.filter(entry => { | ||
| try { | ||
| return fs.statSync(entry).isDirectory(); | ||
| } catch (err) { | ||
| return false; | ||
| } | ||
| }); | ||
|
|
||
| const files = entries.filter(entry => { | ||
| try { | ||
| return fs.statSync(entry).isFile(); | ||
| } catch (err) { | ||
| return false; | ||
| } | ||
| }); | ||
| // localeCompare = take into account rules of system language/region for ordering | ||
| // undefined = uses the system default, numeric = regular number sorting, base = ignore case & accents | ||
| return entries.sort((a, b) => | ||
| a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }) | ||
| ); | ||
| } | ||
|
|
||
|
|
||
| // print each entry on its own line (used for -1 flag) | ||
| function printEntries(entries) { | ||
| entries.forEach(entry => console.log(entry)); | ||
| } | ||
|
|
||
|
|
||
| async function newLs(directory, oneFlag, allFlag) { | ||
| try { | ||
| // check if path exists and determine if file or directory | ||
| const stats = await fs.stat(directory); | ||
|
|
||
| // if a file, just print the name | ||
| if (stats.isFile()) { | ||
| console.log(directory); | ||
| return; | ||
| } | ||
|
|
||
| // reads directory contents | ||
| const entries = await fs.readdir(directory); | ||
|
|
||
| // filter out hidden files if no -a flag | ||
| const filteredEntries = filterFiles(entries, allFlag); | ||
|
|
||
| // sort the entries using the sortEntries helper | ||
| const sortedEntries = sortEntries(filteredEntries); | ||
|
|
||
| // print entries based on -1 flag | ||
| if (oneFlag) { | ||
| printEntries(sortedEntries); // one per line | ||
| } else { | ||
| console.log(sortedEntries.join(" ")); // all on one line, separated by spaces | ||
| } | ||
| } catch (err) { | ||
| console.error(`ls: cannot access '${directory}': ${err.message}`); | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "name": "implement-shell-tools", | ||
| "version": "1.0.0", | ||
| "type": "module", | ||
| "dependencies": { | ||
| "commander": "^14.0.2" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import { program } from "commander"; | ||
| import { promises as fs } from "node:fs"; | ||
| import process from "node:process"; | ||
|
|
||
| // configure the CLI program with its name, description, arguments, options, and actions (the help instructions) | ||
| program | ||
| .name("wc") | ||
| .description("An alternative to the 'wc' command") | ||
| .argument("<files...>", "The file(s) to count lines/words/bytes") | ||
| .option("-l, --lines", "Print the newline counts") | ||
| .option("-w, --words", "Print the word counts") | ||
| .option("-c, --bytes", "Print the byte counts") | ||
| .action(async (files, options) => { | ||
| try { | ||
| // call newWc for all files | ||
| await newWc(files, options) | ||
| } catch (err) { | ||
| console.error(`Error: ${err.message}`); | ||
| } | ||
| }); | ||
|
|
||
| program.parse(process.argv); | ||
|
|
||
| //helper function to format string for output | ||
| function formatCount(count) { | ||
| const paddingStart = 3 | ||
| return count.toString().padStart(paddingStart); | ||
| } | ||
|
|
||
|
|
||
| // helper function to print the wc outputs per case | ||
| function printWcOutput(lineCount, wordCount, byteCount, file, options, noFlags) { | ||
| const parts = []; | ||
|
|
||
| if (noFlags || options.lines) parts.push(formatCount(lineCount)); | ||
| if (noFlags || options.words) parts.push(formatCount(wordCount)); | ||
| if (noFlags || options.bytes) parts.push(formatCount(byteCount)); | ||
|
|
||
| parts.push(file); | ||
| console.log(parts.join(" ")); | ||
| } | ||
|
|
||
| async function newWc(files, options) { | ||
|
|
||
| const noFlags = | ||
| !options.lines && | ||
| !options.words && | ||
| !options.bytes; | ||
|
|
||
| // set the counts variables | ||
| let totalLines = 0; | ||
| let totalWords = 0; | ||
| let totalBytes = 0; | ||
|
|
||
| for (const file of files) { | ||
| try { | ||
| // read each file into a single text string | ||
| const content = await fs.readFile(file, "utf8"); | ||
|
|
||
| // count lines by splitting on '\n' and subtracting 1 because | ||
| // each newline creates an extra array element, so length-1 equals the number of newline characters | ||
| const lineCount = content.split("\n").length - 1; | ||
|
|
||
| // .filter(Boolean) ensures that falsy values like "" (empty string), null, undefined, 0, false are removed | ||
| const wordCount = content.split(/\s+/).filter(Boolean).length; | ||
|
|
||
| // calculates the number of bytes the file uses when encoded as UTF-8. | ||
| // different than just counting chars as some chars (like emojis, accented letters, etc) take more than 1 byte | ||
| const byteCount = Buffer.byteLength(content, "utf8"); | ||
|
|
||
| // update the count | ||
| totalLines += lineCount; | ||
| totalWords += wordCount; | ||
| totalBytes += byteCount; | ||
|
|
||
| printWcOutput(lineCount, wordCount, byteCount, file, options, noFlags); | ||
| } catch (err) { | ||
| console.error(`Error reading file ${file}: ${err.message}`); | ||
| } | ||
| } | ||
| if (files.length > 1) { | ||
| // print the totals as wc does | ||
| printWcOutput(totalLines, totalWords, totalBytes, "total", options, noFlags); | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should happen if I give only a single option, e.g.
node wc.js sample-files/1.txt -c? Does this print correctly?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again @LonMcGregor :) I have spotted my error in the program options configuration and changed the remainder of the file as needed to the long flag names.