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/cat/package.json b/implement-shell-tools/cat/package.json new file mode 100644 index 000000000..732ca9e6f --- /dev/null +++ b/implement-shell-tools/cat/package.json @@ -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" + } +} 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" + } +} 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..96f847c35 --- /dev/null +++ b/implement-shell-tools/wc/wc.mjs @@ -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('', '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(); + +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; +const wordCount = content.split(' ').filter(Boolean).length; +const characterCount = content.length; +console.log(` ${showLines ? lineCount : ''} ${showWords ? wordCount : ''} ${showCharacters ? characterCount : ''} ${path}`); \ No newline at end of file