-
-
Notifications
You must be signed in to change notification settings - Fork 92
London| 2026-MAR-SDC | Imran Mohamed | Sprint 3 | Implement shell tools #450
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
2552f7f
9f574ca
0d1f8b1
e226c94
b75b9f7
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,49 @@ | ||
| import {program} from 'commander'; | ||
| import {promises as fs} from 'node:fs'; | ||
| import process from 'node:process'; | ||
|
|
||
| program | ||
| .name('cat') | ||
| .description('Concatenates and prints the contents of files.') | ||
| .argument('<paths...>', 'The paths to the files to concatenate') | ||
| .option('-n, --number', 'Number all output lines') | ||
| .option('-b, --number-nonblank', 'Number nonempty output lines'); | ||
|
|
||
| program.parse(); | ||
|
|
||
| const argv = program.args; | ||
| const options = program.opts(); | ||
|
|
||
| if (argv.length < 1) { | ||
| console.error( | ||
| `Expected at least 1 argument (a path) to be passed but got ${argv.length}.`, | ||
| ); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| let showLineNumbers = options.number || false; | ||
| let showNonEmptyLineNumbers = options.numberNonblank || false; | ||
|
|
||
| if (showLineNumbers && showNonEmptyLineNumbers) { | ||
| showLineNumbers = false; | ||
| } | ||
|
|
||
| let lineNumber = 1; | ||
| const spacer = ' '; | ||
|
|
||
| for (const path of argv) { | ||
| const content = await fs.readFile(path, 'utf-8'); | ||
| const lines = content.split('\n'); | ||
| while (lines.length > 0 && lines[lines.length - 1] === '') { | ||
|
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. while pops all trailing blank lines, not just one. A file intentionally ending with multiple blank lines will have them silently removed. |
||
| lines.pop(); | ||
| } | ||
| for (const line of lines) { | ||
| if (showLineNumbers) { | ||
| process.stdout.write(`${spacer} ${lineNumber++} ${line}\n`); | ||
|
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. real cat places tab between numbers and line, and also aligns line number to the length of the number. |
||
| } else if (showNonEmptyLineNumbers && line.trim() !== '') { | ||
|
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. Real cat -b considers only truly empty lines (zero characters) as blank, not whitespace-only lines. |
||
| process.stdout.write(`${spacer} ${lineNumber++} ${line}\n`); | ||
| } else { | ||
| process.stdout.write(`${line}\n`); | ||
| } | ||
| } | ||
| } | ||
|
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. Missing newline at end of file. |
||
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,15 @@ | ||
| { | ||
| "name": "cat", | ||
| "version": "1.0.0", | ||
| "description": "You should already be familiar with the `cat` command line tool.", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "test": "echo \"Error: no test specified\" && exit 1" | ||
| }, | ||
| "keywords": [], | ||
| "author": "", | ||
| "license": "ISC", | ||
| "dependencies": { | ||
| "commander": "^14.0.3" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { program } from 'commander'; | ||
| import process from 'node:process'; | ||
| import { promises as fs } from 'node:fs'; | ||
|
|
||
| program | ||
| .name('ls') | ||
| .description('Lists the contents of a directory.') | ||
| .argument('[path]', 'The path to the directory to list, defaults to the current directory') | ||
| .option('-a, --all', 'Do not ignore entries starting with .') | ||
| .option('-1', 'List one file per line'); | ||
|
|
||
| program.parse(); | ||
|
|
||
| const argv = program.args; | ||
| const path = argv[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. Real ls can take multiple paths |
||
|
|
||
| let showAll = program.opts().all || false; | ||
| let onePerLine = program.opts()['1'] || false; | ||
|
|
||
| try { | ||
| const files = await fs.readdir(path); | ||
| files.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })); | ||
|
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. Sort is case-insensitive (sensitivity: 'base'). Real ls is case-sensitive by default (uppercase before lowercase). |
||
| for (const file of files) { | ||
| if (showAll || !file.startsWith('.')) { | ||
| if (onePerLine) { | ||
| process.stdout.write(`${file}\n`); | ||
| } else { | ||
| process.stdout.write(`${file} `); | ||
| } | ||
| } | ||
| } | ||
|
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. Default output (no -1 flag) has no trailing newline. The loop writes file1 file2 file3 with a trailing space but never writes \n at the end. |
||
| } catch (err) { | ||
|
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. Good case of error handling, we need similar in other utils. |
||
| process.stderr.write(`cannot access '${path}': No such file or directory\n`); | ||
| process.exit(1); | ||
| } | ||
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,15 @@ | ||
| { | ||
| "name": "ls", | ||
| "version": "1.0.0", | ||
| "description": "You should already be familiar with the `ls` command line tool.", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "test": "echo \"Error: no test specified\" && exit 1" | ||
| }, | ||
| "keywords": [], | ||
| "author": "", | ||
| "license": "ISC", | ||
| "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,16 @@ | ||
| { | ||
| "name": "wc", | ||
| "version": "1.0.0", | ||
| "description": "You should already be familiar with the `wc` command line tool.", | ||
| "main": "wc.mjs", | ||
| "type": "module", | ||
| "dependencies": { | ||
| "commander": "^14.0.3" | ||
| }, | ||
| "scripts": { | ||
| "test": "echo \"Error: no test specified\" && exit 1" | ||
| }, | ||
| "keywords": [], | ||
| "author": "", | ||
| "license": "ISC" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { program } from 'commander'; | ||
| import process from 'node:process'; | ||
| import { promises as fs } from 'node:fs'; | ||
|
|
||
| program | ||
| .name('wc') | ||
| .description('Counts the number of lines, words, and characters in a file.') | ||
| .argument('<path>', 'The path to the file to analyze') | ||
|
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. Real wc accepts multiple files and prints a total row. |
||
| .option('-l, --lines', 'Only count lines') | ||
| .option('-w, --words', 'Only count words') | ||
| .option('-c, --characters', 'Only count characters'); | ||
|
|
||
| program.parse(); | ||
|
|
||
| const argv = program.args; | ||
|
|
||
| if (argv.length != 1) { | ||
| console.error( | ||
| `Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); | ||
| process.exit(1); | ||
| } | ||
| const path = argv[0]; | ||
| const options = program.opts(); | ||
|
|
||
| let showLines = options.lines || (!options.words && !options.characters); | ||
| let showWords = options.words || (!options.lines && !options.characters); | ||
| let showCharacters = options.characters || (!options.lines && !options.words); | ||
|
|
||
| const content = await fs.readFile(path, 'utf-8'); | ||
|
|
||
| const lineCount = content.split('\n').filter(Boolean).length; | ||
|
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. File with a blank line in the middle will have it excluded from the count. |
||
| const wordCount = content.split(' ').filter(Boolean).length; | ||
|
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. Only splits on spaces — tabs and newlines between words are not treated as separators. |
||
| const characterCount = content.length; | ||
|
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. content.length counts UTF-16 code units. Real wc -c counts bytes. |
||
| console.log(` ${showLines ? lineCount : ''} ${showWords ? wordCount : ''} ${showCharacters ? characterCount : ''} ${path}`); | ||
|
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. What happens if some flags are missing? Does it affect the format? |
||
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 happens if file is absent?