diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 00000000..8b694390 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,38 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program + .name("cat command") + .description("create cat command") + .option("-n", "Number all lines") + .option("-b", "Number non-empty lines") + .argument("", "File paths"); + +program.parse(); +const paths = program.args; +const number = program.opts().n; +const nonEmptyLine = program.opts().b; + +let lineNumber = 1; + +for (const path of paths) { + try { + const read = await fs.readFile(path, "utf-8"); + const lines = read.split("\n"); + + function printNumLine(line) { + console.log(`${String(lineNumber).padStart(6, " ")} ${line}`); + lineNumber++; + } + + for (let i of lines) { + if (number || (nonEmptyLine && i.trim() !== "")) { + printNumLine(i); + } else { + console.log(i); + } + } + } catch (err) { + console.error(`File could not read: ${path}`); + } +} diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 00000000..634fac12 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,43 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program + .name("ls command") + .description("create ls command") + .option("-1", "One entry per line") + .option("-a", "Show all files including hidden") + .argument("[paths...]", "File paths"); + +program.parse(); + +const opts = program.opts(); +const paths = program.args.length > 0 ? program.args : ["."]; +const onePerLine = opts["1"]; +const showAll = opts.a; + +for (const targetPath of paths) { + try { + let files = await fs.readdir(targetPath); + + if (!showAll) { + files = files.filter((f) => !f.startsWith(".")); + } + + + if (paths.length > 1) { + console.log(`${targetPath}:`); + } + + if (onePerLine) { + for (const f of files) console.log(f); + } else { + console.log(files.join(" ")); + } + + if (paths.length > 1) { + console.log(); + } + } catch (err) { + console.error(`ls: cannot access '${targetPath}': ${err.message}`); + } +} diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 00000000..38a22987 --- /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.2" + } + }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "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..e49468b7 --- /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.2" + } +} diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 00000000..9df12a1f --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,54 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +program + .option("-l", "Print only the line count") + .option("-w", "Print only the word count") + .option("-c", "Print only the byte count") + .argument("", "One or more file paths"); + +program.parse(); + +const opts = program.opts(); +const paths = program.args; + +async function wcFile(path) { + const content = await fs.readFile(path, "utf8"); + + const lines = content.split("\n").length; + const words = content.trim().split(/\s+/).filter(Boolean).length; + const bytes = Buffer.byteLength(content, "utf8"); + + return { lines, words, bytes }; +} + +function formatOutput(counts, filename, opts) { + if (opts.l) return `${counts.lines} ${filename}`; + if (opts.w) return `${counts.words} ${filename}`; + if (opts.c) return `${counts.bytes} ${filename}`; + + return `${String(counts.lines).padStart(8)} ${String(counts.words).padStart( + 8 + )} ${String(counts.bytes).padStart(8)} ${filename}`; +} + +async function main() { + let total = { lines: 0, words: 0, bytes: 0 }; + let multipleFiles = paths.length > 1; + + for (const path of paths) { + const counts = await wcFile(path); + + total.lines += counts.lines; + total.words += counts.words; + total.bytes += counts.bytes; + + console.log(formatOutput(counts, path, opts)); + } + + if (multipleFiles) { + console.log(formatOutput(total, "total", opts)); + } +} + +main();