Skip to content

Commit 9f4f7aa

Browse files
committed
Refactor cat and wc tools: improve line handling and output formatting
1 parent a3dc8ae commit 9f4f7aa

2 files changed

Lines changed: 64 additions & 24 deletions

File tree

implement-shell-tools/cat/cat.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/usr/bin/env node
22

33
const fs = require('fs');
4-
const path = require('path');
54

65
function cat(files, options) {
76
let lineNumber = 1;
@@ -10,16 +9,23 @@ function cat(files, options) {
109
try {
1110
const data = fs.readFileSync(file, 'utf8');
1211
const lines = data.split('\n');
12+
const hasTrailingNewline = data.endsWith('\n');
13+
14+
lines.forEach((line, index) => {
15+
const isVirtualTrailingLine = hasTrailingNewline && index === lines.length - 1;
16+
if (isVirtualTrailingLine) {
17+
return;
18+
}
1319

14-
lines.forEach((line) => {
1520
let prefix = '';
16-
if (options.numberNonEmpty && line.trim()) {
17-
prefix = `${lineNumber}\t`;
18-
} else if (options.numberLines) {
19-
prefix = `${lineNumber}\t`;
21+
const shouldNumberLine = options.numberLines || (options.numberNonEmpty && line.length > 0);
22+
if (shouldNumberLine) {
23+
prefix = `${String(lineNumber).padStart(6)}\t`;
24+
lineNumber++;
2025
}
21-
console.log(`${prefix}${line}`);
22-
if (prefix) lineNumber++;
26+
27+
const shouldAppendNewline = hasTrailingNewline || index < lines.length - 1;
28+
process.stdout.write(`${prefix}${line}${shouldAppendNewline ? '\n' : ''}`);
2329
});
2430
} catch (err) {
2531
if (err.code === 'ENOENT') {

implement-shell-tools/wc/wc.js

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,55 @@
22

33
const fs = require('fs');
44

5-
function countFile(file, options) {
5+
function countFile(file) {
66
try {
77
const data = fs.readFileSync(file, 'utf8');
88

9-
const lines = data.split('\n').length;
9+
const lines = (data.match(/\n/g) || []).length;
1010
const words = data.split(/\s+/).filter(Boolean).length;
1111
const bytes = Buffer.byteLength(data, 'utf8');
1212

13-
const results = [];
14-
if (options.lines || (!options.lines && !options.words && !options.bytes)) results.push(lines);
15-
if (options.words || (!options.lines && !options.words && !options.bytes)) results.push(words);
16-
if (options.bytes || (!options.lines && !options.words && !options.bytes)) results.push(bytes);
17-
18-
console.log(`${results.join('\t')}\t${file}`);
19-
2013
return { lines, words, bytes };
2114
} catch (err) {
2215
console.error(`wc: ${file}: ${err.code === 'ENOENT' ? 'No such file or directory' : 'An error occurred'}`);
2316
process.exit(1);
2417
}
2518
}
2619

20+
function selectedKeys(options) {
21+
const keys = [];
22+
if (options.lines) keys.push('lines');
23+
if (options.words) keys.push('words');
24+
if (options.bytes) keys.push('bytes');
25+
return keys;
26+
}
27+
28+
function valuesForKeys(counts, keys) {
29+
return keys.map((key) => counts[key]);
30+
}
31+
32+
function printRows(rows, keys) {
33+
const alignColumns = keys.length > 1 || rows.length > 1;
34+
35+
if (!alignColumns) {
36+
const [values, name] = rows[0];
37+
console.log(`${values[0]} ${name}`);
38+
return;
39+
}
40+
41+
const widths = keys.map((_, index) => {
42+
const maxLen = Math.max(...rows.map(([values]) => String(values[index]).length));
43+
return Math.max(3, maxLen);
44+
});
45+
46+
rows.forEach(([values, name]) => {
47+
const formattedValues = values
48+
.map((value, index) => String(value).padStart(widths[index], ' '))
49+
.join(' ');
50+
console.log(`${formattedValues} ${name}`);
51+
});
52+
}
53+
2754
function main() {
2855
const args = process.argv.slice(2);
2956
const options = {
@@ -46,6 +73,12 @@ function main() {
4673
}
4774
});
4875

76+
if (!options.lines && !options.words && !options.bytes) {
77+
options.lines = true;
78+
options.words = true;
79+
options.bytes = true;
80+
}
81+
4982
if (files.length === 0) {
5083
console.error('Usage: wc [-l | -w | -c] <file>...');
5184
process.exit(1);
@@ -54,22 +87,23 @@ function main() {
5487
let totalLines = 0;
5588
let totalWords = 0;
5689
let totalBytes = 0;
90+
const keys = selectedKeys(options);
91+
const rows = [];
5792

5893
files.forEach((file) => {
59-
const { lines, words, bytes } = countFile(file, options);
94+
const counts = countFile(file);
95+
const { lines, words, bytes } = counts;
6096
totalLines += lines;
6197
totalWords += words;
6298
totalBytes += bytes;
99+
rows.push([valuesForKeys(counts, keys), file]);
63100
});
64101

65102
if (files.length > 1) {
66-
const totalResults = [];
67-
if (options.lines || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalLines);
68-
if (options.words || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalWords);
69-
if (options.bytes || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalBytes);
70-
71-
console.log(`${totalResults.join('\t')}\ttotal`);
103+
rows.push([valuesForKeys({ lines: totalLines, words: totalWords, bytes: totalBytes }, keys), 'total']);
72104
}
105+
106+
printRows(rows, keys);
73107
}
74108

75109
main();

0 commit comments

Comments
 (0)