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

program
.name("cat")
.description("Reads file(s) and writes them to the standard output")
.argument("<paths...>", "The file path(s) to process")
.option("-n", "Number the output lines, starting at 1.")
.option("-b", "Number only non-blank output lines, starting at 1.");

program.parse();

try {
const filePaths = program.args;

const options = program.opts();

for (const filePath of filePaths) {
const file = await fs.open(filePath);

let lineNum = 1;

try {
for await (const line of file.readLines()) {
const isBlank = line.trim() === "";
const shouldNumber = options.n || (options.b && !isBlank);

console.log(shouldNumber ? `${lineNum} ${line}` : line);

if (shouldNumber) lineNum++;
}
} finally {
await file.close();
}
}
} catch (err) {
console.error(err.message);
}
21 changes: 21 additions & 0 deletions implement-shell-tools/cat/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions implement-shell-tools/cat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "module",
"dependencies": {
"commander": "^14.0.3"
}
}
67 changes: 67 additions & 0 deletions implement-shell-tools/ls/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { program } from "commander";
import * as fs from "node:fs/promises";

program
.name("ls")
.description("List directory contents")
.argument(
"[paths...]",
"The file path to process (defaults to current directory)",
)
.option("-a", "Include directory entries whose names begin with a dot ('.').")
.option("-1", "Force output to be one entry per line.");

program.parse();

try {
let filePaths = program.args;
if (!filePaths || filePaths.length === 0) {
filePaths = ["."];
}

const options = program.opts();
const includeHidden = Boolean(options.a);
const onePerLine = Boolean(options["1"]);

const result = { files: [], dirs: {} };

for (const filePath of filePaths) {
const stats = await fs.stat(filePath);
if (stats.isFile()) result.files.push(filePath);
if (stats.isDirectory()) {
result.dirs[filePath] = await fs.readdir(filePath);
}
}

const filterHidden = (files) => files.filter((file) => !file.startsWith("."));

const getVisibleEntries = (files) =>
includeHidden ? files : filterHidden(files);

const formatEntries = (files) => {
if (files.length === 0) return;
console.log(files.join(onePerLine ? "\n" : "\t"));
};

result.files = getVisibleEntries(result.files);

if (filePaths.length === 1) {
let entries = [...result.files];

for (const [dir, contents] of Object.entries(result.dirs)) {
const filtered = getVisibleEntries(contents);
entries = entries.concat(filtered);
}
formatEntries(entries);
} else {
formatEntries(result.files);

for (const [dir, contents] of Object.entries(result.dirs)) {
console.log("\n" + dir + ":");
const filtered = getVisibleEntries(contents);
formatEntries(filtered);
}
}
} catch (err) {
console.error(err.message);
}
21 changes: 21 additions & 0 deletions implement-shell-tools/ls/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions implement-shell-tools/ls/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "module",
"dependencies": {
"commander": "^14.0.3"
}
}
21 changes: 21 additions & 0 deletions implement-shell-tools/wc/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions implement-shell-tools/wc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "module",
"dependencies": {
"commander": "^14.0.3"
}
}
65 changes: 65 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { program } from "commander";
import * as fs from "node:fs/promises";

program
.name("wc")
.description("word, line and byte count")
.argument("<paths...>", "The file path(s) to process.")
.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.",
);

program.parse();

try {
const filePaths = program.args;
const results = {};

for (const filePath of filePaths) {
const file = await fs.open(filePath);
const stats = await fs.stat(filePath);
const count = { lines: 0, words: 0, bytes: stats.size };

try {
for await (const line of file.readLines()) {
count.lines++;
const trimmed = line.trim();
if (trimmed.length > 0) {
count.words += trimmed.split(/\s+/).length;
}
}
} finally {
await file.close();
}
results[filePath] = count;
}

if (filePaths.length > 1) {
const total = { lines: 0, words: 0, bytes: 0 };
for (const file of Object.values(results)) {
total.lines += file.lines;
total.words += file.words;
total.bytes += file.bytes;
}
results["total"] = total;
}

const options = program.opts();
const noOptionsProvided = !Object.keys(options).length;
const selectedOptionKeys = [...Object.keys(options)];

noOptionsProvided
? console.table(results)
: console.table(results, selectedOptionKeys);
} catch (err) {
console.error(err.message);
}