Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions implement-shell-tools/cat/sample-files/myCat.js
Original file line number Diff line number Diff line change
@@ -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...>", "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();
51 changes: 51 additions & 0 deletions implement-shell-tools/ls/sample-files/myLs.js
Original file line number Diff line number Diff line change
@@ -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();
114 changes: 114 additions & 0 deletions implement-shell-tools/wc/sample-files/myWc.js
Original file line number Diff line number Diff line change
@@ -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...>", "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();