diff --git a/implement-shell-tools/cat/mycat.js b/implement-shell-tools/cat/mycat.js new file mode 100644 index 00000000..83a03e22 --- /dev/null +++ b/implement-shell-tools/cat/mycat.js @@ -0,0 +1,48 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("mycat") + .description("Outputs the content of the given file(s), like the cat command") + .option("-n", "Number all lines") + .option("-b", "Number non-blank lines only") + .argument("", "File paths to display"); + +program.parse(); + +const options = program.opts(); +const filePaths = program.args; + +let lineNumber = 1; + +for (const path of filePaths) { + try { + const content = await fs.readFile(path, "utf-8"); + const lines = content.trimEnd().split("\n"); + + for (const line of lines) { + const isBlank = line.trim() === ""; + + if (options.b) { + // -b: number only non-blank lines + if (!isBlank) { + process.stdout.write(`${String(lineNumber).padStart(6)} ${line}\n`); + lineNumber++; + } else { + process.stdout.write("\n"); + } + } else if (options.n) { + // -n: number all lines + process.stdout.write(`${String(lineNumber).padStart(6)} ${line}\n`); + lineNumber++; + } else { + // no flags + process.stdout.write(line + "\n"); + } + } + } catch (error) { + process.stderr.write(`Error reading file ${path}: ${error.message}\n`); + process.exit(1); + } +} diff --git a/implement-shell-tools/cat/package-lock.json b/implement-shell-tools/cat/package-lock.json new file mode 100644 index 00000000..a46e9346 --- /dev/null +++ b/implement-shell-tools/cat/package-lock.json @@ -0,0 +1,24 @@ +{ + "name": "cat", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cat", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/cat/package.json b/implement-shell-tools/cat/package.json new file mode 100644 index 00000000..5dee1152 --- /dev/null +++ b/implement-shell-tools/cat/package.json @@ -0,0 +1,16 @@ +{ + "name": "cat", + "version": "1.0.0", + "description": "You should already be familiar with the `cat` command line tool.", + "main": "mycat.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } +} diff --git a/implement-shell-tools/ls/myls.js b/implement-shell-tools/ls/myls.js new file mode 100644 index 00000000..506269c8 --- /dev/null +++ b/implement-shell-tools/ls/myls.js @@ -0,0 +1,36 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("myls") + .description("list file(s) in the directory, like the ls command") + .option("-1", "list one file per line") + .option("-a", "include hidden files") + .argument("[directory]", "Directory to list", "."); + +program.parse(); + +const options = program.opts(); +const directory = program.args[0] || "."; + +try { + let files = await fs.readdir(directory); + + // if "-a" is used, include hidden files; those that start with "." + if (options.a) { + files = [".", "..", ...files]; + } + + for (const file of files) { + // if "-a" is not used, skip hidden files; those that start with "." + if (!options.a && file.startsWith(".")) { + continue; + } + + console.log(file); // print file name; one file per line + } +} catch (error) { + console.error(`Error reading directory ${directory}:`, error.message); + process.exit(1); +} \ No newline at end of file diff --git a/implement-shell-tools/ls/package-lock.json b/implement-shell-tools/ls/package-lock.json new file mode 100644 index 00000000..c663db97 --- /dev/null +++ b/implement-shell-tools/ls/package-lock.json @@ -0,0 +1,24 @@ +{ + "name": "ls", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ls", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/ls/package.json b/implement-shell-tools/ls/package.json new file mode 100644 index 00000000..1e742d85 --- /dev/null +++ b/implement-shell-tools/ls/package.json @@ -0,0 +1,16 @@ +{ + "name": "ls", + "version": "1.0.0", + "description": "You should already be familiar with the `ls` command line tool.", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } +} diff --git a/implement-shell-tools/wc/mywc.js b/implement-shell-tools/wc/mywc.js new file mode 100644 index 00000000..f09a8e4b --- /dev/null +++ b/implement-shell-tools/wc/mywc.js @@ -0,0 +1,68 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program + .name("mywc") + .description( + "Counts lines, words, and bytes in files like the Unix wc command" + ) + .option("-l", "counts the number of lines") + .option("-w", "counts words") + .option("-c", "counts bytes") + .argument("", "Files to count"); + +program.parse(); + +const options = program.opts(); +const files = program.args; + +// If there are no given flags, default to counting lines, words and bytes like the Unix wc command +if (!options.l && !options.w && !options.c) { + options.l = true; + options.w = true; + options.c = true; +} + +const showLines = options.l; +const showWords = options.w; +const showBytes = options.c; + +// To support multiple files and a total +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; + +for (const file of files) { + try { + const content = await fs.readFile(file, "utf-8"); + + const lineCount = content.split("\n").length - 1; + const wordCount = content.trim().split(/\s+/).filter(Boolean).length; + const byteCount = Buffer.byteLength(content, "utf-8"); + + totalLines += lineCount; + totalWords += wordCount; + totalBytes += byteCount; + + let output = ""; + if (showLines) output += `${lineCount.toString().padStart(8)}`; + if (showWords) output += `${wordCount.toString().padStart(8)}`; + if (showBytes) output += `${byteCount.toString().padStart(8)}`; + output += ` ${file}`; + + console.log(output); + } catch (err) { + console.error(`Error reading file ${file}: ${err.message}`); + } +} + +// If multiple files were given, show the total +if (files.length > 1) { + let totalOutput = ""; + if (showLines) totalOutput += `${totalLines.toString().padStart(8)}`; + if (showWords) totalOutput += `${totalWords.toString().padStart(8)}`; + if (showBytes) totalOutput += `${totalBytes.toString().padStart(8)}`; + totalOutput += " total"; + + console.log(totalOutput); +} \ No newline at end of file diff --git a/implement-shell-tools/wc/package-lock.json b/implement-shell-tools/wc/package-lock.json new file mode 100644 index 00000000..092a4c2b --- /dev/null +++ b/implement-shell-tools/wc/package-lock.json @@ -0,0 +1,24 @@ +{ + "name": "wc", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wc", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/wc/package.json b/implement-shell-tools/wc/package.json new file mode 100644 index 00000000..79979331 --- /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": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } +}