Skip to content

Commit ec5f956

Browse files
committed
add basic cat implementation in NodeJS
1 parent 407b010 commit ec5f956

File tree

1 file changed

+135
-0
lines changed
  • implement-shell-tools/cat

1 file changed

+135
-0
lines changed

implement-shell-tools/cat/cat.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env node
2+
3+
// A simple reimplementation of `cat` in NodeJS.
4+
// Supports:
5+
// node cat.js sample-files/1.txt
6+
// node cat.js -n sample-files/1.txt
7+
// node cat.js -b sample-files/3.txt
8+
// node cat.js sample-files/*.txt
9+
//
10+
// -n : number all lines
11+
// -b : number non-empty lines
12+
13+
const fs = require("fs");
14+
const path = require("path");
15+
16+
// -------- argument parsing --------
17+
const args = process.argv.slice(2);
18+
19+
if (args.length === 0) {
20+
console.error("Usage: node cat.js [-n | -b] <file> [<file> ...]");
21+
process.exit(1);
22+
}
23+
24+
let numberAll = false;
25+
let numberNonBlank = false;
26+
let fileArgs = [];
27+
28+
// Very small argument parser:
29+
// We only care about -n or -b as the FIRST arg (like the coursework examples).
30+
if (args[0] === "-n") {
31+
numberAll = true;
32+
fileArgs = args.slice(1);
33+
} else if (args[0] === "-b") {
34+
numberNonBlank = true;
35+
fileArgs = args.slice(1);
36+
} else {
37+
fileArgs = args;
38+
}
39+
40+
if (fileArgs.length === 0) {
41+
console.error("cat.js: no input files");
42+
process.exit(1);
43+
}
44+
45+
// -------- helper functions --------
46+
47+
/**
48+
* Format a single line with the correct line number, if needed.
49+
*
50+
* @param {string} line - line text (without the trailing newline)
51+
* @param {number} currentLineNumber - global line counter
52+
* @param {boolean} numberAll - true if -n
53+
* @param {boolean} numberNonBlank - true if -b
54+
* @returns {{ text: string, nextLineNumber: number }}
55+
*/
56+
function formatLine(line, currentLineNumber, numberAll, numberNonBlank) {
57+
let output = "";
58+
let nextLineNumber = currentLineNumber;
59+
60+
const isBlank = line === "";
61+
62+
if (numberAll) {
63+
// Always number every line
64+
output =
65+
currentLineNumber.toString().padStart(6, " ") + "\t" + line;
66+
nextLineNumber++;
67+
} else if (numberNonBlank) {
68+
// Number only non-blank lines
69+
if (isBlank) {
70+
output = line; // No number, just the blank line
71+
} else {
72+
output =
73+
currentLineNumber.toString().padStart(6, " ") + "\t" + line;
74+
nextLineNumber++;
75+
}
76+
} else {
77+
// No numbering
78+
output = line;
79+
}
80+
81+
return { text: output, nextLineNumber };
82+
}
83+
84+
/**
85+
* Print a file to stdout according to the options.
86+
*
87+
* @param {string} filePath
88+
* @param {number} startLineNumber
89+
* @returns {number} next line number
90+
*/
91+
function printFile(filePath, startLineNumber) {
92+
let content;
93+
94+
try {
95+
content = fs.readFileSync(filePath, "utf8");
96+
} catch (err) {
97+
console.error(`cat.js: ${filePath}: ${err.message}`);
98+
return startLineNumber;
99+
}
100+
101+
const lines = content.split("\n");
102+
let lineNumber = startLineNumber;
103+
104+
for (let i = 0; i < lines.length; i++) {
105+
const line = lines[i];
106+
const { text, nextLineNumber } = formatLine(
107+
line,
108+
lineNumber,
109+
numberAll,
110+
numberNonBlank
111+
);
112+
lineNumber = nextLineNumber;
113+
114+
// Re-add the newline we lost when splitting
115+
process.stdout.write(text);
116+
117+
// Avoid adding an extra newline at very end if the file
118+
// doesn't end with \n — Node's split keeps the last segment.
119+
if (i < lines.length - 1 || content.endsWith("\n")) {
120+
process.stdout.write("\n");
121+
}
122+
}
123+
124+
return lineNumber;
125+
}
126+
127+
// -------- main execution --------
128+
129+
let globalLineNumber = 1;
130+
131+
for (const file of fileArgs) {
132+
// Shell expands *.txt, so here we just get each file path.
133+
const resolvedPath = path.resolve(file);
134+
globalLineNumber = printFile(resolvedPath, globalLineNumber);
135+
}

0 commit comments

Comments
 (0)