diff --git a/bin/json2csv.js b/bin/json2csv.js index 15221c93..d45d21f8 100755 --- a/bin/json2csv.js +++ b/bin/json2csv.js @@ -5,10 +5,10 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); -const Table = require('cli-table'); const program = require('commander'); const json2csv = require('../lib/json2csv'); -const parseNdJson = require('../lib/parse-ndjson'); +const parseNdJson = require('./utils/parseNdjson'); +const TablePrinter = require('./utils/TablePrinter'); const pkg = require('../package'); const JSON2CSVParser = json2csv.Parser; @@ -46,6 +46,9 @@ const inputPath = makePathAbsolute(program.input); const outputPath = makePathAbsolute(program.output); const fieldsConfigPath = makePathAbsolute(program.fieldsConfig); +program.delimiter = program.delimiter || ','; +program.eol = program.eol || os.EOL; + // don't fail if piped to e.g. head /* istanbul ignore next */ process.stdout.on('error', (error) => { @@ -116,25 +119,10 @@ function getInputFromStdin() { }); } -function logPretty(csv) { - let lines = csv.split(os.EOL); - const header = program.header ? lines.shift().split(',') : undefined; - - const table = new Table(header ? { - head: header, - colWidths: header.map(elem => elem.length * 2) - } : undefined); - - lines.forEach(line => table.push(line.split(','))); - - // eslint-disable-next-line no-console - console.log(table.toString()); -} - function processOutput(csv) { if (!outputPath) { // eslint-disable-next-line no-console - program.pretty ? logPretty(csv) : console.log(csv); + program.pretty ? (new TablePrinter(program)).printCSV(csv) : console.log(csv); return; } @@ -203,18 +191,32 @@ Promise.resolve() input.on('error', reject); stream.on('error', reject); let csv = ''; + const table = new TablePrinter(program); stream - .on('data', chunk => (csv += chunk.toString())) - .on('end', () => resolve(csv)) + .on('data', chunk => { + csv += chunk.toString(); + const index = csv.lastIndexOf(program.eol); + let lines = csv.substring(0, index); + csv = csv.substring(index + 1); + + if (lines) { + table.push(lines); + } + + }) + .on('end', () => { + table.end(csv); + resolve(); + }) .on('error', reject); - }).then(logPretty); + }); }) .catch((err) => { - if (err.message.indexOf(inputPath) !== -1) { + if (inputPath && err.message.indexOf(inputPath) !== -1) { err = new Error('Invalid input file. (' + err.message + ')'); - } else if (err.message.indexOf(outputPath) !== -1) { + } else if (outputPath && err.message.indexOf(outputPath) !== -1) { err = new Error('Invalid output file. (' + err.message + ')'); - } else if (err.message.indexOf(fieldsConfigPath) !== -1) { + } else if (fieldsConfigPath && err.message.indexOf(fieldsConfigPath) !== -1) { err = new Error('Invalid fields config file. (' + err.message + ')'); } // eslint-disable-next-line no-console diff --git a/bin/utils/TablePrinter.js b/bin/utils/TablePrinter.js new file mode 100644 index 00000000..7e2804fd --- /dev/null +++ b/bin/utils/TablePrinter.js @@ -0,0 +1,79 @@ +'use strict'; + +const Table = require('cli-table2'); + +const MIN_CELL_WIDTH = 15; + +class TablePrinter { + constructor(params) { + this.params = params; + this._hasWritten = false; + this.colWidths; + } + + push(csv) { + const lines = csv.split(this.params.eol); + + const chars = { + 'bottom': '', + 'bottom-mid': '', + 'bottom-left': '', + 'bottom-right': '' + }; + if (!this._hasWritten) { + this.colWidths = this.getColumnWidths(lines[0]); + if (this.params.header) { + const head = lines.shift().split(this.params.delimiter); + const table = new Table({ head, colWidths: this.colWidths, chars }); + this.print(table, []); + this._hasWritten = true; + } + } else { + chars['top-left'] = '├'; + chars['top-mid'] = '┼'; + chars['top-right'] = '┤'; + } + + if (!lines.length) return; + + const table = new Table({ colWidths: this.colWidths, chars }); + this.print(table, lines); + this._hasWritten = true; + } + + end(csv) { + const lines = csv.split(this.params.eol); + const chars = { 'top-left': '├' , 'top-mid': '┼', 'top-right': '┤' }; + const table = new Table({ colWidths: this.colWidths, chars }); + this.print(table, lines); + } + + printCSV(csv) { + let lines = csv.split(this.params.eol); + + this.colWidths = this.getColumnWidths(lines[0]); + const head = this.params.header + ? lines.shift().split(this.params.delimiter) + : undefined; + + const table = new Table(head + ? { head, colWidths: this.colWidths } + : { colWidths: this.colWidths }); + + this.print(table, lines); + } + + getColumnWidths(line) { + return line + .split(this.params.delimiter) + .map(elem => Math.max(elem.length * 2, MIN_CELL_WIDTH)); + } + + print(table, lines) { + lines.forEach(line => table.push(line.split(this.params.delimiter))); + // eslint-disable-next-line no-console + console.log(table.toString()); + } +} + +module.exports = TablePrinter; \ No newline at end of file diff --git a/lib/parse-ndjson.js b/bin/utils/parseNdjson.js similarity index 100% rename from lib/parse-ndjson.js rename to bin/utils/parseNdjson.js diff --git a/package-lock.json b/package-lock.json index ff1704cf..cadc72b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -132,8 +132,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "2.2.1", @@ -935,12 +934,21 @@ "restore-cursor": "2.0.0" } }, - "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "cli-table2": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cli-table2/-/cli-table2-0.2.0.tgz", + "integrity": "sha1-LR738hig54biFFQFYtS9F3/jLZc=", "requires": { - "colors": "1.0.3" + "colors": "1.1.2", + "lodash": "3.10.1", + "string-width": "1.0.2" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + } } }, "cli-width": { @@ -1033,8 +1041,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "coffee-script": { "version": "1.12.7", @@ -1058,9 +1065,10 @@ "dev": true }, "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "optional": true }, "combine-source-map": { "version": "0.8.0", @@ -4560,7 +4568,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -5952,8 +5959,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nyc": { "version": "11.4.1", @@ -9476,7 +9482,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -9522,7 +9527,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "2.1.1" } diff --git a/package.json b/package.json index a0dac435..fe79eb7f 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "release": "standard-version" }, "dependencies": { - "cli-table": "^0.3.1", + "cli-table2": "^0.2.0", "commander": "^2.8.1", "flat": "^4.0.0", "jsonparse": "^1.3.1", diff --git a/test/CLI.js b/test/CLI.js index be955b61..d6a0fd1e 100644 --- a/test/CLI.js +++ b/test/CLI.js @@ -705,9 +705,20 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => { }); }); + testRunner.add('should print pretty table without header', (t) => { + const opts = ' --no-header --pretty'; + + child_process.exec(cli + '-i ' + getFixturePath('/json/default.json') + opts, (err, stdout, stderr) => { + t.notOk(stderr); + const csv = stdout; + t.equal(csv, csvFixtures.prettyprintWithoutHeader); + t.end(); + }); + }); + testRunner.add('should print pretty table without streaming', (t) => { const opts = ' --fields carModel,price,color' - + ' --pretty --no-streaming'; + + ' --no-streaming --pretty '; child_process.exec(cli + '-i ' + getFixturePath('/json/default.json') + opts, (err, stdout, stderr) => { t.notOk(stderr); @@ -716,5 +727,17 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => { t.end(); }); }); + + testRunner.add('should print pretty table without streaming and without header', (t) => { + const opts = ' --fields carModel,price,color' + + ' --no-streaming --no-header --pretty '; + + child_process.exec(cli + '-i ' + getFixturePath('/json/default.json') + opts, (err, stdout, stderr) => { + t.notOk(stderr); + const csv = stdout; + t.equal(csv, csvFixtures.prettyprintWithoutHeader); + t.end(); + }); + }); }; diff --git a/test/fixtures/csv/prettyprint.txt b/test/fixtures/csv/prettyprint.txt index 58a66a4f..0c167905 100644 --- a/test/fixtures/csv/prettyprint.txt +++ b/test/fixtures/csv/prettyprint.txt @@ -1,11 +1,11 @@ -┌────────────────────┬──────────────┬──────────────┐ -│ "carModel" │ "price" │ "color" │ -├────────────────────┼──────────────┼──────────────┤ -│ "Audi" │ 0 │ "blue" │ -├────────────────────┼──────────────┼──────────────┤ -│ "BMW" │ 15000 │ "red" │ -├────────────────────┼──────────────┼──────────────┤ -│ "Mercedes" │ 20000 │ "yellow" │ -├────────────────────┼──────────────┼──────────────┤ -│ "Porsche" │ 30000 │ "green" │ -└────────────────────┴──────────────┴──────────────┘ +┌────────────────────┬───────────────┬───────────────┐ +│ "carModel" │ "price" │ "color" │ +├────────────────────┼───────────────┼───────────────┤ +│ "Audi" │ 0 │ "blue" │ +├────────────────────┼───────────────┼───────────────┤ +│ "BMW" │ 15000 │ "red" │ +├────────────────────┼───────────────┼───────────────┤ +│ "Mercedes" │ 20000 │ "yellow" │ +├────────────────────┼───────────────┼───────────────┤ +│ "Porsche" │ 30000 │ "green" │ +└────────────────────┴───────────────┴───────────────┘ diff --git a/test/fixtures/csv/prettyprintWithoutHeader.txt b/test/fixtures/csv/prettyprintWithoutHeader.txt new file mode 100644 index 00000000..ec3e8f91 --- /dev/null +++ b/test/fixtures/csv/prettyprintWithoutHeader.txt @@ -0,0 +1,9 @@ +┌───────────────┬───────────────┬───────────────┐ +│ "Audi" │ 0 │ "blue" │ +├───────────────┼───────────────┼───────────────┤ +│ "BMW" │ 15000 │ "red" │ +├───────────────┼───────────────┼───────────────┤ +│ "Mercedes" │ 20000 │ "yellow" │ +├───────────────┼───────────────┼───────────────┤ +│ "Porsche" │ 30000 │ "green" │ +└───────────────┴───────────────┴───────────────┘ diff --git a/test/parseNdjson.js b/test/parseNdjson.js index 4198b267..bb770fba 100644 --- a/test/parseNdjson.js +++ b/test/parseNdjson.js @@ -1,6 +1,6 @@ 'use strict'; -const parsendjson = require('../lib/parse-ndjson'); +const parsendjson = require('../bin/utils/parseNdjson'); module.exports = (testRunner, jsonFixtures) => { testRunner.add('should parse line-delimited JSON', (t) => {