diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 0000000..c5dc568 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,48 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("concatenate-and-print-files") + .description("Concatenate and print files") + .option("-b", "number the non-blank output lines, starting at 1.") + .option("-n", "number all output lines, starting at 1.") + .option("-s", "remove multiple blank lines in the text.") + .argument("", "The file paths to process"); + +program.parse(); +const options = program.opts(); +const argvs = program.args; + +if (options.n && options.b) { + console.error(`Expected -n or -b option to be passed but got both.`); + process.exit(1); +} + +for (const path of argvs) { + try { + let content = await fs.readFile(path, "utf-8"); + const lines = content.split("\n"); + lines.pop(); + let lineNumber = 1; + if (content && content.trim()) { + if (options.s) { + content = content.replace(/\n{2,}/g, "\n"); + } + for (const line of lines) { + if (options.n) { + console.log(lineNumber + " " + line); + lineNumber += 1; + } else if (line.trim() && options.b) { + console.log(lineNumber + " " + line); + lineNumber += 1; + } else { + console.log(line); + } + } + } + } catch (err) { + console.error(`Error reading ${path}: ${err.message}`); + process.exit(1); + } +} diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 0000000..fb21fad --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,67 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; +import path from "node:path"; + +program + .name("ls") + .description( + `For each operand that names a file of type directory, ls displays the names of files contained within that directory, as well as any requested, associated information. + \nIf no operands are given, the contents of the current directory are displayed. + \nIf more than one operand is given, non-directory operands are displayed first; directory and non-directory operands are sorted separately and in lexicographical order.` + ) + .option("-1, --one-per-line", "output one entry per line.") + .option("-a, --all", "shows hidden files") + .argument("[paths...]", "The file paths to process"); + +program.parse(); + +const options = program.opts(); +const arg = program.args; +let currentDirName = process.cwd(); + +async function listDirectory(currentDirName) { + try { + const files = await fs.readdir(currentDirName); + let output = []; + files.forEach((file) => { + if (!options["all"] && file[0] === ".") { + return; + } else { + output.push(file); + } + }); + output.sort(); + options.onePerLine + ? output.forEach((file) => { + console.log(file); + }) + : console.log(output.join(" ")); + } catch (err) { + if (err.code === "ENOENT") { + console.error(`Error: The directory "${currentDirName}" does not exist.`); + } else if (err.code === "ENOTDIR") { + console.error(`Error: "${currentDirName}" is not a directory.`); + } else { + console.error(`Error listing ${currentDirName}: ${err.message}`); + } + } +} + +if (arg[0]) { + for (const dirName of arg) { + try { + const dirPath = path.join(currentDirName, dirName); + console.log(dirName + ":"); + await listDirectory(dirPath); + } catch (err) { + console.log("Path should be a string."); + } + } +} else { + try { + await listDirectory(currentDirName); + } catch (err) { + console.log("Path should be a string."); + } +} diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 0000000..45ca92d --- /dev/null +++ b/implement-shell-tools/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": "^13.1.0" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 0000000..91711a3 --- /dev/null +++ b/implement-shell-tools/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": "cat.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "commander": "^13.1.0" + }, + "type": "module" +} diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 0000000..cd6dc82 --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,131 @@ +import { program } from "commander"; +import * as fs from "node:fs"; +import process from "node:process"; +import path from "node:path"; + +program + .name("wc") + .description(`word, line, character, and byte count`) + .option( + "-l, --lines", + "The number of lines in each input file is written to the standard output." + ) + .option( + "-w, --words", + "The number of words in each input file is written to the standard output." + ) + .option( + "-c, --bytes", + "The number of bytes in each input file is written to the standard output." + ) + .argument("", "The file paths to process"); + +program.parse(); + +const options = program.opts(); +const arg = program.args; +let currentDirName = process.cwd(); + +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; + +function countLines(content) { + let counter = 0; + for (const line of content.split("\n")) { + counter += 1; + } + return counter - 1; +} + +function countWords(content) { + let counter = 0; + for (const word of content.split(" ")) { + for (const w of word.split("\n")) { + if (w) { + counter += 1; + } + } + } + return counter; +} + +function countBytes(content) { + let size = new Blob([content]).size; + return size; +} + +async function fileContentInfo(filePath, file) { + try { + let content = await fs.promises.readFile(filePath, "utf-8"); + let output = ""; + if (Object.keys(options).length == 0) { + let numLines = countLines(content); + let numWords = countWords(content); + let numBytes = countBytes(content); + output += numLines + " "; + output += numWords + " "; + output += numBytes + " "; + totalLines += numLines; + totalWords += numWords; + totalBytes += numBytes; + } + if (options.lines) { + let numLines = countLines(content); + output += numLines + " "; + totalLines += numLines; + } + if (options.words) { + let numWords = countWords(content); + output += numWords + " "; + totalWords += numWords; + } + if (options.bytes) { + let numBytes = countBytes(content); + output += numBytes + " "; + totalBytes += numBytes; + } + output += file; + console.log(output); + } catch (err) { + console.log(err); + } +} + +async function main() { + const promises = arg.map((argument) => { + const filePath = path.join(currentDirName, argument); + return new Promise((resolve, reject) => { + fs.stat(filePath, (err, stats) => { + if (err) { + if (err.code === "ENOENT") { + console.error(`wc: ${argument}: read: No such file or directory`); + } else { + console.error(`wc: ${argument}: read: Error: ${err.message}`); + } + resolve(); + } else if (stats.isFile()) { + fileContentInfo(filePath, argument).then(resolve); + } else if (stats.isDirectory()) { + console.log(`wc: ${argument}: read: Is a directory`); + resolve(); + } else { + console.error(`wc: ${argument}: read: Unknown file type`); + resolve(); + } + }); + }); + }); + await Promise.all(promises); + if (Object.keys(options).length == 0) { + console.log(totalLines, totalWords, totalBytes, "total"); + } else { + let totalOutput = ""; + options.lines ? (totalOutput += totalLines + " ") : null; + options.words ? (totalOutput += totalWords + " ") : null; + options.bytes ? (totalOutput += totalBytes + " ") : null; + console.log(totalOutput + "total"); + } +} + +main();