-
-
Notifications
You must be signed in to change notification settings - Fork 90
Sheffield | 2026-MAR-SDC | Jak Rhodes-Smith | Sprint 3 | Implement shell tools #366
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
base: main
Are you sure you want to change the base?
Changes from all commits
7045902
d373c0a
975035e
d4a4f43
9f1261f
4abcb92
c6a7649
fafe5fd
0071dc4
b9817fc
3f81d49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { program } from "commander"; | ||
| import * as fs from "node:fs/promises"; | ||
|
|
||
| program | ||
| .name("cat") | ||
| .description("Reads file(s) and writes them to the standard output") | ||
| .argument("<paths...>", "The file path(s) to process") | ||
| .option("-n", "Number the output lines, starting at 1.") | ||
| .option("-b", "Number only non-blank output lines, starting at 1."); | ||
|
|
||
| program.parse(); | ||
|
|
||
| try { | ||
| const filePaths = program.args; | ||
|
|
||
| const options = program.opts(); | ||
|
|
||
| for (const filePath of filePaths) { | ||
| const file = await fs.open(filePath); | ||
|
|
||
| let lineNum = 1; | ||
|
|
||
| try { | ||
| for await (const line of file.readLines()) { | ||
| const isBlank = line.trim() === ""; | ||
| const shouldNumber = options.n || (options.b && !isBlank); | ||
|
|
||
| console.log(shouldNumber ? `${lineNum} ${line}` : line); | ||
|
|
||
| if (shouldNumber) lineNum++; | ||
| } | ||
| } finally { | ||
| await file.close(); | ||
|
Comment on lines
+32
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use of finally to close the file after reading |
||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err.message); | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "type": "module", | ||
| "dependencies": { | ||
| "commander": "^14.0.3" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { program } from "commander"; | ||
| import * as fs from "node:fs/promises"; | ||
|
|
||
| program | ||
| .name("ls") | ||
| .description("List directory contents") | ||
| .argument( | ||
| "[paths...]", | ||
| "The file path to process (defaults to current directory)", | ||
| ) | ||
| .option("-a", "Include directory entries whose names begin with a dot ('.').") | ||
| .option("-1", "Force output to be one entry per line."); | ||
|
|
||
| program.parse(); | ||
|
|
||
| try { | ||
| let filePaths = program.args; | ||
| if (!filePaths || filePaths.length === 0) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would argue |
||
| filePaths = ["."]; | ||
| } | ||
|
|
||
| const options = program.opts(); | ||
| const includeHidden = Boolean(options.a); | ||
| const onePerLine = Boolean(options["1"]); | ||
|
|
||
| const result = { files: [], dirs: {} }; | ||
|
|
||
| for (const filePath of filePaths) { | ||
| const stats = await fs.stat(filePath); | ||
| if (stats.isFile()) result.files.push(filePath); | ||
| if (stats.isDirectory()) { | ||
| result.dirs[filePath] = await fs.readdir(filePath); | ||
| } | ||
| } | ||
|
|
||
| const filterHidden = (files) => files.filter((file) => !file.startsWith(".")); | ||
|
|
||
| const getVisibleEntries = (files) => | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally dislike having a function like It breaks the idea of "purity" (a function's output depends solely on arguments passed in or internal logic, no external dependencies - which |
||
| includeHidden ? files : filterHidden(files); | ||
|
|
||
| const formatEntries = (files) => { | ||
| if (files.length === 0) return; | ||
| console.log(files.join(onePerLine ? "\n" : "\t")); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the examples you've been asked to accommodate, there is always a newline option ( I agree the use of |
||
| }; | ||
|
|
||
| result.files = getVisibleEntries(result.files); | ||
|
|
||
| if (filePaths.length === 1) { | ||
| let entries = [...result.files]; | ||
|
|
||
| for (const [dir, contents] of Object.entries(result.dirs)) { | ||
| const filtered = getVisibleEntries(contents); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (based on previous comment) we can see here how |
||
| entries = entries.concat(filtered); | ||
| } | ||
| formatEntries(entries); | ||
| } else { | ||
| formatEntries(result.files); | ||
|
|
||
| for (const [dir, contents] of Object.entries(result.dirs)) { | ||
| console.log("\n" + dir + ":"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small bug here - you're baking into your output a newline on the first line of prints - not an expected behaviour, the first line should be at the top with no newline How can you separate out the concern so the newline gaps are between directory sections only and the top section doesn't have a newline before it? |
||
| const filtered = getVisibleEntries(contents); | ||
| formatEntries(filtered); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err.message); | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "type": "module", | ||
| "dependencies": { | ||
| "commander": "^14.0.3" | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "type": "module", | ||
| "dependencies": { | ||
| "commander": "^14.0.3" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { program } from "commander"; | ||
| import * as fs from "node:fs/promises"; | ||
|
|
||
| program | ||
| .name("wc") | ||
| .description("word, line and byte count") | ||
| .argument("<paths...>", "The file path(s) to process.") | ||
| .option( | ||
| "-l, --lines", | ||
| "The number of lines in each input file is written to the standard output.", | ||
| ) | ||
| .option( | ||
| "-w, --words", | ||
| "The number of words in each input file is written to the standard output.", | ||
| ) | ||
| .option( | ||
| "-c --bytes", | ||
| "The number of bytes in each input file is written to the standard output.", | ||
| ); | ||
|
|
||
| program.parse(); | ||
|
|
||
| try { | ||
| const filePaths = program.args; | ||
| const results = {}; | ||
|
|
||
| for (const filePath of filePaths) { | ||
| const file = await fs.open(filePath); | ||
| const stats = await fs.stat(filePath); | ||
| const count = { lines: 0, words: 0, bytes: stats.size }; | ||
|
|
||
| try { | ||
| for await (const line of file.readLines()) { | ||
| count.lines++; | ||
| const trimmed = line.trim(); | ||
| if (trimmed.length > 0) { | ||
| count.words += trimmed.split(/\s+/).length; | ||
| } | ||
| } | ||
| } finally { | ||
| await file.close(); | ||
| } | ||
| results[filePath] = count; | ||
| } | ||
|
|
||
| if (filePaths.length > 1) { | ||
| const total = { lines: 0, words: 0, bytes: 0 }; | ||
| for (const file of Object.values(results)) { | ||
| total.lines += file.lines; | ||
| total.words += file.words; | ||
| total.bytes += file.bytes; | ||
| } | ||
| results["total"] = total; | ||
| } | ||
|
|
||
| const options = program.opts(); | ||
| const noOptionsProvided = !Object.keys(options).length; | ||
| const selectedOptionKeys = [...Object.keys(options)]; | ||
|
|
||
| noOptionsProvided | ||
| ? console.table(results) | ||
| : console.table(results, selectedOptionKeys); | ||
| } catch (err) { | ||
| console.error(err.message); | ||
| } |
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.
To match the
catoutput accurately you would need to add the correct padding instead of just spaces. Here it's meant to be padding of 6 so the output is right-aligned.