From 2552f7f69202b360f3e2ed3c554a6b889bb89290 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Thu, 26 Mar 2026 20:25:04 +0000 Subject: [PATCH 1/5] prep --- implement-shell-tools/prep/package.json | 15 +++++++++++++ implement-shell-tools/prep/prep.mjs | 28 +++++++++++++++++++++++++ implement-shell-tools/prep/test.txt | 1 + 3 files changed, 44 insertions(+) create mode 100644 implement-shell-tools/prep/package.json create mode 100644 implement-shell-tools/prep/prep.mjs create mode 100644 implement-shell-tools/prep/test.txt diff --git a/implement-shell-tools/prep/package.json b/implement-shell-tools/prep/package.json new file mode 100644 index 000000000..911d1099e --- /dev/null +++ b/implement-shell-tools/prep/package.json @@ -0,0 +1,15 @@ +{ + "name": "prep", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + } +} diff --git a/implement-shell-tools/prep/prep.mjs b/implement-shell-tools/prep/prep.mjs new file mode 100644 index 000000000..f79d89945 --- /dev/null +++ b/implement-shell-tools/prep/prep.mjs @@ -0,0 +1,28 @@ +import { program } from 'commander'; +import process from 'node:process'; +import { promises as fs } from 'node:fs'; + +program + .name('prep') + .description('Counts the number of words in a file that contain the letter "e".') + .option('-c, --char ', 'The character to search for in words', 'e') + .argument('', 'The path to the file to analyze') + +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 char = program.opts().char; + +const content = await fs.readFile(path, 'utf-8'); +const countOfWordsContainingChar = content + .split(' ') + .filter((word) => word.includes(char)).length; +console.log(countOfWordsContainingChar); diff --git a/implement-shell-tools/prep/test.txt b/implement-shell-tools/prep/test.txt new file mode 100644 index 000000000..2c63f1e10 --- /dev/null +++ b/implement-shell-tools/prep/test.txt @@ -0,0 +1 @@ +this is a test. there should be 4 in the output \ No newline at end of file From 9f574ca2cda11abc17d3b7f8ec6467adf7770f5d Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Thu, 26 Mar 2026 20:37:49 +0000 Subject: [PATCH 2/5] Add initial implementation of wc command line tool with no options --- implement-shell-tools/wc/package-lock.json | 26 ++++++++++++++++++++++ implement-shell-tools/wc/package.json | 16 +++++++++++++ implement-shell-tools/wc/wc.mjs | 25 +++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 implement-shell-tools/wc/package-lock.json create mode 100644 implement-shell-tools/wc/package.json create mode 100644 implement-shell-tools/wc/wc.mjs diff --git a/implement-shell-tools/wc/package-lock.json b/implement-shell-tools/wc/package-lock.json new file mode 100644 index 000000000..5804b85cb --- /dev/null +++ b/implement-shell-tools/wc/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "wc", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wc", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + }, + "devDependencies": {} + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/wc/package.json b/implement-shell-tools/wc/package.json new file mode 100644 index 000000000..4eeb3c3ca --- /dev/null +++ b/implement-shell-tools/wc/package.json @@ -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" +} diff --git a/implement-shell-tools/wc/wc.mjs b/implement-shell-tools/wc/wc.mjs new file mode 100644 index 000000000..ecf3542c0 --- /dev/null +++ b/implement-shell-tools/wc/wc.mjs @@ -0,0 +1,25 @@ +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('', 'The path to the file to analyze') + +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 content = await fs.readFile(path, 'utf-8'); +const lineCount = content.split('\n').filter(Boolean).length; +const wordCount = content.split(' ').filter(Boolean).length; +const characterCount = content.length; +console.log(` ${lineCount} ${wordCount} ${characterCount} ${path}`); \ No newline at end of file From 0d1f8b19c004c79dbf9248edc10a2ac7f26e0402 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Thu, 26 Mar 2026 21:32:52 +0000 Subject: [PATCH 3/5] Add options for wc --- implement-shell-tools/wc/wc.mjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/wc/wc.mjs b/implement-shell-tools/wc/wc.mjs index ecf3542c0..96f847c35 100644 --- a/implement-shell-tools/wc/wc.mjs +++ b/implement-shell-tools/wc/wc.mjs @@ -6,6 +6,9 @@ program .name('wc') .description('Counts the number of lines, words, and characters in a file.') .argument('', 'The path to the file to analyze') + .option('-l, --lines', 'Only count lines') + .option('-w, --words', 'Only count words') + .option('-c, --characters', 'Only count characters'); program.parse(); @@ -17,9 +20,15 @@ if (argv.length != 1) { 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; const wordCount = content.split(' ').filter(Boolean).length; const characterCount = content.length; -console.log(` ${lineCount} ${wordCount} ${characterCount} ${path}`); \ No newline at end of file +console.log(` ${showLines ? lineCount : ''} ${showWords ? wordCount : ''} ${showCharacters ? characterCount : ''} ${path}`); \ No newline at end of file From e226c94146fa5218b1f74718f40aa83629a7d7a4 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Fri, 27 Mar 2026 09:20:08 +0000 Subject: [PATCH 4/5] Implement cat with options for line numbering --- implement-shell-tools/cat/cat.mjs | 49 +++++++++++++++++++ implement-shell-tools/cat/package-lock.json | 25 ++++++++++ .../{prep => cat}/package.json | 4 +- implement-shell-tools/prep/prep.mjs | 28 ----------- implement-shell-tools/prep/test.txt | 1 - 5 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 implement-shell-tools/cat/cat.mjs create mode 100644 implement-shell-tools/cat/package-lock.json rename implement-shell-tools/{prep => cat}/package.json (69%) delete mode 100644 implement-shell-tools/prep/prep.mjs delete mode 100644 implement-shell-tools/prep/test.txt diff --git a/implement-shell-tools/cat/cat.mjs b/implement-shell-tools/cat/cat.mjs new file mode 100644 index 000000000..8702bff76 --- /dev/null +++ b/implement-shell-tools/cat/cat.mjs @@ -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('', '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] === '') { + lines.pop(); + } + for (const line of lines) { + if (showLineNumbers) { + process.stdout.write(`${spacer} ${lineNumber++} ${line}\n`); + } else if (showNonEmptyLineNumbers && line.trim() !== '') { + process.stdout.write(`${spacer} ${lineNumber++} ${line}\n`); + } else { + process.stdout.write(`${line}\n`); + } + } +} \ No newline at end of file diff --git a/implement-shell-tools/cat/package-lock.json b/implement-shell-tools/cat/package-lock.json new file mode 100644 index 000000000..737542646 --- /dev/null +++ b/implement-shell-tools/cat/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "cat", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cat", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/prep/package.json b/implement-shell-tools/cat/package.json similarity index 69% rename from implement-shell-tools/prep/package.json rename to implement-shell-tools/cat/package.json index 911d1099e..732ca9e6f 100644 --- a/implement-shell-tools/prep/package.json +++ b/implement-shell-tools/cat/package.json @@ -1,7 +1,7 @@ { - "name": "prep", + "name": "cat", "version": "1.0.0", - "description": "", + "description": "You should already be familiar with the `cat` command line tool.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/implement-shell-tools/prep/prep.mjs b/implement-shell-tools/prep/prep.mjs deleted file mode 100644 index f79d89945..000000000 --- a/implement-shell-tools/prep/prep.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { program } from 'commander'; -import process from 'node:process'; -import { promises as fs } from 'node:fs'; - -program - .name('prep') - .description('Counts the number of words in a file that contain the letter "e".') - .option('-c, --char ', 'The character to search for in words', 'e') - .argument('', 'The path to the file to analyze') - -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 char = program.opts().char; - -const content = await fs.readFile(path, 'utf-8'); -const countOfWordsContainingChar = content - .split(' ') - .filter((word) => word.includes(char)).length; -console.log(countOfWordsContainingChar); diff --git a/implement-shell-tools/prep/test.txt b/implement-shell-tools/prep/test.txt deleted file mode 100644 index 2c63f1e10..000000000 --- a/implement-shell-tools/prep/test.txt +++ /dev/null @@ -1 +0,0 @@ -this is a test. there should be 4 in the output \ No newline at end of file From b75b9f7237a9548797571e080e88dbd7cca0bb09 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Fri, 27 Mar 2026 19:05:17 +0000 Subject: [PATCH 5/5] Implementation of ls with -a -1 options --- implement-shell-tools/ls/ls.mjs | 35 ++++++++++++++++++++++ implement-shell-tools/ls/package-lock.json | 25 ++++++++++++++++ implement-shell-tools/ls/package.json | 15 ++++++++++ 3 files changed, 75 insertions(+) create mode 100644 implement-shell-tools/ls/ls.mjs create mode 100644 implement-shell-tools/ls/package-lock.json create mode 100644 implement-shell-tools/ls/package.json diff --git a/implement-shell-tools/ls/ls.mjs b/implement-shell-tools/ls/ls.mjs new file mode 100644 index 000000000..14af5bee0 --- /dev/null +++ b/implement-shell-tools/ls/ls.mjs @@ -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] || '.'; + +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' })); + for (const file of files) { + if (showAll || !file.startsWith('.')) { + if (onePerLine) { + process.stdout.write(`${file}\n`); + } else { + process.stdout.write(`${file} `); + } + } + } +} catch (err) { + process.stderr.write(`cannot access '${path}': No such file or directory\n`); + process.exit(1); +} diff --git a/implement-shell-tools/ls/package-lock.json b/implement-shell-tools/ls/package-lock.json new file mode 100644 index 000000000..74bc88d5b --- /dev/null +++ b/implement-shell-tools/ls/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "ls", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ls", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.3" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/ls/package.json b/implement-shell-tools/ls/package.json new file mode 100644 index 000000000..465d6bed1 --- /dev/null +++ b/implement-shell-tools/ls/package.json @@ -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" + } +}