Skip to content

Commit e1412bc

Browse files
Commit
1 parent 3f26722 commit e1412bc

9 files changed

Lines changed: 265 additions & 0 deletions

File tree

implement-shell-tools/cat/cat.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { program } from "commander";
2+
import { promises as fs } from "node:fs";
3+
4+
program
5+
.name("cat")
6+
.description("Concatenate and print files")
7+
.argument("<paths...>", "The file paths to process")
8+
.option("-n", "Number lines")
9+
.option("-b", "Number non-blank lines");
10+
11+
program.parse();
12+
13+
function parseNumberMode(options) {
14+
if (options.b) {
15+
return "non-blank";
16+
} else if (options.n) {
17+
return "all";
18+
} else {
19+
return "none";
20+
}
21+
}
22+
23+
function calculatePrefix(
24+
numberMode,
25+
nonBlankLineNumber,
26+
lineNumberIncludingBlanks,
27+
thisLineIsBlank,
28+
) {
29+
if (numberMode === "none") {
30+
return "";
31+
}
32+
let lineNumber;
33+
if (numberMode === "all") {
34+
lineNumber = lineNumberIncludingBlanks;
35+
} else {
36+
if (thisLineIsBlank) {
37+
return "";
38+
} else {
39+
lineNumber = nonBlankLineNumber;
40+
}
41+
}
42+
return lineNumber.toString().padStart(6, " ") + " ";
43+
}
44+
45+
const numberMode = parseNumberMode(program.opts());
46+
47+
for (const path of program.args) {
48+
const content = await fs.readFile(path, "utf-8");
49+
const lines = content.split("\n");
50+
let nonBlankLineNumber = 1;
51+
for (let i = 0; i < lines.length; i++) {
52+
const line = lines[i];
53+
if (i === lines.length - 1 && line === "") {
54+
break;
55+
}
56+
const prefix = calculatePrefix(
57+
numberMode,
58+
nonBlankLineNumber,
59+
i + 1,
60+
line === "",
61+
);
62+
console.log(`${prefix}${line}`);
63+
if (line !== "") {
64+
nonBlankLineNumber += 1;
65+
}
66+
}
67+
}

implement-shell-tools/cat/package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "module",
3+
"dependencies": {
4+
"commander": "^15.0.0"
5+
}
6+
}

implement-shell-tools/ls/ls.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { promises as fs } from "node:fs";
2+
import process from "node:process";
3+
4+
const argv = process.argv.slice(2);
5+
6+
const flags = [];
7+
const paths = [];
8+
9+
for (const arg of argv) {
10+
if (arg.startsWith("-")) {
11+
flags.push(arg);
12+
} else {
13+
paths.push(arg);
14+
}
15+
}
16+
17+
const showOnePerLine = flags.includes("-1");
18+
const showHiddenFiles = flags.includes("-a");
19+
20+
let targets = paths;
21+
if (targets.length === 0) {
22+
targets = ["."];
23+
}
24+
25+
for (const target of targets) {
26+
const info = await fs.stat(target);
27+
28+
let namesToShow;
29+
30+
if (info.isDirectory()) {
31+
namesToShow = await fs.readdir(target);
32+
33+
if (showHiddenFiles) {
34+
namesToShow = [".", "..", ...namesToShow];
35+
} else {
36+
namesToShow = namesToShow.filter((name) => !name.startsWith("."));
37+
}
38+
39+
namesToShow.sort();
40+
} else {
41+
namesToShow = [target];
42+
}
43+
44+
for (const name of namesToShow) {
45+
console.log(name);
46+
}
47+
}

implement-shell-tools/ls/package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "module",
3+
"dependencies": {
4+
"commander": "^15.0.0"
5+
}
6+
}

implement-shell-tools/wc/package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "module",
3+
"dependencies": {
4+
"commander": "^15.0.0"
5+
}
6+
}

implement-shell-tools/wc/wc.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { program } from "commander";
2+
import { promises as fs } from "node:fs";
3+
4+
const argv = process.argv.slice(2);
5+
6+
const flags = argv.filter((arg) => arg.startsWith("-"));
7+
const paths = argv.filter((arg) => !arg.startsWith("-"));
8+
9+
const showLines = flags.includes("-l");
10+
const showWords = flags.includes("-w");
11+
const showBytes = flags.includes("-c");
12+
13+
const noFlagsGiven = !showLines && !showWords && !showBytes;
14+
15+
const columns = [];
16+
if (noFlagsGiven || showLines) columns.push("lines");
17+
if (noFlagsGiven || showWords) columns.push("words");
18+
if (noFlagsGiven || showBytes) columns.push("bytes");
19+
20+
function countStats(content) {
21+
const lines = (content.match(/\n/g) || []).length;
22+
const words = content.split(/\s+/).filter((w) => w.length > 0).length;
23+
const bytes = Buffer.byteLength(content, "utf-8");
24+
return { lines, words, bytes };
25+
}
26+
27+
// Read every file and collect its stats
28+
const rows = [];
29+
for (const path of paths) {
30+
const content = await fs.readFile(path, "utf-8");
31+
rows.push({ path, stats: countStats(content) });
32+
}
33+
34+
// If there's more than one file, we also need a "total" row at the end
35+
if (rows.length > 1) {
36+
const total = { lines: 0, words: 0, bytes: 0 };
37+
for (const { stats } of rows) {
38+
total.lines += stats.lines;
39+
total.words += stats.words;
40+
total.bytes += stats.bytes;
41+
}
42+
rows.push({ path: "total", stats: total });
43+
}
44+
45+
const needsAlignment = columns.length > 1 || rows.length > 1;
46+
47+
let width = 0;
48+
if (needsAlignment) {
49+
for (const { stats } of rows) {
50+
for (const col of columns) {
51+
width = Math.max(width, String(stats[col]).length);
52+
}
53+
}
54+
55+
width = Math.max(width, 3);
56+
}
57+
58+
for (const { path, stats } of rows) {
59+
const parts = columns.map((col, index) => {
60+
const text = String(stats[col]);
61+
if (!needsAlignment) {
62+
return text;
63+
}
64+
const padded = text.padStart(width, " ");
65+
66+
return index === 0 ? padded : " " + padded;
67+
});
68+
69+
console.log(`${parts.join("")} ${path}`);
70+
}

0 commit comments

Comments
 (0)