diff --git a/implement-shell-tools/cat/cat.mjs b/implement-shell-tools/cat/cat.mjs new file mode 100644 index 00000000..660cadc3 --- /dev/null +++ b/implement-shell-tools/cat/cat.mjs @@ -0,0 +1,51 @@ +import { program } from "commander"; +import {promises as fs} from "node:fs"; + +program + .name("cat") + .description("read, display, and concatenate text files.") + .option("-n", " Number all output lines.") + .option("-b", " Number non-blank output lines.") + .arguments(""); // allow more file paths + +program.parse(); + +const options = program.opts(); +const paths = program.args; + +const numNonBlank = options.b; +const numAll = !!options.n && !numNonBlank; + +let hadError = false; +for(const path of paths){ + let content; + try { + content = await fs.readFile(path, "utf-8") + } catch(err) { + console.error(`Error reading file "${path}": ${err.message} `); + hadError = true; + continue; + } + + // split file into lines + let lines = content.replace(/\n$/, "").split("\n"); + let lineNum = 1; + + for (const line of lines){ + if(numNonBlank){ + if(line.trim() !== ""){ + console.log(`${lineNum.toString().padStart(5)} ${line}`) + lineNum++; + } else { + console.log(""); + } + } else if(numAll){ + console.log(`${lineNum.toString().padStart(5)} ${line}`) + lineNum++; + } else{ + console.log(`${line}`) + } +} +} + +if (hadError) process.exit(1); \ No newline at end of file diff --git a/implement-shell-tools/ls/ls.mjs b/implement-shell-tools/ls/ls.mjs new file mode 100644 index 00000000..b445025b --- /dev/null +++ b/implement-shell-tools/ls/ls.mjs @@ -0,0 +1,38 @@ +import {program} from "commander"; +import {promises as fs} from "node:fs"; + +program + .name("ls") + .description("List all the files in a directory") + .option("-a, --all", "Include hidden files") + .option("-1, --one", "One entry per line") + .argument("[dir]", "directory to list"); + +program.parse(); + +const options = program.opts(); +const dir = program.args[0] || "."; + +let entries; +try { + entries = await fs.readdir(dir, { withFileTypes: true }); +} catch (err) { + console.error(`Error accessing ${dir}: ${err.message}`); + process.exit(1); +} + +const visibleNames = []; + +for(const entry of entries){ + if(!options.all && entry.name.startsWith(".")) continue; + visibleNames.push(entry.name); +} + +if (options.all) { + visibleNames.unshift(".", ".."); +} +if(options.one){ + console.log(visibleNames.join("\n")); +} else{ + console.log(visibleNames.join(" ")); +} \ No newline at end of file diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 00000000..35bb20ea --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "implement-shell-tools", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "implement-shell-tools", + "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==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 00000000..809da8b9 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,16 @@ +{ + "name": "implement-shell-tools", + "version": "1.0.0", + "description": "Your task is to re-implement shell tools you have used.", + "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/wc.mjs b/implement-shell-tools/wc/wc.mjs new file mode 100644 index 00000000..ad54e55f --- /dev/null +++ b/implement-shell-tools/wc/wc.mjs @@ -0,0 +1,69 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program + .name("wc") + .description("Display numbers of line, words, and bytes in each file") + .option("-l", "Number of lines") + .option("-w", "Number of words") + .option("-c", "Number of bytes") + .argument(""); + +program.parse(); + +const options = program.opts(); +const paths = program.args; + +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; +let hadError = false; + +for(const path of paths){ + let content; + try{ + content = await fs.readFile(path, "utf-8"); + } catch (err){ + console.error(`Error reading file "${path}":`, err.message); + hadError = true; + continue; + } + + const lines = content.replace(/\n$/, "").split("\n"); + + const words = content.trim().split(/\s+/); // handles multiple spaces + const { size } = await fs.stat(path); + + const lineCount = lines.length; + const wordCount = words.length; + const byteCount = size; + + totalLines += lineCount; + totalWords += wordCount; + totalBytes += byteCount; + + if(options.l) { + console.log(`\t${lineCount} ${path}`); + } else if(options.w) { + console.log(`\t${wordCount} ${path}`); + } else if(options.c) { + console.log(`\t${byteCount} ${path}`) + } else { + console.log(`\t${lineCount}\t${wordCount}\t${size} ${path}`); + } + +} + +if (paths.length > 1) { + if (options.l) { + console.log(`\t${totalLines} total`); + } else if (options.w) { + console.log(`\t${totalWords} total`); + } else if (options.c) { + console.log(`\t${totalBytes} total`); + } else { + console.log(`\t${totalLines}\t${totalWords}\t${totalBytes} total`); + } +} + +if (hadError) process.exit(1); \ No newline at end of file