diff --git a/.gitignore b/.gitignore index 3c3629e64..0099691d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +demo* diff --git a/implement-shell-tools/.gitignore b/implement-shell-tools/.gitignore new file mode 100644 index 000000000..40b878db5 --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/implement-shell-tools/cat/customCat.js b/implement-shell-tools/cat/customCat.js new file mode 100755 index 000000000..98770c107 --- /dev/null +++ b/implement-shell-tools/cat/customCat.js @@ -0,0 +1,41 @@ +#!/usr/bin/env node +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("ccat") + .description("CLI command to concatenate and print files") + .option("-n, --number", "Number all output lines starting, at 1") + .option("-b, --nonBlank", "Number only non-blank lines, starting at 1") + .argument("", "Files to read"); + +program.parse(); + +const argv = program.args; +const options = program.opts(); + +for (const filePath of argv) { + try { + const content = await fs.readFile(filePath, "utf-8"); + const lines = content.split("\n"); + + if (lines[lines.length - 1] === "") lines.pop(); + + lines.forEach((line, index) => { + if (options.nonBlank) { + if (line.trim() !== "") { + process.stdout.write( + `${(index + 1).toString().padStart(6)} ${line}\n`, + ); + } else process.stdout.write(`${line}\n`); + } else if (options.number) { + process.stdout.write(`${(index + 1).toString().padStart(6)} ${line}\n`); + } else { + process.stdout.write(`${line}\n`); + } + }); + } catch (error) { + console.error(`ccat: ${filePath}: No such file or directory.`); + } +} diff --git a/implement-shell-tools/ls/customLs.js b/implement-shell-tools/ls/customLs.js new file mode 100755 index 000000000..928566ef4 --- /dev/null +++ b/implement-shell-tools/ls/customLs.js @@ -0,0 +1,53 @@ +#!/usr/bin/env node + +import process from "node:process"; +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program + .name("cls") + .description("List contents of a directory") + .option("-1", "Force output to be one entry per line") + .option("-a", "Include hidden files") + .argument("[path...]", "directories to list"); + +program.parse(); + +const options = program.opts(); +const targetPaths = program.args.length > 0 ? program.args : ["."]; + +async function listDir(dirPath, showHeader) { + try { + let contents = await fs.readdir(dirPath); + + if (options.a) { + contents.push(".", ".."); + } else { + contents = contents.filter((name) => !name.startsWith(".")); + } + + contents.sort(); + + if (showHeader) { + process.stdout.write(`${dirPath}:\n`); + } + + if (options["1"]) { + contents.forEach((item) => process.stdout.write(`${item}\n`)); + } else { + process.stdout.write(`${contents.join(" ")}\n`); + } + } catch (error) { + console.error(`cls: ${dirPath}: ${error.message}`); + process.exit(1); + } +} + +for (let i = 0; i < targetPaths.length; i++) { + const path = targetPaths[i]; + const isMultiplePath = targetPaths.length > 1; + + await listDir(path, isMultiplePath); + + if (isMultiplePath && i < targetPaths.length - 1) process.stdout.write(`\n`); +} diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 000000000..cdf736677 --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "implement-shell-tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "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==", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 000000000..043047a15 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "commander": "^14.0.3" + } +} diff --git a/implement-shell-tools/wc/customWc.js b/implement-shell-tools/wc/customWc.js new file mode 100755 index 000000000..0281db95e --- /dev/null +++ b/implement-shell-tools/wc/customWc.js @@ -0,0 +1,69 @@ +#!/usr/bin/env node + +import { program } from "commander"; +import process from "node:process"; +import { promises as fs } from "node:fs"; + +program + .name("cwc") + .description("Displays number of lines, words, and bytes in a file") + .option("-l, --lines", "Counts number of newline characters") + .option( + "-w, --words", + "Counts sequence of characters separated by whitespace", + ) + .option("-c, --bytes", "Counts raw size of the files in bytes") + .argument("", "File(s) to read and count"); + +program.parse(); + +const options = program.opts(); +const files = program.args; + +const noFlags = !options.lines && !options.words && !options.bytes; + +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; + +async function countFiles(file) { + try { + const buffer = await fs.readFile(file); + const content = buffer.toString("utf-8"); + + const lineCount = content === "" ? 0 : content.split("\n").length - 1; + const wordCount = content.trim() ? content.trim().split(/\s+/).length : 0; + const byteCount = buffer.length; + + totalLines += lineCount; + totalWords += wordCount; + totalBytes += byteCount; + + let result = ""; + + if (options.lines || noFlags) result += `${String(lineCount).padStart(8)}`; + if (options.words || noFlags) result += `${String(wordCount).padStart(8)}`; + if (options.bytes || noFlags) result += `${String(byteCount).padStart(8)}`; + + process.stdout.write(`${result} ${file}\n`); + } catch (error) { + console.error(`cwc: ${file}: ${error.message}`); + process.exit(1); + } +} + +(async () => { + for (const file of files) { + await countFiles(file); + } + + if (files.length > 1) { + let total = ""; + + if (options.lines || noFlags) total += `${String(totalLines).padStart(8)}`; + if (options.words || noFlags) total += `${String(totalWords).padStart(8)}`; + if (options.bytes || noFlags) total += `${String(totalBytes).padStart(8)}`; + + process.stdout.write(`${total} total\n`); + } +})();