Skip to content

Commit fb7ad53

Browse files
juanjoDiazknownasilya
authored andcommitted
fix: Streamify pretty print (#248)
1 parent bb8126f commit fb7ad53

File tree

9 files changed

+172
-55
lines changed

9 files changed

+172
-55
lines changed

bin/json2csv.js

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
const fs = require('fs');
66
const os = require('os');
77
const path = require('path');
8-
const Table = require('cli-table');
98
const program = require('commander');
109
const json2csv = require('../lib/json2csv');
11-
const parseNdJson = require('../lib/parse-ndjson');
10+
const parseNdJson = require('./utils/parseNdjson');
11+
const TablePrinter = require('./utils/TablePrinter');
1212
const pkg = require('../package');
1313

1414
const JSON2CSVParser = json2csv.Parser;
@@ -46,6 +46,9 @@ const inputPath = makePathAbsolute(program.input);
4646
const outputPath = makePathAbsolute(program.output);
4747
const fieldsConfigPath = makePathAbsolute(program.fieldsConfig);
4848

49+
program.delimiter = program.delimiter || ',';
50+
program.eol = program.eol || os.EOL;
51+
4952
// don't fail if piped to e.g. head
5053
/* istanbul ignore next */
5154
process.stdout.on('error', (error) => {
@@ -116,25 +119,10 @@ function getInputFromStdin() {
116119
});
117120
}
118121

119-
function logPretty(csv) {
120-
let lines = csv.split(os.EOL);
121-
const header = program.header ? lines.shift().split(',') : undefined;
122-
123-
const table = new Table(header ? {
124-
head: header,
125-
colWidths: header.map(elem => elem.length * 2)
126-
} : undefined);
127-
128-
lines.forEach(line => table.push(line.split(',')));
129-
130-
// eslint-disable-next-line no-console
131-
console.log(table.toString());
132-
}
133-
134122
function processOutput(csv) {
135123
if (!outputPath) {
136124
// eslint-disable-next-line no-console
137-
program.pretty ? logPretty(csv) : console.log(csv);
125+
program.pretty ? (new TablePrinter(program)).printCSV(csv) : console.log(csv);
138126
return;
139127
}
140128

@@ -203,18 +191,32 @@ Promise.resolve()
203191
input.on('error', reject);
204192
stream.on('error', reject);
205193
let csv = '';
194+
const table = new TablePrinter(program);
206195
stream
207-
.on('data', chunk => (csv += chunk.toString()))
208-
.on('end', () => resolve(csv))
196+
.on('data', chunk => {
197+
csv += chunk.toString();
198+
const index = csv.lastIndexOf(program.eol);
199+
let lines = csv.substring(0, index);
200+
csv = csv.substring(index + 1);
201+
202+
if (lines) {
203+
table.push(lines);
204+
}
205+
206+
})
207+
.on('end', () => {
208+
table.end(csv);
209+
resolve();
210+
})
209211
.on('error', reject);
210-
}).then(logPretty);
212+
});
211213
})
212214
.catch((err) => {
213-
if (err.message.indexOf(inputPath) !== -1) {
215+
if (inputPath && err.message.indexOf(inputPath) !== -1) {
214216
err = new Error('Invalid input file. (' + err.message + ')');
215-
} else if (err.message.indexOf(outputPath) !== -1) {
217+
} else if (outputPath && err.message.indexOf(outputPath) !== -1) {
216218
err = new Error('Invalid output file. (' + err.message + ')');
217-
} else if (err.message.indexOf(fieldsConfigPath) !== -1) {
219+
} else if (fieldsConfigPath && err.message.indexOf(fieldsConfigPath) !== -1) {
218220
err = new Error('Invalid fields config file. (' + err.message + ')');
219221
}
220222
// eslint-disable-next-line no-console

bin/utils/TablePrinter.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use strict';
2+
3+
const Table = require('cli-table2');
4+
5+
const MIN_CELL_WIDTH = 15;
6+
7+
class TablePrinter {
8+
constructor(params) {
9+
this.params = params;
10+
this._hasWritten = false;
11+
this.colWidths;
12+
}
13+
14+
push(csv) {
15+
const lines = csv.split(this.params.eol);
16+
17+
const chars = {
18+
'bottom': '',
19+
'bottom-mid': '',
20+
'bottom-left': '',
21+
'bottom-right': ''
22+
};
23+
if (!this._hasWritten) {
24+
this.colWidths = this.getColumnWidths(lines[0]);
25+
if (this.params.header) {
26+
const head = lines.shift().split(this.params.delimiter);
27+
const table = new Table({ head, colWidths: this.colWidths, chars });
28+
this.print(table, []);
29+
this._hasWritten = true;
30+
}
31+
} else {
32+
chars['top-left'] = '├';
33+
chars['top-mid'] = '┼';
34+
chars['top-right'] = '┤';
35+
}
36+
37+
if (!lines.length) return;
38+
39+
const table = new Table({ colWidths: this.colWidths, chars });
40+
this.print(table, lines);
41+
this._hasWritten = true;
42+
}
43+
44+
end(csv) {
45+
const lines = csv.split(this.params.eol);
46+
const chars = { 'top-left': '├' , 'top-mid': '┼', 'top-right': '┤' };
47+
const table = new Table({ colWidths: this.colWidths, chars });
48+
this.print(table, lines);
49+
}
50+
51+
printCSV(csv) {
52+
let lines = csv.split(this.params.eol);
53+
54+
this.colWidths = this.getColumnWidths(lines[0]);
55+
const head = this.params.header
56+
? lines.shift().split(this.params.delimiter)
57+
: undefined;
58+
59+
const table = new Table(head
60+
? { head, colWidths: this.colWidths }
61+
: { colWidths: this.colWidths });
62+
63+
this.print(table, lines);
64+
}
65+
66+
getColumnWidths(line) {
67+
return line
68+
.split(this.params.delimiter)
69+
.map(elem => Math.max(elem.length * 2, MIN_CELL_WIDTH));
70+
}
71+
72+
print(table, lines) {
73+
lines.forEach(line => table.push(line.split(this.params.delimiter)));
74+
// eslint-disable-next-line no-console
75+
console.log(table.toString());
76+
}
77+
}
78+
79+
module.exports = TablePrinter;
File renamed without changes.

package-lock.json

Lines changed: 21 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"release": "standard-version"
3838
},
3939
"dependencies": {
40-
"cli-table": "^0.3.1",
40+
"cli-table2": "^0.2.0",
4141
"commander": "^2.8.1",
4242
"flat": "^4.0.0",
4343
"jsonparse": "^1.3.1",

test/CLI.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,9 +705,20 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => {
705705
});
706706
});
707707

708+
testRunner.add('should print pretty table without header', (t) => {
709+
const opts = ' --no-header --pretty';
710+
711+
child_process.exec(cli + '-i ' + getFixturePath('/json/default.json') + opts, (err, stdout, stderr) => {
712+
t.notOk(stderr);
713+
const csv = stdout;
714+
t.equal(csv, csvFixtures.prettyprintWithoutHeader);
715+
t.end();
716+
});
717+
});
718+
708719
testRunner.add('should print pretty table without streaming', (t) => {
709720
const opts = ' --fields carModel,price,color'
710-
+ ' --pretty --no-streaming';
721+
+ ' --no-streaming --pretty ';
711722

712723
child_process.exec(cli + '-i ' + getFixturePath('/json/default.json') + opts, (err, stdout, stderr) => {
713724
t.notOk(stderr);
@@ -716,5 +727,17 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => {
716727
t.end();
717728
});
718729
});
730+
731+
testRunner.add('should print pretty table without streaming and without header', (t) => {
732+
const opts = ' --fields carModel,price,color'
733+
+ ' --no-streaming --no-header --pretty ';
734+
735+
child_process.exec(cli + '-i ' + getFixturePath('/json/default.json') + opts, (err, stdout, stderr) => {
736+
t.notOk(stderr);
737+
const csv = stdout;
738+
t.equal(csv, csvFixtures.prettyprintWithoutHeader);
739+
t.end();
740+
});
741+
});
719742
};
720743

test/fixtures/csv/prettyprint.txt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
┌────────────────────┬────────────────────────────┐
2-
│ "carModel" │ "price" │ "color" │
3-
├────────────────────┼────────────────────────────┤
4-
│ "Audi" │ 0 │ "blue" │
5-
├────────────────────┼────────────────────────────┤
6-
│ "BMW" │ 15000 │ "red" │
7-
├────────────────────┼────────────────────────────┤
8-
│ "Mercedes" │ 20000 │ "yellow" │
9-
├────────────────────┼────────────────────────────┤
10-
│ "Porsche" │ 30000 │ "green" │
11-
└────────────────────┴────────────────────────────┘
1+
┌────────────────────┬───────────────┬───────────────┐
2+
│ "carModel" │ "price" │ "color"
3+
├────────────────────┼───────────────┼───────────────┤
4+
│ "Audi" │ 0 │ "blue"
5+
├────────────────────┼───────────────┼───────────────┤
6+
│ "BMW" │ 15000 │ "red"
7+
├────────────────────┼───────────────┼───────────────┤
8+
│ "Mercedes" │ 20000 │ "yellow"
9+
├────────────────────┼───────────────┼───────────────┤
10+
│ "Porsche" │ 30000 │ "green"
11+
└────────────────────┴───────────────┴───────────────┘
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
┌───────────────┬───────────────┬───────────────┐
2+
│ "Audi" │ 0 │ "blue" │
3+
├───────────────┼───────────────┼───────────────┤
4+
│ "BMW" │ 15000 │ "red" │
5+
├───────────────┼───────────────┼───────────────┤
6+
│ "Mercedes" │ 20000 │ "yellow" │
7+
├───────────────┼───────────────┼───────────────┤
8+
│ "Porsche" │ 30000 │ "green" │
9+
└───────────────┴───────────────┴───────────────┘

test/parseNdjson.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const parsendjson = require('../lib/parse-ndjson');
3+
const parsendjson = require('../bin/utils/parseNdjson');
44

55
module.exports = (testRunner, jsonFixtures) => {
66
testRunner.add('should parse line-delimited JSON', (t) => {

0 commit comments

Comments
 (0)