diff --git a/implement-shell-tools/cat/sample-files/myCat.js b/implement-shell-tools/cat/sample-files/myCat.js new file mode 100755 index 00000000..9073b545 --- /dev/null +++ b/implement-shell-tools/cat/sample-files/myCat.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +const { program } = require("commander"); +const fs = require("fs"); +const path = require("path"); + +function expandWildcard(pattern) { + const dir = path.dirname(pattern); + const base = path.basename(pattern); + + if (!base.includes("*")) return [pattern]; + + let files; + try { + files = fs.readdirSync(dir); + } catch { + console.error(`cat: ${pattern}: No such directory`); + return []; + } + + const regex = new RegExp("^" + base.replace(/\*/g, ".*") + "$"); + + return files + .filter((f) => regex.test(f)) + .map((f) => path.join(dir, f)); +} + +function printFile(filename, options) { + let text; + try { + text = fs.readFileSync(filename, "utf-8"); + } catch { + console.error(`cat: ${filename}: No such file`); + return; + } + + const lines = text.split("\n"); + if (lines[lines.length - 1] === "") lines.pop(); + + let counter = 1; + const paddingSize = 6; + + lines.forEach((line) => { + const isEmpty = line.trim() === ""; + + const shouldNumber = + options.numberAll || + (options.numberNonempty && !isEmpty); + + if (shouldNumber) { + console.log( + `${String(counter).padStart(paddingSize)} ${line}` + ); + counter++; + } else { + console.log(line); + } + }); +} + +program + .name("mycat") + .description("A custom implementation of the cat command") + .argument("", "files or wildcard patterns") + .option("-n, --number-all", "number all lines") + .option("-b, --number-nonempty", "number non-empty lines") + .action((patterns, options) => { + let allFiles = []; + + patterns.forEach((p) => { + allFiles = allFiles.concat(expandWildcard(p)); + }); + + allFiles.forEach((file) => printFile(file, options)); + }); + +program.parse(); diff --git a/implement-shell-tools/ls/sample-files/myLs.js b/implement-shell-tools/ls/sample-files/myLs.js new file mode 100755 index 00000000..9f011c19 --- /dev/null +++ b/implement-shell-tools/ls/sample-files/myLs.js @@ -0,0 +1,51 @@ +#!/usr/bin/env node +const { program } = require("commander"); +const fs = require("fs"); +const path = require("path"); + +function listDirectory(dir, options) { + try { + const stats = fs.statSync(dir); + + if (stats.isFile()) { + console.log(dir); + return; + } + } catch (e) { + console.error(`ls: cannot access '${dir}': No such file or directory`); + return; + } + + let entries; + + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch (e) { + console.error(`ls: cannot access '${dir}': No such file or directory`); + return; + } + + let names = entries.map(e => e.name); + + if (options.all) { + names.unshift(".", ".."); + } else { + names = names.filter(name => !name.startsWith(".")); + } + + names.sort(); + names.forEach(name => console.log(name)); +} + +program + .name("myls") + .description("Custom implementation of ls") + .option("-1", "list one file per line (required)") + + .option("-a, --all", "include hidden files") + .argument("[dir]", "directory to list", ".") + .action((dir, options) => { + listDirectory(dir, options); + }); + +program.parse(); \ No newline at end of file diff --git a/implement-shell-tools/wc/sample-files/myWc.js b/implement-shell-tools/wc/sample-files/myWc.js new file mode 100755 index 00000000..4c90111f --- /dev/null +++ b/implement-shell-tools/wc/sample-files/myWc.js @@ -0,0 +1,114 @@ +#!/usr/bin/env node +const { program } = require("commander"); +const fs = require("fs"); +const path = require("path"); + +function expandWildcard(pattern) { + const dir = path.dirname(pattern); + const base = path.basename(pattern); + + if (!base.includes("*")) return [pattern]; + + let files; + try { + files = fs.readdirSync(dir); + } catch { + console.error(`wc: ${pattern}: No such directory`); + return []; + } + + const regex = new RegExp("^" + base.replace(/\*/g, ".*") + "$"); + return files + .filter(f => regex.test(f)) + .map(f => path.join(dir, f)); +} + +function countLines(text) { + if (text === "") return 0; + const matches = text.match(/\n/g) || []; + return text.endsWith("\n") ? matches.length : matches.length + 1; +} + +function countWords(text) { + return text.split(/\s+/).filter(Boolean).length; +} + +function countChars(text) { + return Buffer.byteLength(text, "utf-8"); +} + +function formatOutput({ lines, words, chars }, options, label) { + const paddingSize = 7; + + const paddedLines = String(lines).padStart(paddingSize); + const paddedWords = String(words).padStart(paddingSize); + const paddedChars = String(chars).padStart(paddingSize); + + const onlyLines = options.lines && !options.words && !options.chars; + const onlyWords = options.words && !options.lines && !options.chars; + const onlyChars = options.chars && !options.lines && !options.words; + + if (onlyLines) return `${paddedLines} ${label}`; + if (onlyWords) return `${paddedWords} ${label}`; + if (onlyChars) return `${paddedChars} ${label}`; + + return `${paddedLines} ${paddedWords} ${paddedChars} ${label}`; +} + +function wcFile(filename, options) { + let text; + try { + text = fs.readFileSync(filename, "utf-8"); + } catch { + console.error(`wc: ${filename}: No such file`); + return null; + } + + const counts = { + lines: countLines(text), + words: countWords(text), + chars: countChars(text), + }; + + console.log(formatOutput(counts, options, filename)); + return counts; +} + +program + .name("mywc") + .description("Custom implementation of wc") + .option("-l, --lines", "count lines") + .option("-w, --words", "count words") + .option("-c, --chars", "count characters") + .argument("", "files or wildcard patterns") + .action((patterns, options) => { + let allFiles = []; + patterns.forEach(p => { + allFiles = allFiles.concat(expandWildcard(p)); + }); + + let totalLines = 0; + let totalWords = 0; + let totalChars = 0; + + allFiles.forEach(file => { + const result = wcFile(file, options); + if (result) { + totalLines += result.lines; + totalWords += result.words; + totalChars += result.chars; + } + }); + + if (allFiles.length > 1) { + console.log( + formatOutput( + { lines: totalLines, words: totalWords, chars: totalChars }, + options, + "total" + ) + ); + } + }); + +program.parse();