csv-stringify
Advanced tools
Comparing version
# Changelog | ||
## Version 5.1.0 | ||
Fix: | ||
* header: ensure column definition | ||
New features | ||
* cast: pass context to functions | ||
Minor enhancements: | ||
* write: validate written records | ||
* src: extends stream class | ||
Project Management: | ||
* package: latest dev dependencies | ||
## Version 5.0.0 | ||
@@ -8,4 +27,4 @@ | ||
* cast: was formatters | ||
* record_delimiter: was row_delimiter | ||
* `cast`: was `formatters` | ||
* `record_delimiter`: was `row_delimiter` | ||
* options: instance options stored in underscore form | ||
@@ -16,3 +35,3 @@ * nodejs: drop support for version 7, use './lib/es5' | ||
* quoted_match: new option | ||
* `quoted_match`: new option | ||
* options: accept underscore and camelcase forms | ||
@@ -41,3 +60,3 @@ | ||
* formatter: new string formatter | ||
* `formatters`: new string formatter | ||
* stream: be a much better transform citizen | ||
@@ -48,3 +67,3 @@ * package: upgrade to babel 7 | ||
* columns: support array with column definition objects | ||
* `columns`: support array with column definition objects | ||
* travis: support Node.js 10 | ||
@@ -63,11 +82,11 @@ * samples: new formatters script | ||
* formatters: rename bool to boolean | ||
* `formatters`: rename bool to boolean | ||
New features: | ||
* formatters: handle number | ||
* `formatters`: handle number | ||
Cleanup | ||
* src: cache call to typeof | ||
* src: cache call to `typeof` | ||
* package: latest dependencies | ||
@@ -85,3 +104,3 @@ | ||
* Switch linebreak check for rowDelimiter check | ||
* Switch linebreak check for `rowDelimiter` check | ||
@@ -91,3 +110,3 @@ ## Version 2.1.0 | ||
* package: allow empty quote value | ||
* package: add ascii option for rowDelimiter | ||
* package: add ascii option for `rowDelimiter` | ||
@@ -123,2 +142,2 @@ ## Version 2.0.4 | ||
* test: should require handled by mocha | ||
* package: coffeescript 2 and use semver tilde | ||
* package: CoffeeScript 2 and use of semver tilde |
"use strict"; | ||
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } | ||
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } | ||
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } | ||
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } | ||
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } | ||
@@ -9,2 +17,18 @@ | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } | ||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } | ||
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } | ||
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } | ||
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } | ||
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } | ||
// Generated by CoffeeScript 2.3.2 | ||
@@ -14,4 +38,3 @@ // # CSV Stringifier | ||
// information. | ||
var _Stringifier, get, stream, underscore, util; | ||
var Stringifier, get, stream, underscore, util; | ||
stream = require('stream'); | ||
@@ -58,3 +81,3 @@ util = require('util'); | ||
stringifier = new _Stringifier(options); | ||
stringifier = new Stringifier(options); | ||
@@ -109,449 +132,559 @@ if (data) { | ||
_Stringifier = function Stringifier() { | ||
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var base, base1, base2, base3, base4, isRegExp, isString, j, k, len, options, quoted_match, ref, v; | ||
stream.Transform.call(this, _objectSpread({}, options, { | ||
writableObjectMode: true | ||
})); | ||
options = {}; | ||
Stringifier = | ||
/*#__PURE__*/ | ||
function (_stream$Transform) { | ||
_inherits(Stringifier, _stream$Transform); | ||
for (k in opts) { | ||
v = opts[k]; // Immutable options and camelcase conversion | ||
function Stringifier() { | ||
var _this; | ||
options[underscore(k)] = v; | ||
} //# Default options | ||
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
_classCallCheck(this, Stringifier); | ||
if (options.delimiter == null) { | ||
options.delimiter = ','; | ||
} | ||
var base, base1, base2, base3, base4, isRegExp, isString, j, k, len, options, quoted_match, ref, v; | ||
_this = _possibleConstructorReturn(this, _getPrototypeOf(Stringifier).call(this, _objectSpread({}, { | ||
writableObjectMode: true | ||
}, options))); | ||
options = {}; | ||
if (options.quote == null) { | ||
options.quote = '"'; | ||
} | ||
for (k in opts) { | ||
v = opts[k]; // Immutable options and camelcase conversion | ||
if (options.quoted == null) { | ||
options.quoted = false; | ||
} | ||
options[underscore(k)] = v; | ||
} //# Default options | ||
if (options.quoted_empty == null) { | ||
options.quoted_empty = void 0; | ||
} | ||
if (options.quoted_string == null) { | ||
options.quoted_string = false; | ||
} | ||
if (options.delimiter == null) { | ||
options.delimiter = ','; | ||
} | ||
if (options.eof == null) { | ||
options.eof = true; | ||
} | ||
if (options.quote == null) { | ||
options.quote = '"'; | ||
} | ||
if (options.escape == null) { | ||
options.escape = '"'; | ||
} | ||
if (options.quoted == null) { | ||
options.quoted = false; | ||
} | ||
if (options.header == null) { | ||
options.header = false; | ||
} // Normalize the columns option | ||
if (options.quoted_empty == null) { | ||
options.quoted_empty = void 0; | ||
} | ||
if (options.quoted_string == null) { | ||
options.quoted_string = false; | ||
} | ||
options.columns = _Stringifier.normalize_columns(options.columns); | ||
if (options.eof == null) { | ||
options.eof = true; | ||
} | ||
if (options.cast == null) { | ||
options.cast = {}; | ||
} // Normalize option `quoted_match` | ||
if (options.escape == null) { | ||
options.escape = '"'; | ||
} | ||
if (options.header == null) { | ||
options.header = false; | ||
} // Normalize the columns option | ||
if (options.quoted_match === void 0 || options.quoted_match === null || options.quoted_match === false) { | ||
options.quoted_match = null; | ||
} else if (!Array.isArray(options.quoted_match)) { | ||
options.quoted_match = [options.quoted_match]; | ||
} | ||
if (options.quoted_match) { | ||
ref = options.quoted_match; | ||
options.columns = _this.normalize_columns(options.columns); | ||
for (j = 0, len = ref.length; j < len; j++) { | ||
quoted_match = ref[j]; | ||
isString = typeof quoted_match === 'string'; | ||
isRegExp = quoted_match instanceof RegExp; | ||
if (options.cast == null) { | ||
options.cast = {}; | ||
} // Normalize option `quoted_match` | ||
if (!isString && !isRegExp) { | ||
throw Error("Invalid Option: quoted_match must be a string or a regex, got ".concat(JSON.stringify(quoted_match))); | ||
} | ||
if (options.quoted_match === void 0 || options.quoted_match === null || options.quoted_match === false) { | ||
options.quoted_match = null; | ||
} else if (!Array.isArray(options.quoted_match)) { | ||
options.quoted_match = [options.quoted_match]; | ||
} | ||
} | ||
if (options.cast.bool) { | ||
// Backward compatibility | ||
options.cast.boolean = options.cast.bool; | ||
} // Custom cast | ||
if (options.quoted_match) { | ||
ref = options.quoted_match; | ||
for (j = 0, len = ref.length; j < len; j++) { | ||
quoted_match = ref[j]; | ||
isString = typeof quoted_match === 'string'; | ||
isRegExp = quoted_match instanceof RegExp; | ||
if ((base = options.cast).string == null) { | ||
base.string = function (value) { | ||
return value; | ||
}; | ||
} | ||
if (!isString && !isRegExp) { | ||
throw Error("Invalid Option: quoted_match must be a string or a regex, got ".concat(JSON.stringify(quoted_match))); | ||
} | ||
} | ||
} | ||
if ((base1 = options.cast).date == null) { | ||
base1.date = function (value) { | ||
// Cast date to timestamp string by default | ||
return '' + value.getTime(); | ||
}; | ||
} | ||
if (options.cast.bool) { | ||
// Backward compatibility | ||
options.cast.boolean = options.cast.bool; | ||
} // Custom cast | ||
if ((base2 = options.cast).boolean == null) { | ||
base2.boolean = function (value) { | ||
// Cast boolean to string by default | ||
if (value) { | ||
return '1'; | ||
} else { | ||
return ''; | ||
} | ||
}; | ||
} | ||
if ((base3 = options.cast).number == null) { | ||
base3.number = function (value) { | ||
// Cast number to string using native casting by default | ||
return '' + value; | ||
}; | ||
} | ||
if ((base = options.cast).string == null) { | ||
base.string = function (value) { | ||
return value; | ||
}; | ||
} | ||
if ((base4 = options.cast).object == null) { | ||
base4.object = function (value) { | ||
// Stringify object as JSON by default | ||
return JSON.stringify(value); | ||
}; | ||
} | ||
if ((base1 = options.cast).date == null) { | ||
base1.date = function (value) { | ||
// Cast date to timestamp string by default | ||
return '' + value.getTime(); | ||
}; | ||
} | ||
if (options.record_delimiter === void 0 || options.record_delimiter === null || options.record_delimiter === false) { | ||
if (options.record_delimiter == null) { | ||
options.record_delimiter = '\n'; | ||
if ((base2 = options.cast).boolean == null) { | ||
base2.boolean = function (value) { | ||
// Cast boolean to string by default | ||
if (value) { | ||
return '1'; | ||
} else { | ||
return ''; | ||
} | ||
}; | ||
} | ||
} else if (typeof options.record_delimiter === 'string') { | ||
switch (options.record_delimiter) { | ||
case 'auto': | ||
options.record_delimiter = null; | ||
break; | ||
case 'unix': | ||
options.record_delimiter = "\n"; | ||
break; | ||
if ((base3 = options.cast).number == null) { | ||
base3.number = function (value) { | ||
// Cast number to string using native casting by default | ||
return '' + value; | ||
}; | ||
} | ||
case 'mac': | ||
options.record_delimiter = "\r"; | ||
break; | ||
if ((base4 = options.cast).object == null) { | ||
base4.object = function (value) { | ||
// Stringify object as JSON by default | ||
return JSON.stringify(value); | ||
}; | ||
} | ||
case 'windows': | ||
options.record_delimiter = "\r\n"; | ||
break; | ||
if (options.record_delimiter === void 0 || options.record_delimiter === null || options.record_delimiter === false) { | ||
if (options.record_delimiter == null) { | ||
options.record_delimiter = '\n'; | ||
} | ||
} else if (typeof options.record_delimiter === 'string') { | ||
switch (options.record_delimiter) { | ||
case 'auto': | ||
options.record_delimiter = null; | ||
break; | ||
case 'ascii': | ||
options.record_delimiter = "\x1E"; | ||
break; | ||
case 'unix': | ||
options.record_delimiter = "\n"; | ||
break; | ||
case 'unicode': | ||
options.record_delimiter = "\u2028"; | ||
} | ||
} else if (Buffer.isBuffer(options.record_delimiter)) { | ||
options.record_delimiter = options.record_delimiter.toString(); | ||
} else { | ||
throw Error("Invalid Option: record_delimiter must be a string or a buffer, got ".concat(JSON.stringify(options.record_delimiter))); | ||
} // Internal usage, state related | ||
case 'mac': | ||
options.record_delimiter = "\r"; | ||
break; | ||
case 'windows': | ||
options.record_delimiter = "\r\n"; | ||
break; | ||
if (this.countWriten == null) { | ||
this.countWriten = 0; | ||
} // Expose options | ||
case 'ascii': | ||
options.record_delimiter = "\x1E"; | ||
break; | ||
case 'unicode': | ||
options.record_delimiter = "\u2028"; | ||
} | ||
} else if (Buffer.isBuffer(options.record_delimiter)) { | ||
options.record_delimiter = options.record_delimiter.toString(); | ||
} else { | ||
throw Error("Invalid Option: record_delimiter must be a string or a buffer, got ".concat(JSON.stringify(options.record_delimiter))); | ||
} // Expose options | ||
this.options = options; | ||
return this; | ||
}; | ||
util.inherits(_Stringifier, stream.Transform); | ||
module.exports.Stringifier = _Stringifier; // ## `Stringifier.prototype._transform(chunk, encoding, callback)` | ||
// Implementation of the [transform._transform function](https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback). | ||
_this.options = options; // Internal state | ||
_Stringifier.prototype._transform = function (chunk, encoding, callback) { | ||
var base, e, preserve; // Nothing to do if null or undefined | ||
_this.state = { | ||
stop: false | ||
}; // Information | ||
if (chunk == null) { | ||
return; | ||
} | ||
_this.info = { | ||
records: 0 | ||
}; | ||
preserve = _typeof(chunk) !== 'object'; // Emit and stringify the record if an object or an array | ||
_assertThisInitialized(_assertThisInitialized(_this)); | ||
if (!preserve) { | ||
// Detect columns from the first record | ||
if (this.countWriten === 0 && !Array.isArray(chunk)) { | ||
if ((base = this.options).columns == null) { | ||
base.columns = _Stringifier.normalize_columns(Object.keys(chunk)); | ||
} | ||
} | ||
return _this; | ||
} // ## `Stringifier.prototype._transform(chunk, encoding, callback)` | ||
// Implementation of the [transform._transform function](https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback). | ||
try { | ||
this.emit('record', chunk, this.countWriten); | ||
} catch (error) { | ||
e = error; | ||
return this.emit('error', e); | ||
} // Convert the record into a string | ||
_createClass(Stringifier, [{ | ||
key: "_transform", | ||
value: function _transform(chunk, encoding, callback) { | ||
var base, e; | ||
if (this.options.eof) { | ||
chunk = this.stringify(chunk); | ||
if (chunk == null) { | ||
if (this.state.stop === true) { | ||
return; | ||
} | ||
} // Chunk validation | ||
chunk = chunk + this.options.record_delimiter; | ||
} else { | ||
chunk = this.stringify(chunk); | ||
if (chunk == null) { | ||
return; | ||
if (!(Array.isArray(chunk) || _typeof(chunk) === 'object')) { | ||
this.state.stop = true; | ||
return callback(Error("Invalid Record: expect an array or an object, got ".concat(JSON.stringify(chunk)))); | ||
} // Detect columns from the first record | ||
if (this.info.records === 0) { | ||
if (Array.isArray(chunk)) { | ||
if (this.options.header === true && !this.options.columns) { | ||
this.state.stop = true; | ||
return callback(Error('Undiscoverable Columns: header option requires column option or object records')); | ||
} | ||
} else { | ||
if ((base = this.options).columns == null) { | ||
base.columns = this.normalize_columns(Object.keys(chunk)); | ||
} | ||
} | ||
} | ||
if (this.options.header || this.countWriten) { | ||
chunk = this.options.record_delimiter + chunk; | ||
if (this.info.records === 0) { | ||
// Emit the header | ||
this.headers(); | ||
} | ||
} | ||
} | ||
if (typeof chunk === 'number') { | ||
// Emit the csv | ||
chunk = "".concat(chunk); | ||
} | ||
try { | ||
// Emit and stringify the record if an object or an array | ||
this.emit('record', chunk, this.info.records); | ||
} catch (error) { | ||
e = error; | ||
this.state.stop = true; | ||
return this.emit('error', e); | ||
} // Convert the record into a string | ||
if (this.countWriten === 0) { | ||
this.headers(); | ||
} | ||
if (!preserve) { | ||
this.countWriten++; | ||
} | ||
if (this.options.eof) { | ||
chunk = this.stringify(chunk); | ||
this.push(chunk); | ||
return callback(); | ||
}; // ## `Stringifier.prototype._flush(callback)` | ||
// Implementation of the [transform._flush function](https://nodejs.org/api/stream.html#stream_transform_flush_callback). | ||
if (chunk == null) { | ||
return; | ||
} | ||
chunk = chunk + this.options.record_delimiter; | ||
} else { | ||
chunk = this.stringify(chunk); | ||
_Stringifier.prototype._flush = function (callback) { | ||
if (this.countWriten === 0) { | ||
this.headers(); | ||
} | ||
if (chunk == null) { | ||
return; | ||
} | ||
return callback(); | ||
}; // ## `Stringifier.prototype.stringify(line)` | ||
// Convert a line to a string. Line may be an object, an array or a string. | ||
if (this.options.header || this.info.records) { | ||
chunk = this.options.record_delimiter + chunk; | ||
} | ||
} // Emit the csv | ||
_Stringifier.prototype.stringify = function (record) { | ||
var _record, column, columns, containsEscape, containsQuote, containsRowDelimiter, containsdelimiter, delimiter, err, escape, field, i, j, l, newrecord, quote, quoted, quotedMatch, quotedString, ref, ref1, regexp, shouldQuote, type, value; | ||
this.info.records++; | ||
this.push(chunk); | ||
return callback(); | ||
} // ## `Stringifier.prototype._flush(callback)` | ||
// Implementation of the [transform._flush function](https://nodejs.org/api/stream.html#stream_transform_flush_callback). | ||
if (_typeof(record) !== 'object') { | ||
return record; | ||
} | ||
}, { | ||
key: "_flush", | ||
value: function _flush(callback) { | ||
if (this.info.records === 0) { | ||
this.headers(); | ||
} | ||
columns = this.options.columns; | ||
delimiter = this.options.delimiter; | ||
quote = this.options.quote; | ||
escape = this.options.escape; | ||
return callback(); | ||
} // ## `Stringifier.prototype.stringify(line)` | ||
// Convert a line to a string. Line may be an object, an array or a string. | ||
if (!Array.isArray(record)) { | ||
_record = []; | ||
}, { | ||
key: "stringify", | ||
value: function stringify(record) { | ||
var _record, column, columns, containsEscape, containsQuote, containsRowDelimiter, containsdelimiter, delimiter, err, escape, field, header, i, j, l, len, m, newrecord, quote, quoted, quotedMatch, quotedString, ref, ref1, regexp, shouldQuote, value; | ||
if (columns) { | ||
for (i = j = 0, ref = columns.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { | ||
value = get(record, columns[i].key); | ||
_record[i] = typeof value === 'undefined' || value === null ? '' : value; | ||
if (_typeof(record) !== 'object') { | ||
return record; | ||
} | ||
} else { | ||
for (column in record) { | ||
_record.push(record[column]); | ||
} | ||
} | ||
record = _record; | ||
_record = null; | ||
} else if (columns) { | ||
// Note, we used to have @options.columns | ||
// We are getting an array but the user want specified output columns. In | ||
// this case, we respect the columns indexes | ||
record.splice(columns.length); | ||
} | ||
var _this$options = this.options; | ||
columns = _this$options.columns; | ||
delimiter = _this$options.delimiter; | ||
header = _this$options.header; | ||
quote = _this$options.quote; | ||
escape = _this$options.escape; | ||
if (Array.isArray(record)) { | ||
newrecord = ''; | ||
// Record is an array | ||
if (Array.isArray(record)) { | ||
if (columns) { | ||
// We are getting an array but the user has specified output columns. In | ||
// this case, we respect the columns indexes | ||
record.splice(columns.length); | ||
} // Cast record elements | ||
for (i = l = 0, ref1 = record.length; 0 <= ref1 ? l < ref1 : l > ref1; i = 0 <= ref1 ? ++l : --l) { | ||
field = record[i]; | ||
type = _typeof(field); | ||
try { | ||
if (type === 'string') { | ||
// fine 99% of the cases | ||
field = this.options.cast.string(field); | ||
} else if (type === 'number') { | ||
field = this.options.cast.number(field); | ||
} else if (type === 'boolean') { | ||
field = this.options.cast.boolean(field); | ||
} else if (field instanceof Date) { | ||
field = this.options.cast.date(field); | ||
} else if (type === 'object' && field !== null) { | ||
field = this.options.cast.object(field); | ||
} | ||
} catch (error) { | ||
err = error; | ||
this.emit('error', err); | ||
return; | ||
} | ||
for (i = j = 0, len = record.length; j < len; i = ++j) { | ||
field = record[i]; | ||
if (field) { | ||
if (typeof field !== 'string') { | ||
this.emit('error', Error('Formatter must return a string, null or undefined')); | ||
return null; | ||
} | ||
var _this$__cast = this.__cast(field, { | ||
index: i, | ||
column: i, | ||
records: this.info.records, | ||
header: header && this.info.records === 0 | ||
}); | ||
containsdelimiter = field.indexOf(delimiter) >= 0; | ||
containsQuote = quote !== '' && field.indexOf(quote) >= 0; | ||
containsEscape = field.indexOf(escape) >= 0 && escape !== quote; | ||
containsRowDelimiter = field.indexOf(this.options.record_delimiter) >= 0; | ||
quoted = this.options.quoted; | ||
quotedString = this.options.quoted_string && typeof record[i] === 'string'; | ||
quotedMatch = this.options.quoted_match && typeof record[i] === 'string' && this.options.quoted_match.filter(function (quoted_match) { | ||
if (typeof quoted_match === 'string') { | ||
return record[i].indexOf(quoted_match) !== -1; | ||
} else { | ||
return quoted_match.test(record[i]); | ||
var _this$__cast2 = _slicedToArray(_this$__cast, 2); | ||
err = _this$__cast2[0]; | ||
value = _this$__cast2[1]; | ||
if (err) { | ||
this.emit('error', err); | ||
return; | ||
} | ||
}); | ||
quotedMatch = quotedMatch && quotedMatch.length > 0; | ||
shouldQuote = containsQuote || containsdelimiter || containsRowDelimiter || quoted || quotedString || quotedMatch; | ||
if (shouldQuote && containsEscape) { | ||
regexp = escape === '\\' ? new RegExp(escape + escape, 'g') : new RegExp(escape, 'g'); | ||
field = field.replace(regexp, escape + escape); | ||
record[i] = [value, field]; | ||
} | ||
} else { | ||
// Record is a literal object | ||
_record = []; | ||
if (containsQuote) { | ||
regexp = new RegExp(quote, 'g'); | ||
field = field.replace(regexp, escape + quote); | ||
} | ||
if (columns) { | ||
for (i = l = 0, ref = columns.length; 0 <= ref ? l < ref : l > ref; i = 0 <= ref ? ++l : --l) { | ||
field = get(record, columns[i].key); | ||
if (shouldQuote) { | ||
field = quote + field + quote; | ||
var _this$__cast3 = this.__cast(field, { | ||
index: i, | ||
column: columns[i].key, | ||
records: this.info.records, | ||
header: header && this.info.records === 0 | ||
}); | ||
var _this$__cast4 = _slicedToArray(_this$__cast3, 2); | ||
err = _this$__cast4[0]; | ||
value = _this$__cast4[1]; | ||
if (err) { | ||
this.emit('error', err); | ||
return; | ||
} | ||
_record[i] = [value, field]; | ||
} | ||
} else { | ||
for (column in record) { | ||
field = record[column]; | ||
var _this$__cast5 = this.__cast(field, { | ||
index: i, | ||
column: columns[i].key, | ||
records: this.info.records, | ||
header: header && this.info.records === 0 | ||
}); | ||
var _this$__cast6 = _slicedToArray(_this$__cast5, 2); | ||
err = _this$__cast6[0]; | ||
value = _this$__cast6[1]; | ||
if (err) { | ||
this.emit('error', err); | ||
return; | ||
} | ||
_record.push([value, field]); | ||
} | ||
} | ||
newrecord += field; | ||
} else if (this.options.quoted_empty || this.options.quoted_empty == null && record[i] === '' && this.options.quoted_string) { | ||
newrecord += quote + quote; | ||
record = _record; | ||
_record = null; | ||
} | ||
if (i !== record.length - 1) { | ||
newrecord += delimiter; | ||
} | ||
} | ||
if (Array.isArray(record)) { | ||
newrecord = ''; | ||
record = newrecord; | ||
} | ||
for (i = m = 0, ref1 = record.length; 0 <= ref1 ? m < ref1 : m > ref1; i = 0 <= ref1 ? ++m : --m) { | ||
var _record$i = _slicedToArray(record[i], 2); | ||
return record; | ||
}; // ## `Stringifier.prototype.headers` | ||
// Print the header line if the option "header" is "true". | ||
value = _record$i[0]; | ||
field = _record$i[1]; | ||
if (err) { | ||
this.emit('error', err); | ||
return; | ||
} | ||
_Stringifier.prototype.headers = function () { | ||
var headers; | ||
if (value) { | ||
if (typeof value !== 'string') { | ||
this.emit('error', Error("Formatter must return a string, null or undefined, got ".concat(JSON.stringify(value)))); | ||
return null; | ||
} | ||
if (!this.options.header) { | ||
return; | ||
} | ||
containsdelimiter = value.indexOf(delimiter) >= 0; | ||
containsQuote = quote !== '' && value.indexOf(quote) >= 0; | ||
containsEscape = value.indexOf(escape) >= 0 && escape !== quote; | ||
containsRowDelimiter = value.indexOf(this.options.record_delimiter) >= 0; | ||
quoted = this.options.quoted; | ||
quotedString = this.options.quoted_string && typeof field === 'string'; | ||
quotedMatch = this.options.quoted_match && typeof field === 'string' && this.options.quoted_match.filter(function (quoted_match) { | ||
if (typeof quoted_match === 'string') { | ||
return value.indexOf(quoted_match) !== -1; | ||
} else { | ||
return quoted_match.test(value); | ||
} | ||
}); | ||
quotedMatch = quotedMatch && quotedMatch.length > 0; | ||
shouldQuote = containsQuote || containsdelimiter || containsRowDelimiter || quoted || quotedString || quotedMatch; | ||
if (!this.options.columns) { | ||
return; | ||
} | ||
if (shouldQuote && containsEscape) { | ||
regexp = escape === '\\' ? new RegExp(escape + escape, 'g') : new RegExp(escape, 'g'); | ||
value = value.replace(regexp, escape + escape); | ||
} | ||
headers = this.options.columns.map(function (column) { | ||
return column.header; | ||
}); | ||
if (containsQuote) { | ||
regexp = new RegExp(quote, 'g'); | ||
value = value.replace(regexp, escape + quote); | ||
} | ||
if (this.options.eof) { | ||
headers = this.stringify(headers) + this.options.record_delimiter; | ||
} else { | ||
headers = this.stringify(headers); | ||
} | ||
if (shouldQuote) { | ||
value = quote + value + quote; | ||
} | ||
return this.push(headers); | ||
}; // ## `Stringifier.prototype.headers` | ||
// Print the header line if the option "header" is "true". | ||
newrecord += value; | ||
} else if (this.options.quoted_empty || this.options.quoted_empty == null && field === '' && this.options.quoted_string) { | ||
newrecord += quote + quote; | ||
} | ||
if (i !== record.length - 1) { | ||
newrecord += delimiter; | ||
} | ||
} | ||
_Stringifier.normalize_columns = function (columns) { | ||
var column, k, v; | ||
record = newrecord; | ||
} | ||
if (columns == null) { | ||
return null; | ||
} | ||
return record; | ||
} // ## `Stringifier.prototype.headers` | ||
// Print the header line if the option "header" is "true". | ||
if (columns != null) { | ||
if (_typeof(columns) !== 'object') { | ||
throw Error('Invalid option "columns": expect an array or an object'); | ||
}, { | ||
key: "headers", | ||
value: function headers() { | ||
var headers; | ||
if (!this.options.header) { | ||
return; | ||
} | ||
if (!this.options.columns) { | ||
return; | ||
} | ||
headers = this.options.columns.map(function (column) { | ||
return column.header; | ||
}); | ||
if (this.options.eof) { | ||
headers = this.stringify(headers) + this.options.record_delimiter; | ||
} else { | ||
headers = this.stringify(headers); | ||
} | ||
return this.push(headers); | ||
} | ||
}, { | ||
key: "__cast", | ||
value: function __cast(value, context) { | ||
var err, type; | ||
type = _typeof(value); | ||
if (!Array.isArray(columns)) { | ||
columns = function () { | ||
var results; | ||
results = []; | ||
try { | ||
if (type === 'string') { | ||
// Fine for 99% of the cases | ||
return [void 0, this.options.cast.string(value, context)]; | ||
} else if (type === 'number') { | ||
return [void 0, this.options.cast.number(value, context)]; | ||
} else if (type === 'boolean') { | ||
return [void 0, this.options.cast.boolean(value, context)]; | ||
} else if (value instanceof Date) { | ||
return [void 0, this.options.cast.date(value, context)]; | ||
} else if (type === 'object' && value !== null) { | ||
return [void 0, this.options.cast.object(value, context)]; | ||
} else { | ||
return [void 0, value, value]; | ||
} | ||
} catch (error) { | ||
err = error; | ||
return [err]; | ||
} | ||
} // ## `Stringifier.prototype.normalize_columns` | ||
for (k in columns) { | ||
v = columns[k]; | ||
results.push({ | ||
key: k, | ||
header: v | ||
}); | ||
}, { | ||
key: "normalize_columns", | ||
value: function normalize_columns(columns) { | ||
var column, k, v; | ||
if (columns == null) { | ||
return null; | ||
} | ||
if (columns != null) { | ||
if (_typeof(columns) !== 'object') { | ||
throw Error('Invalid option "columns": expect an array or an object'); | ||
} | ||
return results; | ||
}(); | ||
} else { | ||
columns = function () { | ||
var j, len, results; | ||
results = []; | ||
if (!Array.isArray(columns)) { | ||
columns = function () { | ||
var results; | ||
results = []; | ||
for (j = 0, len = columns.length; j < len; j++) { | ||
column = columns[j]; | ||
if (typeof column === 'string') { | ||
results.push({ | ||
key: column, | ||
header: column | ||
}); | ||
} else if (_typeof(column) === 'object' && column != null && !Array.isArray(column)) { | ||
if (!column.key) { | ||
throw Error('Invalid column definition: property "key" is required'); | ||
for (k in columns) { | ||
v = columns[k]; | ||
results.push({ | ||
key: k, | ||
header: v | ||
}); | ||
} | ||
if (column.header == null) { | ||
column.header = column.key; | ||
return results; | ||
}(); | ||
} else { | ||
columns = function () { | ||
var j, len, results; | ||
results = []; | ||
for (j = 0, len = columns.length; j < len; j++) { | ||
column = columns[j]; | ||
if (typeof column === 'string') { | ||
results.push({ | ||
key: column, | ||
header: column | ||
}); | ||
} else if (_typeof(column) === 'object' && column != null && !Array.isArray(column)) { | ||
if (!column.key) { | ||
throw Error('Invalid column definition: property "key" is required'); | ||
} | ||
if (column.header == null) { | ||
column.header = column.key; | ||
} | ||
results.push(column); | ||
} else { | ||
throw Error('Invalid column definition: expect a string or an object'); | ||
} | ||
} | ||
results.push(column); | ||
} else { | ||
throw Error('Invalid column definition: expect a string or an object'); | ||
} | ||
return results; | ||
}(); | ||
} | ||
} | ||
return results; | ||
}(); | ||
return columns; | ||
} | ||
} | ||
}]); | ||
return columns; | ||
}; | ||
return Stringifier; | ||
}(stream.Transform); | ||
module.exports.Stringifier = Stringifier; | ||
underscore = function underscore(str) { | ||
@@ -558,0 +691,0 @@ return str.replace(/([A-Z])/g, function (_, match, index) { |
@@ -8,17 +8,27 @@ /// <reference types="node" /> | ||
declare function stringify(callback?: stringify.Callback): stringify.Stringifier | ||
declare function stringify(options?: stringify.Options, callback?: stringify.Callback): stringify.Stringifier | ||
declare function stringify(options: stringify.Options, callback?: stringify.Callback): stringify.Stringifier | ||
declare function stringify(input: stringify.Input, callback?: stringify.Callback): stringify.Stringifier | ||
declare function stringify(input: stringify.Input, options?: stringify.Options, callback?: stringify.Callback): stringify.Stringifier | ||
declare namespace stringify { | ||
type Callback = (err: any | Error, output: any) => void | ||
type Callback = (err?: null | Error, output?: string) => void | ||
type RowDelimiter = string | 'auto' | 'unix' | 'windows' | 'ascii' | 'unicode' | ||
type RowDelimiter = string | 'auto' | 'unix' | 'mac' | 'windows' | 'ascii' | 'unicode' | ||
type Formatter<T> = (value: T) => string | ||
type Cast<T> = (value: T, CastingContext) => string | ||
interface PlainObject<T> { | ||
[key: string]: T | ||
type PlainObject<T> = Record<string, T> | ||
type Input = any[] | ||
interface ColumnOption { | ||
key: string | ||
header?: string | ||
} | ||
type Input = any[] | PlainObject<any> | ||
interface CastingContext { | ||
readonly column?: number | string; | ||
readonly header: boolean; | ||
readonly index: number; | ||
readonly records: number; | ||
} | ||
@@ -34,3 +44,3 @@ interface Options { | ||
*/ | ||
columns?: string[] | PlainObject<string> | ||
columns?: string[] | PlainObject<string> | ColumnOption[] | ||
@@ -85,12 +95,12 @@ /** | ||
/** | ||
* Key-value object which defines custom formatters for certain data types | ||
* Key-value object which defines custom cast for certain data types | ||
*/ | ||
formatters?: { | ||
boolean?: Formatter<boolean> | ||
date?: Formatter<Date> | ||
number?: Formatter<number> | ||
cast?: { | ||
boolean?: Cast<boolean> | ||
date?: Cast<Date> | ||
number?: Cast<number> | ||
/** | ||
* Custom formatter for generic object values | ||
*/ | ||
object?: Formatter<any> | ||
object?: Cast<Record<string, any>> | ||
} | ||
@@ -97,0 +107,0 @@ } |
684
lib/index.js
@@ -97,162 +97,177 @@ // Generated by CoffeeScript 2.3.2 | ||
// Options are documented [here](http://csv.adaltas.com/stringify/). | ||
Stringifier = function(opts = {}) { | ||
var base, base1, base2, base3, base4, isRegExp, isString, j, k, len, options, quoted_match, ref, v; | ||
stream.Transform.call(this, {...options, ...{ | ||
writableObjectMode: true | ||
}}); | ||
options = {}; | ||
for (k in opts) { | ||
v = opts[k]; | ||
// Immutable options and camelcase conversion | ||
options[underscore(k)] = v; | ||
} | ||
//# Default options | ||
if (options.delimiter == null) { | ||
options.delimiter = ','; | ||
} | ||
if (options.quote == null) { | ||
options.quote = '"'; | ||
} | ||
if (options.quoted == null) { | ||
options.quoted = false; | ||
} | ||
if (options.quoted_empty == null) { | ||
options.quoted_empty = void 0; | ||
} | ||
if (options.quoted_string == null) { | ||
options.quoted_string = false; | ||
} | ||
if (options.eof == null) { | ||
options.eof = true; | ||
} | ||
if (options.escape == null) { | ||
options.escape = '"'; | ||
} | ||
if (options.header == null) { | ||
options.header = false; | ||
} | ||
// Normalize the columns option | ||
options.columns = Stringifier.normalize_columns(options.columns); | ||
if (options.cast == null) { | ||
options.cast = {}; | ||
} | ||
// Normalize option `quoted_match` | ||
if (options.quoted_match === void 0 || options.quoted_match === null || options.quoted_match === false) { | ||
options.quoted_match = null; | ||
} else if (!Array.isArray(options.quoted_match)) { | ||
options.quoted_match = [options.quoted_match]; | ||
} | ||
if (options.quoted_match) { | ||
ref = options.quoted_match; | ||
for (j = 0, len = ref.length; j < len; j++) { | ||
quoted_match = ref[j]; | ||
isString = typeof quoted_match === 'string'; | ||
isRegExp = quoted_match instanceof RegExp; | ||
if (!isString && !isRegExp) { | ||
throw Error(`Invalid Option: quoted_match must be a string or a regex, got ${JSON.stringify(quoted_match)}`); | ||
Stringifier = class Stringifier extends stream.Transform { | ||
constructor(opts = {}) { | ||
var base, base1, base2, base3, base4, isRegExp, isString, j, k, len, options, quoted_match, ref, v; | ||
super({...{ | ||
writableObjectMode: true | ||
}, ...options}); | ||
options = {}; | ||
for (k in opts) { | ||
v = opts[k]; | ||
// Immutable options and camelcase conversion | ||
options[underscore(k)] = v; | ||
} | ||
//# Default options | ||
if (options.delimiter == null) { | ||
options.delimiter = ','; | ||
} | ||
if (options.quote == null) { | ||
options.quote = '"'; | ||
} | ||
if (options.quoted == null) { | ||
options.quoted = false; | ||
} | ||
if (options.quoted_empty == null) { | ||
options.quoted_empty = void 0; | ||
} | ||
if (options.quoted_string == null) { | ||
options.quoted_string = false; | ||
} | ||
if (options.eof == null) { | ||
options.eof = true; | ||
} | ||
if (options.escape == null) { | ||
options.escape = '"'; | ||
} | ||
if (options.header == null) { | ||
options.header = false; | ||
} | ||
// Normalize the columns option | ||
options.columns = this.normalize_columns(options.columns); | ||
if (options.cast == null) { | ||
options.cast = {}; | ||
} | ||
// Normalize option `quoted_match` | ||
if (options.quoted_match === void 0 || options.quoted_match === null || options.quoted_match === false) { | ||
options.quoted_match = null; | ||
} else if (!Array.isArray(options.quoted_match)) { | ||
options.quoted_match = [options.quoted_match]; | ||
} | ||
if (options.quoted_match) { | ||
ref = options.quoted_match; | ||
for (j = 0, len = ref.length; j < len; j++) { | ||
quoted_match = ref[j]; | ||
isString = typeof quoted_match === 'string'; | ||
isRegExp = quoted_match instanceof RegExp; | ||
if (!isString && !isRegExp) { | ||
throw Error(`Invalid Option: quoted_match must be a string or a regex, got ${JSON.stringify(quoted_match)}`); | ||
} | ||
} | ||
} | ||
} | ||
if (options.cast.bool) { | ||
// Backward compatibility | ||
options.cast.boolean = options.cast.bool; | ||
} | ||
// Custom cast | ||
if ((base = options.cast).string == null) { | ||
base.string = function(value) { | ||
return value; | ||
}; | ||
} | ||
if ((base1 = options.cast).date == null) { | ||
base1.date = function(value) { | ||
// Cast date to timestamp string by default | ||
return '' + value.getTime(); | ||
}; | ||
} | ||
if ((base2 = options.cast).boolean == null) { | ||
base2.boolean = function(value) { | ||
// Cast boolean to string by default | ||
if (value) { | ||
return '1'; | ||
} else { | ||
return ''; | ||
if (options.cast.bool) { | ||
// Backward compatibility | ||
options.cast.boolean = options.cast.bool; | ||
} | ||
// Custom cast | ||
if ((base = options.cast).string == null) { | ||
base.string = function(value) { | ||
return value; | ||
}; | ||
} | ||
if ((base1 = options.cast).date == null) { | ||
base1.date = function(value) { | ||
// Cast date to timestamp string by default | ||
return '' + value.getTime(); | ||
}; | ||
} | ||
if ((base2 = options.cast).boolean == null) { | ||
base2.boolean = function(value) { | ||
// Cast boolean to string by default | ||
if (value) { | ||
return '1'; | ||
} else { | ||
return ''; | ||
} | ||
}; | ||
} | ||
if ((base3 = options.cast).number == null) { | ||
base3.number = function(value) { | ||
// Cast number to string using native casting by default | ||
return '' + value; | ||
}; | ||
} | ||
if ((base4 = options.cast).object == null) { | ||
base4.object = function(value) { | ||
// Stringify object as JSON by default | ||
return JSON.stringify(value); | ||
}; | ||
} | ||
if (options.record_delimiter === void 0 || options.record_delimiter === null || options.record_delimiter === false) { | ||
if (options.record_delimiter == null) { | ||
options.record_delimiter = '\n'; | ||
} | ||
} else if (typeof options.record_delimiter === 'string') { | ||
switch (options.record_delimiter) { | ||
case 'auto': | ||
options.record_delimiter = null; | ||
break; | ||
case 'unix': | ||
options.record_delimiter = "\n"; | ||
break; | ||
case 'mac': | ||
options.record_delimiter = "\r"; | ||
break; | ||
case 'windows': | ||
options.record_delimiter = "\r\n"; | ||
break; | ||
case 'ascii': | ||
options.record_delimiter = "\u001e"; | ||
break; | ||
case 'unicode': | ||
options.record_delimiter = "\u2028"; | ||
} | ||
} else if (Buffer.isBuffer(options.record_delimiter)) { | ||
options.record_delimiter = options.record_delimiter.toString(); | ||
} else { | ||
throw Error(`Invalid Option: record_delimiter must be a string or a buffer, got ${JSON.stringify(options.record_delimiter)}`); | ||
} | ||
// Expose options | ||
this.options = options; | ||
// Internal state | ||
this.state = { | ||
stop: false | ||
}; | ||
} | ||
if ((base3 = options.cast).number == null) { | ||
base3.number = function(value) { | ||
// Cast number to string using native casting by default | ||
return '' + value; | ||
// Information | ||
this.info = { | ||
records: 0 | ||
}; | ||
this; | ||
} | ||
if ((base4 = options.cast).object == null) { | ||
base4.object = function(value) { | ||
// Stringify object as JSON by default | ||
return JSON.stringify(value); | ||
}; | ||
} | ||
if (options.record_delimiter === void 0 || options.record_delimiter === null || options.record_delimiter === false) { | ||
if (options.record_delimiter == null) { | ||
options.record_delimiter = '\n'; | ||
// ## `Stringifier.prototype._transform(chunk, encoding, callback)` | ||
// Implementation of the [transform._transform function](https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback). | ||
_transform(chunk, encoding, callback) { | ||
var base, e; | ||
if (this.state.stop === true) { | ||
return; | ||
} | ||
} else if (typeof options.record_delimiter === 'string') { | ||
switch (options.record_delimiter) { | ||
case 'auto': | ||
options.record_delimiter = null; | ||
break; | ||
case 'unix': | ||
options.record_delimiter = "\n"; | ||
break; | ||
case 'mac': | ||
options.record_delimiter = "\r"; | ||
break; | ||
case 'windows': | ||
options.record_delimiter = "\r\n"; | ||
break; | ||
case 'ascii': | ||
options.record_delimiter = "\u001e"; | ||
break; | ||
case 'unicode': | ||
options.record_delimiter = "\u2028"; | ||
// Chunk validation | ||
if (!(Array.isArray(chunk) || typeof chunk === 'object')) { | ||
this.state.stop = true; | ||
return callback(Error(`Invalid Record: expect an array or an object, got ${JSON.stringify(chunk)}`)); | ||
} | ||
} else if (Buffer.isBuffer(options.record_delimiter)) { | ||
options.record_delimiter = options.record_delimiter.toString(); | ||
} else { | ||
throw Error(`Invalid Option: record_delimiter must be a string or a buffer, got ${JSON.stringify(options.record_delimiter)}`); | ||
} | ||
// Internal usage, state related | ||
if (this.countWriten == null) { | ||
this.countWriten = 0; | ||
} | ||
// Expose options | ||
this.options = options; | ||
return this; | ||
}; | ||
util.inherits(Stringifier, stream.Transform); | ||
module.exports.Stringifier = Stringifier; | ||
// ## `Stringifier.prototype._transform(chunk, encoding, callback)` | ||
// Implementation of the [transform._transform function](https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback). | ||
Stringifier.prototype._transform = function(chunk, encoding, callback) { | ||
var base, e, preserve; | ||
// Nothing to do if null or undefined | ||
if (chunk == null) { | ||
return; | ||
} | ||
preserve = typeof chunk !== 'object'; | ||
// Emit and stringify the record if an object or an array | ||
if (!preserve) { | ||
// Detect columns from the first record | ||
if (this.countWriten === 0 && !Array.isArray(chunk)) { | ||
if ((base = this.options).columns == null) { | ||
base.columns = Stringifier.normalize_columns(Object.keys(chunk)); | ||
if (this.info.records === 0) { | ||
if (Array.isArray(chunk)) { | ||
if (this.options.header === true && !this.options.columns) { | ||
this.state.stop = true; | ||
return callback(Error('Undiscoverable Columns: header option requires column option or object records')); | ||
} | ||
} else { | ||
if ((base = this.options).columns == null) { | ||
base.columns = this.normalize_columns(Object.keys(chunk)); | ||
} | ||
} | ||
} | ||
if (this.info.records === 0) { | ||
// Emit the header | ||
this.headers(); | ||
} | ||
try { | ||
this.emit('record', chunk, this.countWriten); | ||
// Emit and stringify the record if an object or an array | ||
this.emit('record', chunk, this.info.records); | ||
} catch (error) { | ||
e = error; | ||
this.state.stop = true; | ||
return this.emit('error', e); | ||
@@ -272,206 +287,245 @@ } | ||
} | ||
if (this.options.header || this.countWriten) { | ||
if (this.options.header || this.info.records) { | ||
chunk = this.options.record_delimiter + chunk; | ||
} | ||
} | ||
} | ||
if (typeof chunk === 'number') { | ||
// Emit the csv | ||
chunk = `${chunk}`; | ||
this.info.records++; | ||
this.push(chunk); | ||
return callback(); | ||
} | ||
if (this.countWriten === 0) { | ||
this.headers(); | ||
} | ||
if (!preserve) { | ||
this.countWriten++; | ||
} | ||
this.push(chunk); | ||
return callback(); | ||
}; | ||
// ## `Stringifier.prototype._flush(callback)` | ||
// ## `Stringifier.prototype._flush(callback)` | ||
// Implementation of the [transform._flush function](https://nodejs.org/api/stream.html#stream_transform_flush_callback). | ||
Stringifier.prototype._flush = function(callback) { | ||
if (this.countWriten === 0) { | ||
this.headers(); | ||
// Implementation of the [transform._flush function](https://nodejs.org/api/stream.html#stream_transform_flush_callback). | ||
_flush(callback) { | ||
if (this.info.records === 0) { | ||
this.headers(); | ||
} | ||
return callback(); | ||
} | ||
return callback(); | ||
}; | ||
// ## `Stringifier.prototype.stringify(line)` | ||
// ## `Stringifier.prototype.stringify(line)` | ||
// Convert a line to a string. Line may be an object, an array or a string. | ||
Stringifier.prototype.stringify = function(record) { | ||
var _record, column, columns, containsEscape, containsQuote, containsRowDelimiter, containsdelimiter, delimiter, err, escape, field, i, j, l, newrecord, quote, quoted, quotedMatch, quotedString, ref, ref1, regexp, shouldQuote, type, value; | ||
if (typeof record !== 'object') { | ||
return record; | ||
} | ||
columns = this.options.columns; | ||
delimiter = this.options.delimiter; | ||
quote = this.options.quote; | ||
escape = this.options.escape; | ||
if (!Array.isArray(record)) { | ||
_record = []; | ||
if (columns) { | ||
for (i = j = 0, ref = columns.length; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) { | ||
value = get(record, columns[i].key); | ||
_record[i] = (typeof value === 'undefined' || value === null) ? '' : value; | ||
// Convert a line to a string. Line may be an object, an array or a string. | ||
stringify(record) { | ||
var _record, column, columns, containsEscape, containsQuote, containsRowDelimiter, containsdelimiter, delimiter, err, escape, field, header, i, j, l, len, m, newrecord, quote, quoted, quotedMatch, quotedString, ref, ref1, regexp, shouldQuote, value; | ||
if (typeof record !== 'object') { | ||
return record; | ||
} | ||
({columns, delimiter, header, quote, escape} = this.options); | ||
// Record is an array | ||
if (Array.isArray(record)) { | ||
if (columns) { | ||
// We are getting an array but the user has specified output columns. In | ||
// this case, we respect the columns indexes | ||
record.splice(columns.length); | ||
} | ||
// Cast record elements | ||
for (i = j = 0, len = record.length; j < len; i = ++j) { | ||
field = record[i]; | ||
[err, value] = this.__cast(field, { | ||
index: i, | ||
column: i, | ||
records: this.info.records, | ||
header: header && this.info.records === 0 | ||
}); | ||
if (err) { | ||
this.emit('error', err); | ||
return; | ||
} | ||
record[i] = [value, field]; | ||
} | ||
} else { | ||
for (column in record) { | ||
_record.push(record[column]); | ||
// Record is a literal object | ||
_record = []; | ||
if (columns) { | ||
for (i = l = 0, ref = columns.length; (0 <= ref ? l < ref : l > ref); i = 0 <= ref ? ++l : --l) { | ||
field = get(record, columns[i].key); | ||
[err, value] = this.__cast(field, { | ||
index: i, | ||
column: columns[i].key, | ||
records: this.info.records, | ||
header: header && this.info.records === 0 | ||
}); | ||
if (err) { | ||
this.emit('error', err); | ||
return; | ||
} | ||
_record[i] = [value, field]; | ||
} | ||
} else { | ||
for (column in record) { | ||
field = record[column]; | ||
[err, value] = this.__cast(field, { | ||
index: i, | ||
column: columns[i].key, | ||
records: this.info.records, | ||
header: header && this.info.records === 0 | ||
}); | ||
if (err) { | ||
this.emit('error', err); | ||
return; | ||
} | ||
_record.push([value, field]); | ||
} | ||
} | ||
record = _record; | ||
_record = null; | ||
} | ||
record = _record; | ||
_record = null; | ||
} else if (columns) { // Note, we used to have @options.columns | ||
// We are getting an array but the user want specified output columns. In | ||
// this case, we respect the columns indexes | ||
record.splice(columns.length); | ||
} | ||
if (Array.isArray(record)) { | ||
newrecord = ''; | ||
for (i = l = 0, ref1 = record.length; (0 <= ref1 ? l < ref1 : l > ref1); i = 0 <= ref1 ? ++l : --l) { | ||
field = record[i]; | ||
type = typeof field; | ||
try { | ||
if (type === 'string') { | ||
// fine 99% of the cases | ||
field = this.options.cast.string(field); | ||
} else if (type === 'number') { | ||
field = this.options.cast.number(field); | ||
} else if (type === 'boolean') { | ||
field = this.options.cast.boolean(field); | ||
} else if (field instanceof Date) { | ||
field = this.options.cast.date(field); | ||
} else if (type === 'object' && field !== null) { | ||
field = this.options.cast.object(field); | ||
if (Array.isArray(record)) { | ||
newrecord = ''; | ||
for (i = m = 0, ref1 = record.length; (0 <= ref1 ? m < ref1 : m > ref1); i = 0 <= ref1 ? ++m : --m) { | ||
[value, field] = record[i]; | ||
if (err) { | ||
this.emit('error', err); | ||
return; | ||
} | ||
} catch (error) { | ||
err = error; | ||
this.emit('error', err); | ||
return; | ||
} | ||
if (field) { | ||
if (typeof field !== 'string') { | ||
this.emit('error', Error('Formatter must return a string, null or undefined')); | ||
return null; | ||
} | ||
containsdelimiter = field.indexOf(delimiter) >= 0; | ||
containsQuote = (quote !== '') && field.indexOf(quote) >= 0; | ||
containsEscape = field.indexOf(escape) >= 0 && (escape !== quote); | ||
containsRowDelimiter = field.indexOf(this.options.record_delimiter) >= 0; | ||
quoted = this.options.quoted; | ||
quotedString = this.options.quoted_string && typeof record[i] === 'string'; | ||
quotedMatch = this.options.quoted_match && typeof record[i] === 'string' && this.options.quoted_match.filter(function(quoted_match) { | ||
if (typeof quoted_match === 'string') { | ||
return record[i].indexOf(quoted_match) !== -1; | ||
} else { | ||
return quoted_match.test(record[i]); | ||
if (value) { | ||
if (typeof value !== 'string') { | ||
this.emit('error', Error(`Formatter must return a string, null or undefined, got ${JSON.stringify(value)}`)); | ||
return null; | ||
} | ||
}); | ||
quotedMatch = quotedMatch && quotedMatch.length > 0; | ||
shouldQuote = containsQuote || containsdelimiter || containsRowDelimiter || quoted || quotedString || quotedMatch; | ||
if (shouldQuote && containsEscape) { | ||
regexp = escape === '\\' ? new RegExp(escape + escape, 'g') : new RegExp(escape, 'g'); | ||
field = field.replace(regexp, escape + escape); | ||
containsdelimiter = value.indexOf(delimiter) >= 0; | ||
containsQuote = (quote !== '') && value.indexOf(quote) >= 0; | ||
containsEscape = value.indexOf(escape) >= 0 && (escape !== quote); | ||
containsRowDelimiter = value.indexOf(this.options.record_delimiter) >= 0; | ||
quoted = this.options.quoted; | ||
quotedString = this.options.quoted_string && typeof field === 'string'; | ||
quotedMatch = this.options.quoted_match && typeof field === 'string' && this.options.quoted_match.filter(function(quoted_match) { | ||
if (typeof quoted_match === 'string') { | ||
return value.indexOf(quoted_match) !== -1; | ||
} else { | ||
return quoted_match.test(value); | ||
} | ||
}); | ||
quotedMatch = quotedMatch && quotedMatch.length > 0; | ||
shouldQuote = containsQuote || containsdelimiter || containsRowDelimiter || quoted || quotedString || quotedMatch; | ||
if (shouldQuote && containsEscape) { | ||
regexp = escape === '\\' ? new RegExp(escape + escape, 'g') : new RegExp(escape, 'g'); | ||
value = value.replace(regexp, escape + escape); | ||
} | ||
if (containsQuote) { | ||
regexp = new RegExp(quote, 'g'); | ||
value = value.replace(regexp, escape + quote); | ||
} | ||
if (shouldQuote) { | ||
value = quote + value + quote; | ||
} | ||
newrecord += value; | ||
} else if (this.options.quoted_empty || ((this.options.quoted_empty == null) && field === '' && this.options.quoted_string)) { | ||
newrecord += quote + quote; | ||
} | ||
if (containsQuote) { | ||
regexp = new RegExp(quote, 'g'); | ||
field = field.replace(regexp, escape + quote); | ||
if (i !== record.length - 1) { | ||
newrecord += delimiter; | ||
} | ||
if (shouldQuote) { | ||
field = quote + field + quote; | ||
} | ||
newrecord += field; | ||
} else if (this.options.quoted_empty || ((this.options.quoted_empty == null) && record[i] === '' && this.options.quoted_string)) { | ||
newrecord += quote + quote; | ||
} | ||
if (i !== record.length - 1) { | ||
newrecord += delimiter; | ||
} | ||
record = newrecord; | ||
} | ||
record = newrecord; | ||
return record; | ||
} | ||
return record; | ||
}; | ||
// ## `Stringifier.prototype.headers` | ||
// ## `Stringifier.prototype.headers` | ||
// Print the header line if the option "header" is "true". | ||
Stringifier.prototype.headers = function() { | ||
var headers; | ||
if (!this.options.header) { | ||
return; | ||
// Print the header line if the option "header" is "true". | ||
headers() { | ||
var headers; | ||
if (!this.options.header) { | ||
return; | ||
} | ||
if (!this.options.columns) { | ||
return; | ||
} | ||
headers = this.options.columns.map(function(column) { | ||
return column.header; | ||
}); | ||
if (this.options.eof) { | ||
headers = this.stringify(headers) + this.options.record_delimiter; | ||
} else { | ||
headers = this.stringify(headers); | ||
} | ||
return this.push(headers); | ||
} | ||
if (!this.options.columns) { | ||
return; | ||
__cast(value, context) { | ||
var err, type; | ||
type = typeof value; | ||
try { | ||
if (type === 'string') { | ||
// Fine for 99% of the cases | ||
return [void 0, this.options.cast.string(value, context)]; | ||
} else if (type === 'number') { | ||
return [void 0, this.options.cast.number(value, context)]; | ||
} else if (type === 'boolean') { | ||
return [void 0, this.options.cast.boolean(value, context)]; | ||
} else if (value instanceof Date) { | ||
return [void 0, this.options.cast.date(value, context)]; | ||
} else if (type === 'object' && value !== null) { | ||
return [void 0, this.options.cast.object(value, context)]; | ||
} else { | ||
return [void 0, value, value]; | ||
} | ||
} catch (error) { | ||
err = error; | ||
return [err]; | ||
} | ||
} | ||
headers = this.options.columns.map(function(column) { | ||
return column.header; | ||
}); | ||
if (this.options.eof) { | ||
headers = this.stringify(headers) + this.options.record_delimiter; | ||
} else { | ||
headers = this.stringify(headers); | ||
} | ||
return this.push(headers); | ||
}; | ||
// ## `Stringifier.prototype.headers` | ||
// Print the header line if the option "header" is "true". | ||
Stringifier.normalize_columns = function(columns) { | ||
var column, k, v; | ||
if (columns == null) { | ||
return null; | ||
} | ||
if (columns != null) { | ||
if (typeof columns !== 'object') { | ||
throw Error('Invalid option "columns": expect an array or an object'); | ||
// ## `Stringifier.prototype.normalize_columns` | ||
normalize_columns(columns) { | ||
var column, k, v; | ||
if (columns == null) { | ||
return null; | ||
} | ||
if (!Array.isArray(columns)) { | ||
columns = (function() { | ||
var results; | ||
results = []; | ||
for (k in columns) { | ||
v = columns[k]; | ||
results.push({ | ||
key: k, | ||
header: v | ||
}); | ||
} | ||
return results; | ||
})(); | ||
} else { | ||
columns = (function() { | ||
var j, len, results; | ||
results = []; | ||
for (j = 0, len = columns.length; j < len; j++) { | ||
column = columns[j]; | ||
if (typeof column === 'string') { | ||
if (columns != null) { | ||
if (typeof columns !== 'object') { | ||
throw Error('Invalid option "columns": expect an array or an object'); | ||
} | ||
if (!Array.isArray(columns)) { | ||
columns = (function() { | ||
var results; | ||
results = []; | ||
for (k in columns) { | ||
v = columns[k]; | ||
results.push({ | ||
key: column, | ||
header: column | ||
key: k, | ||
header: v | ||
}); | ||
} else if (typeof column === 'object' && (column != null) && !Array.isArray(column)) { | ||
if (!column.key) { | ||
throw Error('Invalid column definition: property "key" is required'); | ||
} | ||
return results; | ||
})(); | ||
} else { | ||
columns = (function() { | ||
var j, len, results; | ||
results = []; | ||
for (j = 0, len = columns.length; j < len; j++) { | ||
column = columns[j]; | ||
if (typeof column === 'string') { | ||
results.push({ | ||
key: column, | ||
header: column | ||
}); | ||
} else if (typeof column === 'object' && (column != null) && !Array.isArray(column)) { | ||
if (!column.key) { | ||
throw Error('Invalid column definition: property "key" is required'); | ||
} | ||
if (column.header == null) { | ||
column.header = column.key; | ||
} | ||
results.push(column); | ||
} else { | ||
throw Error('Invalid column definition: expect a string or an object'); | ||
} | ||
if (column.header == null) { | ||
column.header = column.key; | ||
} | ||
results.push(column); | ||
} else { | ||
throw Error('Invalid column definition: expect a string or an object'); | ||
} | ||
} | ||
return results; | ||
})(); | ||
return results; | ||
})(); | ||
} | ||
} | ||
return columns; | ||
} | ||
return columns; | ||
}; | ||
module.exports.Stringifier = Stringifier; | ||
underscore = function(str) { | ||
@@ -478,0 +532,0 @@ return str.replace(/([A-Z])/g, function(_, match, index) { |
{ | ||
"version": "5.0.0", | ||
"version": "5.1.0", | ||
"name": "csv-stringify", | ||
@@ -20,7 +20,7 @@ "description": "CSV stringifier implementing the Node.js `stream.Transform` API", | ||
"devDependencies": { | ||
"@babel/cli": "^7.1.0", | ||
"@babel/core": "^7.1.0", | ||
"@babel/preset-env": "^7.1.0", | ||
"coffeescript": "~2.3.1", | ||
"csv-generate": "~2.1.0", | ||
"@babel/cli": "^7.2.0", | ||
"@babel/core": "^7.2.0", | ||
"@babel/preset-env": "^7.2.0", | ||
"coffeescript": "~2.3.2", | ||
"csv-generate": "~3.2.0", | ||
"mocha": "~5.2.0", | ||
@@ -27,0 +27,0 @@ "should": "~13.2.3" |
@@ -13,4 +13,3 @@ | ||
],{ | ||
header: true, | ||
columns: ['year', 'phone'] | ||
header: true | ||
}, function(err, data){ | ||
@@ -17,0 +16,0 @@ assert.equal( |
54113
20.3%17
6.25%1306
15.78%