From 3e99c53b9fffad4593ddf93366a4a44e1b37caff Mon Sep 17 00:00:00 2001 From: doug-martin Date: Fri, 27 Dec 2019 00:04:06 -0600 Subject: [PATCH 1/2] Added writeHeaders option * [ADDED] `writeHeaders` to `@fast-csv/format` option to prevent writing headers. * This makes appending to a csv easier and safer because you can still specify headers without writing them. --- History.md | 5 + examples/formatting-js/README.md | 193 +++++++++++++----- .../formatting-js/examples/append.example.js | 108 +++++----- .../write_headers_auto_discover.example.js | 17 ++ .../write_headers_provided_headers.example.js | 17 ++ examples/formatting-ts/README.md | 193 +++++++++++++----- .../formatting-ts/examples/append.example.ts | 115 +++++++---- .../write_headers_auto_discover.example.ts | 17 ++ .../write_headers_provided_headers.example.ts | 17 ++ lerna.json | 1 + package.json | 2 +- packages/format/README.md | 4 + .../format/__tests__/FormatterOptions.spec.ts | 34 +++ .../__tests__/formatter/RowFormatter.spec.ts | 17 +- packages/format/src/FormatterOptions.ts | 3 +- packages/format/src/formatter/RowFormatter.ts | 2 +- 16 files changed, 547 insertions(+), 198 deletions(-) create mode 100644 examples/formatting-js/examples/write_headers_auto_discover.example.js create mode 100644 examples/formatting-js/examples/write_headers_provided_headers.example.js create mode 100644 examples/formatting-ts/examples/write_headers_auto_discover.example.ts create mode 100644 examples/formatting-ts/examples/write_headers_provided_headers.example.ts diff --git a/History.md b/History.md index b622a027..01dad52d 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +# v4.0.2 + +* [ADDED] `writeHeaders` to `@fast-csv/format` option to prevent writing headers. + * This makes appending to a csv easier and safer because you can still specify headers without writing them. + # v4.0.1 * [FIXED] package.json homepage links diff --git a/examples/formatting-js/README.md b/examples/formatting-js/README.md index f9d78d58..4f350c2b 100644 --- a/examples/formatting-js/README.md +++ b/examples/formatting-js/README.md @@ -35,6 +35,9 @@ npm run example -- {example_name} * [Hash Array Rows](#headers-provided-hash-array) * [Object Rows - Reorder Columns](#headers-provided-object) * [Object Rows - Remove Columns](#headers-provided-object-remove-column) + * Write Headers + * [Auto Discovered Headers](#write-headers-auto-discover) + * [Provided Headers](#write-headers-provided-headers) * [`quoteColumns`](#examples-quote-columns) * [`quoteHeaders`](#examples-quote-headers) * [Transforming Rows](#examples-transforming) @@ -379,6 +382,77 @@ value3b value4b ``` +### Write Headers + +The `writeHeaders` option can be used to prevent writing headers, while still auto discovering them or providing them. + +The `writeHeaders` option can be useful when appending to a csv to prevent writing headers twice. See the [append example](#examples-appending) + + +**NOTE** When writing array rows and headers is set to `true` then the first row will be not be written. + + +[`examples/write_headers_auto_discover.example.js`](./examples/write_headers_auto_discover.example.js) + +In this example the auto discovered headers are not written. + +```sh +npm run example -- write_headers_auto_discover +``` + +```js +const csv = require('@fast-csv/format'); + +const csvStream = csv.format({ headers: true, writeHeaders: false }); + +csvStream.pipe(process.stdout).on('end', process.exit); + +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.end(); +``` + +Expected Output: +``` +value1a,value2a +value1a,value2a +value1a,value2a +value1a,value2a +``` + + +[`examples/write_headers_provided_headers.example.js`](./examples/write_headers_provided_headers.example.js) + +In this example the headers are provided to specify order of columns but they are **not** written. + +```sh +npm run example -- write_headers_provided_headers +``` + +```js +const csv = require('@fast-csv/format'); + +const csvStream = csv.format({ headers: ['header2', 'header1'], writeHeaders: false }); + +csvStream.pipe(process.stdout).on('end', process.exit); + +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.end(); +``` + +Expected Output: +``` +value2a,value1a +value2a,value1a +value2a,value1a +value2a,value1a +``` + --- ### `quoteColumns` @@ -740,57 +814,70 @@ In this example a new csv is created then appended to. ```javascript const path = require('path'); const fs = require('fs'); - -const write = (filestream, rows, options) => { - return new Promise((res, rej) => { - csv.writeToStream(filestream, rows, options) - .on('error', err => rej(err)) - .on('finish', () => res()); - }); -}; - -// create a new csv -const createCsv = (filePath, rows) => { - const csvFile = fs.createWriteStream(filePath); - return write(csvFile, rows, { headers: true, includeEndRowDelimiter: true }); -}; - -// append the rows to the csv -const appendToCsv = (filePath, rows = []) => { - const csvFile = fs.createWriteStream(filePath, { flags: 'a' }); - // notice how headers are set to false - return write(csvFile, rows, { headers: false }); -}; - -// read the file -const readFile = filePath => { - return new Promise((res, rej) => { - fs.readFile(filePath, (err, contents) => { - if (err) { - return rej(err); - } - return res(contents); +const csv = require('@fast-csv/format'); + +class CsvFile { + static write(filestream, rows, options) { + return new Promise((res, rej) => { + csv.writeToStream(filestream, rows, options) + .on('error', err => rej(err)) + .on('finish', () => res()); }); - }); -}; + } + + constructor(opts) { + this.headers = opts.headers; + this.path = opts.path; + this.writeOpts = { headers: this.headers, includeEndRowDelimiter: true }; + } + + create(rows) { + return CsvFile.write(fs.createWriteStream(this.path), rows, { ...this.writeOpts }); + } + + append(rows) { + return CsvFile.write(fs.createWriteStream(this.path, { flags: 'a' }), rows, { + ...this.writeOpts, + // dont write the headers when appending + writeHeaders: false, + }); + } + + read() { + return new Promise((res, rej) => { + fs.readFile(this.path, (err, contents) => { + if (err) { + return rej(err); + } + return res(contents); + }); + }); + } +} -const csvFilePath = path.resolve(__dirname, 'tmp', 'append.csv'); +const csvFile = new CsvFile({ + path: path.resolve(__dirname, 'append.tmp.csv'), + // headers to write + headers: ['c', 'b', 'a'], +}); // 1. create the csv -createCsv(csvFilePath, [ - { a: 'a1', b: 'b1', c: 'c1' }, - { a: 'a2', b: 'b2', c: 'c2' }, - { a: 'a3', b: 'b3', c: 'c3' }, -]) - .then(() => { - // 2. append to the csv - return appendToCsv(csvFilePath, [ +csvFile + .create([ + { a: 'a1', b: 'b1', c: 'c1' }, + { b: 'b2', a: 'a2', c: 'c2' }, + { a: 'a3', b: 'b3', c: 'c3' }, + ]) + // append rows to file + .then(() => + csvFile.append([ { a: 'a4', b: 'b4', c: 'c4' }, { a: 'a5', b: 'b5', c: 'c5' }, - { a: 'a6', b: 'b6', c: 'c6' }, - ]); - }) - .then(() => readFile(csvFilePath)) + ]), + ) + // append another row + .then(() => csvFile.append([{ a: 'a6', b: 'b6', c: 'c6' }])) + .then(() => csvFile.read()) .then(contents => { console.log(`${contents}`); }) @@ -798,18 +885,16 @@ createCsv(csvFilePath, [ console.error(err.stack); process.exit(1); }); - - ``` Expected output ``` -a,b,c -a1,b1,c1 -a2,b2,c2 -a3,b3,c3 -a4,b4,c4 -a5,b5,c5 -a6,b6,c6 +c,b,a +c1,b1,a1 +c2,b2,a2 +c3,b3,a3 +c4,b4,a4 +c5,b5,a5 +c6,b6,a6 ``` \ No newline at end of file diff --git a/examples/formatting-js/examples/append.example.js b/examples/formatting-js/examples/append.example.js index aef28bea..c1bbf63b 100644 --- a/examples/formatting-js/examples/append.example.js +++ b/examples/formatting-js/examples/append.example.js @@ -2,56 +2,68 @@ const path = require('path'); const fs = require('fs'); const csv = require('@fast-csv/format'); -const write = (filestream, rows, options) => { - return new Promise((res, rej) => { - csv.writeToStream(filestream, rows, options) - .on('error', err => rej(err)) - .on('finish', () => res()); - }); -}; +class CsvFile { + static write(filestream, rows, options) { + return new Promise((res, rej) => { + csv.writeToStream(filestream, rows, options) + .on('error', err => rej(err)) + .on('finish', () => res()); + }); + } -// create a new csv -const createCsv = (filePath, rows) => { - const csvFile = fs.createWriteStream(filePath); - return write(csvFile, rows, { headers: true, includeEndRowDelimiter: true }); -}; + constructor(opts) { + this.headers = opts.headers; + this.path = opts.path; + this.writeOpts = { headers: this.headers, includeEndRowDelimiter: true }; + } -// append the rows to the csv -const appendToCsv = (filePath, rows = []) => { - const csvFile = fs.createWriteStream(filePath, { flags: 'a' }); - // notice how headers are set to false - return write(csvFile, rows, { headers: false }); -}; + create(rows) { + return CsvFile.write(fs.createWriteStream(this.path), rows, { ...this.writeOpts }); + } -// read the file -const readFile = filePath => { - return new Promise((res, rej) => { - fs.readFile(filePath, (err, contents) => { - if (err) { - return rej(err); - } - return res(contents); + append(rows) { + return CsvFile.write(fs.createWriteStream(this.path, { flags: 'a' }), rows, { + ...this.writeOpts, + // dont write the headers when appending + writeHeaders: false, }); - }); -}; + } -const csvFilePath = path.resolve(__dirname, 'append.tmp.csv'); + read() { + return new Promise((res, rej) => { + fs.readFile(this.path, (err, contents) => { + if (err) { + return rej(err); + } + return res(contents); + }); + }); + } +} + +const csvFile = new CsvFile({ + path: path.resolve(__dirname, 'append.tmp.csv'), + // headers to write + headers: ['c', 'b', 'a'], +}); // 1. create the csv -createCsv(csvFilePath, [ - { a: 'a1', b: 'b1', c: 'c1' }, - { a: 'a2', b: 'b2', c: 'c2' }, - { a: 'a3', b: 'b3', c: 'c3' }, -]) - .then(() => { - // 2. append to the csv - return appendToCsv(csvFilePath, [ +csvFile + .create([ + { a: 'a1', b: 'b1', c: 'c1' }, + { b: 'b2', a: 'a2', c: 'c2' }, + { a: 'a3', b: 'b3', c: 'c3' }, + ]) + // append rows to file + .then(() => + csvFile.append([ { a: 'a4', b: 'b4', c: 'c4' }, { a: 'a5', b: 'b5', c: 'c5' }, - { a: 'a6', b: 'b6', c: 'c6' }, - ]); - }) - .then(() => readFile(csvFilePath)) + ]), + ) + // append another row + .then(() => csvFile.append([{ a: 'a6', b: 'b6', c: 'c6' }])) + .then(() => csvFile.read()) .then(contents => { console.log(`${contents}`); }) @@ -61,10 +73,10 @@ createCsv(csvFilePath, [ }); // Output: -// a,b,c -// a1,b1,c1 -// a2,b2,c2 -// a3,b3,c3 -// a4,b4,c4 -// a5,b5,c5 -// a6,b6,c6 +// c,b,a +// c1,b1,a1 +// c2,b2,a2 +// c3,b3,a3 +// c4,b4,a4 +// c5,b5,a5 +// c6,b6,a6 diff --git a/examples/formatting-js/examples/write_headers_auto_discover.example.js b/examples/formatting-js/examples/write_headers_auto_discover.example.js new file mode 100644 index 00000000..a172f794 --- /dev/null +++ b/examples/formatting-js/examples/write_headers_auto_discover.example.js @@ -0,0 +1,17 @@ +const csv = require('@fast-csv/format'); + +const csvStream = csv.format({ headers: true, writeHeaders: false }); + +csvStream.pipe(process.stdout).on('end', process.exit); + +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.end(); + +// Output: +// value1a,value2a +// value1a,value2a +// value1a,value2a +// value1a,value2a diff --git a/examples/formatting-js/examples/write_headers_provided_headers.example.js b/examples/formatting-js/examples/write_headers_provided_headers.example.js new file mode 100644 index 00000000..aadf0846 --- /dev/null +++ b/examples/formatting-js/examples/write_headers_provided_headers.example.js @@ -0,0 +1,17 @@ +const csv = require('@fast-csv/format'); + +const csvStream = csv.format({ headers: ['header2', 'header1'], writeHeaders: false }); + +csvStream.pipe(process.stdout).on('end', process.exit); + +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.end(); + +// Output: +// value2a,value1a +// value2a,value1a +// value2a,value1a +// value2a,value1a diff --git a/examples/formatting-ts/README.md b/examples/formatting-ts/README.md index efb511f4..453c80f8 100644 --- a/examples/formatting-ts/README.md +++ b/examples/formatting-ts/README.md @@ -39,6 +39,9 @@ npm run example -- {example_name} * [Hash Array Rows](#headers-provided-hash-array) * [Object Rows - Reorder Columns](#headers-provided-object) * [Object Rows - Remove Columns](#headers-provided-object-remove-column) + * Write Headers + * [Auto Discovered Headers](#write-headers-auto-discover) + * [Provided Headers](#write-headers-provided-headers) * [`quoteColumns`](#examples-quote-columns) * [`quoteHeaders`](#examples-quote-headers) * [Transforming Rows](#examples-transforming) @@ -415,6 +418,77 @@ value3b value4b ``` +### Write Headers + +The `writeHeaders` option can be used to prevent writing headers, while still auto discovering them or providing them. + +The `writeHeaders` option can be useful when appending to a csv to prevent writing headers twice. See the [append example](#examples-appending) + + +**NOTE** When writing array rows and headers is set to `true` then the first row will be not be written. + + +[`examples/write_headers_auto_discover.example.ts`](./examples/write_headers_auto_discover.example.ts) + +In this example the auto discovered headers are not written. + +```sh +npm run example -- write_headers_auto_discover +``` + +```typescript +import { format } from '@fast-csv/format'; + +const csvStream = format({ headers: true, writeHeaders: false }); + +csvStream.pipe(process.stdout).on('end', process.exit); + +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.end(); +``` + +Expected Output: +``` +value1a,value2a +value1a,value2a +value1a,value2a +value1a,value2a +``` + + +[`examples/write_headers_provided_headers.example.ts`](./examples/write_headers_provided_headers.example.ts) + +In this example the headers are provided to specify order of columns but they are **not** written. + +```sh +npm run example -- write_headers_provided_headers +``` + +```typescript +import { format } from '@fast-csv/format'; + +const csvStream = format({ headers: ['header2', 'header1'], writeHeaders: false }); + +csvStream.pipe(process.stdout).on('end', process.exit); + +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.end(); +``` + +Expected Output: +``` +value2a,value1a +value2a,value1a +value2a,value1a +value2a,value1a +``` + --- ### `quoteColumns` @@ -791,57 +865,82 @@ import * as path from 'path'; import * as fs from 'fs'; import { FormatterOptionsArgs, Row, writeToStream } from '@fast-csv/format'; -const write = (stream: NodeJS.WritableStream, rows: Row[], options: FormatterOptionsArgs): Promise => { - return new Promise((res, rej) => { - writeToStream(stream, rows, options) - .on('error', err => rej(err)) - .on('finish', () => res()); - }); -}; - -// create a new csv -const createCsv = (filePath: string, rows: Row[]): Promise => { - const csvFile = fs.createWriteStream(filePath); - return write(csvFile, rows, { headers: true, includeEndRowDelimiter: true }); +type CsvFileOpts = { + headers: string[]; + path: string; }; -// append the rows to the csv -const appendToCsv = (filePath: string, rows: Row[] = []): Promise => { - const csvFile = fs.createWriteStream(filePath, { flags: 'a' }); - // notice how headers are set to false - return write(csvFile, rows, { headers: false }); -}; - -// read the file -const readFile = (filePath: string): Promise => { - return new Promise((res, rej) => { - fs.readFile(filePath, (err, contents) => { - if (err) { - return rej(err); - } - return res(contents); +class CsvFile { + static write(stream: NodeJS.WritableStream, rows: Row[], options: FormatterOptionsArgs): Promise { + return new Promise((res, rej) => { + writeToStream(stream, rows, options) + .on('error', (err: Error) => rej(err)) + .on('finish', () => res()); }); - }); -}; + } + + private readonly headers: string[]; + + private readonly path: string; + + private readonly writeOpts: FormatterOptionsArgs; + + constructor(opts: CsvFileOpts) { + this.headers = opts.headers; + this.path = opts.path; + this.writeOpts = { headers: this.headers, includeEndRowDelimiter: true }; + } + + create(rows: Row[]): Promise { + return CsvFile.write(fs.createWriteStream(this.path), rows, { ...this.writeOpts }); + } + + append(rows: Row[]): Promise { + return CsvFile.write(fs.createWriteStream(this.path, { flags: 'a' }), rows, { + ...this.writeOpts, + // dont write the headers when appending + writeHeaders: false, + } as FormatterOptionsArgs); + } + + read(): Promise { + return new Promise((res, rej) => { + fs.readFile(this.path, (err, contents) => { + if (err) { + return rej(err); + } + return res(contents); + }); + }); + } +} -const csvFilePath = path.resolve(__dirname, 'tmp', 'append.csv'); +const csvFile = new CsvFile({ + path: path.resolve(__dirname, 'append.tmp.csv'), + // headers to write + headers: ['c', 'b', 'a'], +}); // 1. create the csv -createCsv(csvFilePath, [ - { a: 'a1', b: 'b1', c: 'c1' }, - { a: 'a2', b: 'b2', c: 'c2' }, - { a: 'a3', b: 'b3', c: 'c3' }, -]) - // 2. append to the csv +csvFile + .create([ + { a: 'a1', b: 'b1', c: 'c1' }, + { b: 'b2', a: 'a2', c: 'c2' }, + { a: 'a3', b: 'b3', c: 'c3' }, + ]) + // append rows to file .then(() => - appendToCsv(csvFilePath, [ + csvFile.append([ { a: 'a4', b: 'b4', c: 'c4' }, { a: 'a5', b: 'b5', c: 'c5' }, - { a: 'a6', b: 'b6', c: 'c6' }, ]), ) - .then(() => readFile(csvFilePath)) - .then(contents => console.log(`${contents}`)) + // append another row + .then(() => csvFile.append([{ a: 'a6', b: 'b6', c: 'c6' }])) + .then(() => csvFile.read()) + .then(contents => { + console.log(`${contents}`); + }) .catch(err => { console.error(err.stack); process.exit(1); @@ -851,11 +950,11 @@ createCsv(csvFilePath, [ Expected output ``` -a,b,c -a1,b1,c1 -a2,b2,c2 -a3,b3,c3 -a4,b4,c4 -a5,b5,c5 -a6,b6,c6 +c,b,a +c1,b1,a1 +c2,b2,a2 +c3,b3,a3 +c4,b4,a4 +c5,b5,a5 +c6,b6,a6 ``` \ No newline at end of file diff --git a/examples/formatting-ts/examples/append.example.ts b/examples/formatting-ts/examples/append.example.ts index 897a2307..215d9ede 100644 --- a/examples/formatting-ts/examples/append.example.ts +++ b/examples/formatting-ts/examples/append.example.ts @@ -2,67 +2,92 @@ import * as path from 'path'; import * as fs from 'fs'; import { FormatterOptionsArgs, Row, writeToStream } from '@fast-csv/format'; -const write = (stream: NodeJS.WritableStream, rows: Row[], options: FormatterOptionsArgs): Promise => { - return new Promise((res, rej) => { - writeToStream(stream, rows, options) - .on('error', err => rej(err)) - .on('finish', () => res()); - }); +type CsvFileOpts = { + headers: string[]; + path: string; }; -// create a new csv -const createCsv = (filePath: string, rows: Row[]): Promise => { - const csvFile = fs.createWriteStream(filePath); - return write(csvFile, rows, { headers: true, includeEndRowDelimiter: true }); -}; +class CsvFile { + static write(stream: NodeJS.WritableStream, rows: Row[], options: FormatterOptionsArgs): Promise { + return new Promise((res, rej) => { + writeToStream(stream, rows, options) + .on('error', (err: Error) => rej(err)) + .on('finish', () => res()); + }); + } -// append the rows to the csv -const appendToCsv = (filePath: string, rows: Row[] = []): Promise => { - const csvFile = fs.createWriteStream(filePath, { flags: 'a' }); - // notice how headers are set to false - return write(csvFile, rows, { headers: false }); -}; + private readonly headers: string[]; + + private readonly path: string; + + private readonly writeOpts: FormatterOptionsArgs; -// read the file -const readFile = (filePath: string): Promise => { - return new Promise((res, rej) => { - fs.readFile(filePath, (err, contents) => { - if (err) { - return rej(err); - } - return res(contents); + constructor(opts: CsvFileOpts) { + this.headers = opts.headers; + this.path = opts.path; + this.writeOpts = { headers: this.headers, includeEndRowDelimiter: true }; + } + + create(rows: Row[]): Promise { + return CsvFile.write(fs.createWriteStream(this.path), rows, { ...this.writeOpts }); + } + + append(rows: Row[]): Promise { + return CsvFile.write(fs.createWriteStream(this.path, { flags: 'a' }), rows, { + ...this.writeOpts, + // dont write the headers when appending + writeHeaders: false, + } as FormatterOptionsArgs); + } + + read(): Promise { + return new Promise((res, rej) => { + fs.readFile(this.path, (err, contents) => { + if (err) { + return rej(err); + } + return res(contents); + }); }); - }); -}; + } +} -const csvFilePath = path.resolve(__dirname, 'append.csv'); +const csvFile = new CsvFile({ + path: path.resolve(__dirname, 'append.tmp.csv'), + // headers to write + headers: ['c', 'b', 'a'], +}); // 1. create the csv -createCsv(csvFilePath, [ - { a: 'a1', b: 'b1', c: 'c1' }, - { a: 'a2', b: 'b2', c: 'c2' }, - { a: 'a3', b: 'b3', c: 'c3' }, -]) - // 2. append to the csv +csvFile + .create([ + { a: 'a1', b: 'b1', c: 'c1' }, + { b: 'b2', a: 'a2', c: 'c2' }, + { a: 'a3', b: 'b3', c: 'c3' }, + ]) + // append rows to file .then(() => - appendToCsv(csvFilePath, [ + csvFile.append([ { a: 'a4', b: 'b4', c: 'c4' }, { a: 'a5', b: 'b5', c: 'c5' }, - { a: 'a6', b: 'b6', c: 'c6' }, ]), ) - .then(() => readFile(csvFilePath)) - .then(contents => console.log(`${contents}`)) + // append another row + .then(() => csvFile.append([{ a: 'a6', b: 'b6', c: 'c6' }])) + .then(() => csvFile.read()) + .then(contents => { + console.log(`${contents}`); + }) .catch(err => { console.error(err.stack); process.exit(1); }); // Output: -// a,b,c -// a1,b1,c1 -// a2,b2,c2 -// a3,b3,c3 -// a4,b4,c4 -// a5,b5,c5 -// a6,b6,c6 +// c,b,a +// c1,b1,a1 +// c2,b2,a2 +// c3,b3,a3 +// c4,b4,a4 +// c5,b5,a5 +// c6,b6,a6 diff --git a/examples/formatting-ts/examples/write_headers_auto_discover.example.ts b/examples/formatting-ts/examples/write_headers_auto_discover.example.ts new file mode 100644 index 00000000..a214e5f9 --- /dev/null +++ b/examples/formatting-ts/examples/write_headers_auto_discover.example.ts @@ -0,0 +1,17 @@ +import { format } from '@fast-csv/format'; + +const csvStream = format({ headers: true, writeHeaders: false }); + +csvStream.pipe(process.stdout).on('end', process.exit); + +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.end(); + +// Output: +// value1a,value2a +// value1a,value2a +// value1a,value2a +// value1a,value2a diff --git a/examples/formatting-ts/examples/write_headers_provided_headers.example.ts b/examples/formatting-ts/examples/write_headers_provided_headers.example.ts new file mode 100644 index 00000000..755185a0 --- /dev/null +++ b/examples/formatting-ts/examples/write_headers_provided_headers.example.ts @@ -0,0 +1,17 @@ +import { format } from '@fast-csv/format'; + +const csvStream = format({ headers: ['header2', 'header1'], writeHeaders: false }); + +csvStream.pipe(process.stdout).on('end', process.exit); + +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.write({ header1: 'value1a', header2: 'value2a' }); +csvStream.end(); + +// Output: +// value2a,value1a +// value2a,value1a +// value2a,value1a +// value2a,value1a diff --git a/lerna.json b/lerna.json index d952a400..629f189d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,4 +1,5 @@ { + "tagVersionPrefix": "v", "packages": [ "examples/*", "packages/*" diff --git a/package.json b/package.json index 0bd24bc0..1fbaf319 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "clean": "lerna run clean", "build": "lerna run build", "pub": "lerna publish from-git", - "version": "npx lerna version --tag-version-prefix=v", + "version": "npx lerna version", "test": "npm run lint && npm run jest && npm run examples", "lint": "eslint --ext=.ts,.js .", "jest": "jest --coverage", diff --git a/packages/format/README.md b/packages/format/README.md index af3ecada..eb143f2b 100644 --- a/packages/format/README.md +++ b/packages/format/README.md @@ -58,6 +58,10 @@ import * as format csv '@fast-csv/format'; * If there is not a headers row and you want to provide one then set to a `string[]` * **NOTE** If the row is an object the headers must match fields in the object, otherwise you will end up with empty fields * **NOTE** If there are more headers than columns then additional empty columns will be added +* `writeHeaders: {boolean} = true`: Set to false you dont want to write headers. + * If `headers` is set to `true` and `writeHeaders` is `false` then any auto discovered headers will not be written in the output. + * If `headers` is an `array` and `writeHeaders` is `false` then they will not be written. + * **NOTE** This is can be used to append to an existing csv. * `alwaysWriteHeaders: {boolean} = false`: Set to true if you always want headers written, even if no rows are written. * **NOTE** This will throw an error if headers are not specified as an array. * `quoteColumns: {boolean|boolean[]|{[string]: boolean} = false` diff --git a/packages/format/__tests__/FormatterOptions.spec.ts b/packages/format/__tests__/FormatterOptions.spec.ts index d4e1b3b1..b4a259b2 100644 --- a/packages/format/__tests__/FormatterOptions.spec.ts +++ b/packages/format/__tests__/FormatterOptions.spec.ts @@ -141,6 +141,40 @@ describe('FormatterOptions', () => { }); }); + describe('#shouldWriteHeaders', () => { + it('should set to true if headers is true', () => { + expect(createOptions({ headers: true }).shouldWriteHeaders).toBe(true); + }); + + it('should set to false if headers is true and writeHeaders is false', () => { + expect(createOptions({ headers: true, writeHeaders: false }).shouldWriteHeaders).toBe(false); + }); + + it('should set to true if headers is true and writeHeaders is true', () => { + expect(createOptions({ headers: true, writeHeaders: true }).shouldWriteHeaders).toBe(true); + }); + + it('should set to true if headers is an array', () => { + expect(createOptions({ headers: ['h1', 'h2'] }).shouldWriteHeaders).toBe(true); + }); + + it('should set to false if headers is an array and writeHeaders is false', () => { + expect(createOptions({ headers: ['h1', 'h2'], writeHeaders: false }).shouldWriteHeaders).toBe(false); + }); + + it('should set to true if headers is an array and writeHeaders is true', () => { + expect(createOptions({ headers: ['h1', 'h2'], writeHeaders: true }).shouldWriteHeaders).toBe(true); + }); + + it('should set to false if headers is not defined', () => { + expect(createOptions({}).shouldWriteHeaders).toBe(false); + }); + + it('should set to false if headers is not defined and writeHeaders is true', () => { + expect(createOptions({ writeHeaders: true }).shouldWriteHeaders).toBe(false); + }); + }); + describe('#includeEndRowDelimiter', () => { it('should set includeEndRowDelimiter to false by default', () => { expect(createOptions().includeEndRowDelimiter).toBe(false); diff --git a/packages/format/__tests__/formatter/RowFormatter.spec.ts b/packages/format/__tests__/formatter/RowFormatter.spec.ts index 8c4f97ca..f3cb31b6 100644 --- a/packages/format/__tests__/formatter/RowFormatter.spec.ts +++ b/packages/format/__tests__/formatter/RowFormatter.spec.ts @@ -279,14 +279,24 @@ describe('RowFormatter', () => { const formatter = createFormatter({ headers: true }); await expect(formatRow(row, formatter)).resolves.toEqual(['a,b', '\na1,b1']); }); + + it('should not write the first row if writeHeaders is false', async () => { + const formatter = createFormatter({ headers: true, writeHeaders: false }); + await expect(formatRow(row, formatter)).resolves.toEqual(['a1,b1']); + }); }); describe('with headers provided', () => { - it('should the new headers and the row', async () => { + it('should write the provided headers and the row', async () => { const formatter = createFormatter({ headers: ['a', 'b'] }); await expect(formatRow(row, formatter)).resolves.toEqual(['a,b', '\na1,b1']); }); + it('should not write the header row if writeHeaders is false', async () => { + const formatter = createFormatter({ headers: ['a', 'b'], writeHeaders: false }); + await expect(formatRow(row, formatter)).resolves.toEqual(['a1,b1']); + }); + it('should respect the order of the columns', async () => { const formatter = createFormatter({ headers: ['b', 'a'] }); await expect(formatRow(row, formatter)).resolves.toEqual(['b,a', '\nb1,a1']); @@ -296,6 +306,11 @@ describe('RowFormatter', () => { const formatter = createFormatter({ headers: ['a', 'b', 'no_field'] }); await expect(formatRow(row, formatter)).resolves.toEqual(['a,b,no_field', '\na1,b1,']); }); + + it('should respect the order of the columns and not write the headers if writeHeaders is false', async () => { + const formatter = createFormatter({ headers: ['b', 'a'], writeHeaders: false }); + await expect(formatRow(row, formatter)).resolves.toEqual(['b1,a1']); + }); }); }); diff --git a/packages/format/src/FormatterOptions.ts b/packages/format/src/FormatterOptions.ts index 54e3ba19..17bb49bc 100644 --- a/packages/format/src/FormatterOptions.ts +++ b/packages/format/src/FormatterOptions.ts @@ -15,6 +15,7 @@ export interface FormatterOptionsArgs { quoteColumns?: QuoteColumns; quoteHeaders?: QuoteColumns; headers?: null | boolean | string[]; + writeHeaders?: boolean; includeEndRowDelimiter?: boolean; writeBOM?: boolean; transform?: RowTransformFunction; @@ -66,7 +67,7 @@ export class FormatterOptions { if (typeof opts?.escape !== 'string') { this.escape = this.quote; } - this.shouldWriteHeaders = !!this.headers; + this.shouldWriteHeaders = !!this.headers && (opts.writeHeaders ?? true); this.headers = Array.isArray(this.headers) ? this.headers : null; this.escapedQuote = `${this.escape}${this.quote}`; } diff --git a/packages/format/src/formatter/RowFormatter.ts b/packages/format/src/formatter/RowFormatter.ts index 5dcdbceb..19168bac 100644 --- a/packages/format/src/formatter/RowFormatter.ts +++ b/packages/format/src/formatter/RowFormatter.ts @@ -135,7 +135,7 @@ export class RowFormatter { this.fieldFormatter.headers = headers; if (!this.shouldWriteHeaders) { // if we are not supposed to write the headers then - // alwyas format the columns + // always format the columns return { shouldFormatColumns: true, headers: null }; } // if the row is equal to headers dont format From 66f519b5709a02d0e9992fc9318e3b7ca3b15664 Mon Sep 17 00:00:00 2001 From: doug-martin Date: Fri, 27 Dec 2019 16:52:34 -0600 Subject: [PATCH 2/2] Added directory to package.json --- packages/fast-csv/package.json | 3 ++- packages/format/package.json | 5 +++-- packages/parse/package.json | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/fast-csv/package.json b/packages/fast-csv/package.json index e8a1d86b..34d25134 100644 --- a/packages/fast-csv/package.json +++ b/packages/fast-csv/package.json @@ -18,7 +18,8 @@ ], "repository": { "type": "git", - "url": "git@github.com:C2FO/fast-csv.git" + "url": "git+https://github.com/C2FO/fast-csv.git", + "directory": "packages/fast-csv" }, "keywords": [ "csv", diff --git a/packages/format/package.json b/packages/format/package.json index d65142a1..449d790c 100644 --- a/packages/format/package.json +++ b/packages/format/package.json @@ -9,7 +9,7 @@ ], "author": "doug-martin ", "homepage": "http://c2fo.github.com/fast-csv/packages/format", - "license": "ISC", + "license": "MIT", "main": "build/src/index.js", "types": "build/src/index.d.ts", "directories": { @@ -24,7 +24,8 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/C2FO/fast-csv.git" + "url": "git+https://github.com/C2FO/fast-csv.git", + "directory": "packages/format" }, "scripts": { "build": "npm run clean && npm run compile", diff --git a/packages/parse/package.json b/packages/parse/package.json index b4d3db42..b2626af2 100644 --- a/packages/parse/package.json +++ b/packages/parse/package.json @@ -25,7 +25,8 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/C2FO/fast-csv.git" + "url": "git+https://github.com/C2FO/fast-csv.git", + "directory": "packages/parse" }, "scripts": { "build": "npm run clean && npm run compile",