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
65 changes: 65 additions & 0 deletions implement-shell-tools/cat/script-cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env node
import { program } from "commander";
import { promises as fs } from "node:fs";

// Setup CLI
program
.name("cat")
.description("Concatenate files and print on the standard output")
.option("-n, --number", "number all output lines")
.option("-b, --number-nonblank", "number nonempty output lines")
.argument("<path...>", "file(s) to read");

program.parse();

const files = program.args;
const { number, numberNonblank } = program.opts();

// Validate input
if (files.length === 0) {
console.error("cat: missing file operand");
process.exit(1);
}

let lineNumber = 1;

// Helper to print lines with optional numbering
function printLine(line, shouldNumber) {
if (shouldNumber) {
console.log(`${String(lineNumber).padStart(6)}\t${line}`);
lineNumber++;
} else {
console.log(line);
}
}

for (const file of files) {
let content;
try {
content = await fs.readFile(file, "utf8");
} catch (err) {
console.error(`cat: ${file}: ${err.message}`);
continue;
}

const lines = content.split("\n");

for (let i = 0; i < lines.length; i++) {
const line = lines[i];

// Avoid printing an extra line if file ends with \n
if (i === lines.length - 1 && line === "") {
break;
}

const isBlank = line.trim() === "";

if (numberNonblank) {
printLine(line, !isBlank);
} else if (number) {
printLine(line, true);
} else {
printLine(line, false);
}
}
}
40 changes: 40 additions & 0 deletions implement-shell-tools/ls/script-ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env node
import { program } from "commander";
import fs from "fs";
import path from "path";

// Define CLI
program
.name("ls-clone")
.description("A simple implementation of ls")
.option("-1", "list one file per line")
.option("-a", "include hidden files")
.argument("[dirs...]", "directories to list", "."); // default is current dir

program.parse();

const options = program.opts();
const dirs = program.args.length ? program.args : ["."];
const onePerLine = options["1"];
const showAll = options.a;

for (const dir of dirs) {
let files;
try {
files = fs.readdirSync(dir);
} catch (err) {
console.error(`ls-clone: cannot access '${dir}': No such file or directory`);
continue;
}

if (!showAll) {
files = files.filter(name => !name.startsWith("."));
}

// Output
if (onePerLine) {
files.forEach(f => console.log(f));
} else {
console.log(files.join(" "));
}
}
72 changes: 72 additions & 0 deletions implement-shell-tools/wc/script-wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env node
import { program } from "commander";
import { promises as fs } from "node:fs";
import process from "node:process";

// Setup CLI
program
.name("wc-clone")
.description("A simplified implementation of the wc command")
.option("-l", "count lines")
.option("-w", "count words")
.option("-c", "count bytes")
.argument("[files...]", "files to process");

program.parse();

const options = program.opts();
const files = program.args;

if (files.length === 0) {
console.error("Please provide at least one file.");
process.exit(1);
}

// Count lines, words, bytes
function countContent(content) {
const lines = content.split("\n").length;
const words = content.trim().split(/\s+/).filter(Boolean).length;
const bytes = Buffer.byteLength(content, "utf-8");
return { lines, words, bytes };
}

// Format output consistently
function formatOutput(counts, label = "") {
const showAll = !options.l && !options.w && !options.c;
const parts = [];

if (options.l || showAll) parts.push(counts.lines.toString().padStart(8));
if (options.w || showAll) parts.push(counts.words.toString().padStart(8));
if (options.c || showAll) parts.push(counts.bytes.toString().padStart(8));

if (label) parts.push(label);

return parts.join(" ");
}

(async () => {
let total = { lines: 0, words: 0, bytes: 0 };
const multipleFiles = files.length > 1;

for (const file of files) {
let content;
try {
content = await fs.readFile(file, "utf-8");
} catch {
console.error(`wc-clone: cannot open '${file}': No such file`);
continue;
}

const counts = countContent(content);

total.lines += counts.lines;
total.words += counts.words;
total.bytes += counts.bytes;

console.log(formatOutput(counts, file));
}

if (multipleFiles) {
console.log(formatOutput(total, "total"));
}
})();