diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 00000000..0c9970ea --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,60 @@ +import { promises as fs } from "node:fs"; +import { program } from "commander"; + +program + .name("cat") + .description("Concatenate and print files") + .option("-n", "Number the output lines, starting at 1") + .option("-b", "Number the non-blank output lines, starting at 1") + .argument("", "The file path to process") + .parse(); + +const argv = program.args; + +const opts = program.opts(); + +const countLines = (data) => { + const lines = data.split("\n"); + if (lines[lines.length - 1] === "") { + lines.pop(); + } + + let lineNum = 1; + + for (const line of lines) { + if (opts.b) { + if (line.trim() === "") { + console.log(); + } else { + console.log(`${lineNum} ${line}`); + lineNum++; + } + } else if (opts.n) { + console.log(`${lineNum} ${line}`); + lineNum++; + } + } +}; + +async function example(path) { + try { + const data = await fs.readFile(path, { encoding: "utf8" }); + if (opts["b"]) { + countLines(data); + } else if (opts["n"]) { + countLines(data); + } else { + console.log(data.trimEnd()); + } + } catch (err) { + console.error(err); + } +} + +const handleInput = async () => { + for (const path of argv) { + await example(path); + } +}; + +handleInput(); diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 00000000..d2906380 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,86 @@ +import { promises as fs } from "node:fs"; +import process from "node:process"; +import path from "node:path"; +import { program } from "commander"; + +program + .name("ls") + .description("Shows files in directory") + .option("-1", "list one file per line") + .option( + "-a", + "Used to list all files, including hidden files, in the current directory" + ) + .argument("[sample-files]", "The file path to process"); + +program.parse(); + +let pathToFile = ""; + +const programArgv = program.args; + +(async () => { + if (programArgv.length === 1) { + pathToFile = programArgv[0]; + try { + const stats = await fs.stat(pathToFile); + if (stats.isFile()) { + await listFiles("file"); + } else if (stats.isDirectory()) { + listFiles("directory"); + } else { + console.error("Not a file or directory."); + } + } catch (err) { + console.error("Invalid path:", err.message); + } + } else if (programArgv.length === 0) { + pathToFile = process.cwd(); + await listFiles("directory"); + } else { + console.error( + `Expected no more than 1 argument (sample-files) but got ${programArgv.length}.` + ); + } +})(); + +const flag_1 = (files) => { + files.forEach(function (file) { + console.log(file); + }); +}; + +const flag_a = (files) => { + files.unshift(".."); + files.unshift("."); + return files; +}; + +async function listFiles(type) { + let output = []; + let formattedPath = ""; + if (type == "directory") { + formattedPath = pathToFile; + } else if (type == "file") { + formattedPath = path.dirname(pathToFile); + } + const char = program.opts(); + const files = await fs.readdir(formattedPath); + const sortedOutput = files.sort((a, b) => a.localeCompare(b)); + + if (char["a"]) { + output = flag_a(sortedOutput); + } else { + sortedOutput.forEach(function (file) { + if (file[0] != ".") { + output.push(file); + } + }); + } + + if (char["1"]) { + flag_1(output); + } else { + console.log(output.join(" ")); + } +} diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 00000000..07c7f3f2 --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "implement-shell-tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "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..68279600 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "commander": "^14.0.0" + } +} diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 00000000..1da1bd1b --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,95 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program + .name("count-containing-words") + .description("Counts words in a file that contain a particular character") + .option( + "-l", + "The number of lines in each input file is written to the standard output." + ) + .option( + "-w", + "The number of words in each input file is written to the standard output." + ) + .option( + "-c", + "The number of bytes in each input file is written to the standard output." + ) + .argument("", "The file path to process") + .parse(); + +const argv = program.args; + +const opts = program.opts(); + +const total = []; +const output = []; +const countsPerFile = []; +let columnWidth = 0; + +const flag_c = (content) => { + return Buffer.byteLength(content, "utf8"); +}; + +const flag_w = (content) => { + return content.match(/\b[\w']+\b/g).length; +}; + +const flag_l = (content) => { + return content.split("\n").length - 1; +}; + +const countAndDisplay = async (path) => { + const outputPerFile = []; + const content = await fs.readFile(path, "utf-8"); + if (opts["l"]) { + outputPerFile.push(flag_l(content)); + } + if (opts["w"]) { + outputPerFile.push(flag_w(content)); + } + if (opts["c"]) { + outputPerFile.push(flag_c(content)); + } + if (argv.length > 1) { + if (total.length == 0) { + total.push(...outputPerFile); + } else { + for (let index = 0; index < outputPerFile.length; index++) { + total[index] += outputPerFile[index]; + } + } + countsPerFile.push(...outputPerFile); + } + outputPerFile.push(path); + output.push([...outputPerFile]); +}; + +const handleInput = async () => { + if (Object.keys(opts).length == 0) { + ["l", "w", "c"].forEach((key) => (opts[key] = true)); + } + for (const path of argv) { + await countAndDisplay(path); + } + const numOfColumns = Object.keys(opts).length; + if (argv.length > 1) { + total.push("total"); + output.push(total); + } + for (const row of output) { + for (let i = 0; i < numOfColumns; i++) { + columnWidth = Math.max(columnWidth, String(row[i]).length); + } + } + + for (let row of output) { + const line_parts = []; + for (let i = 0; i < numOfColumns; i++) { + line_parts.push(String(row[i]).padStart(columnWidth + 4)); + } + console.log(line_parts.join(" "), row.at(-1)); + } +}; +handleInput();