UNPKG

@sugarcube/plugin-csv

Version:
131 lines (105 loc) 4.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _fp = require("lodash/fp"); var _fs = _interopRequireDefault(require("fs")); var _csvStringify = _interopRequireDefault(require("csv-stringify")); var _core = require("@sugarcube/core"); var _assertions = require("../assertions"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // Prefix keys in a list of pairs with a string and return it as an object, // e.g.: prefixPairsToObj('xx', [[0, 'a'], [1, 'b']]) => {xx_0: 'a', xx_1; 'b'} // Prefixing the namespace and turning a list of pairs to an object are handled // in one pass for performance reasons. const namespacePairsToObj = (0, _fp.curry)((ns, pairs) => (0, _fp.reduce)((memo, [k, v]) => (0, _fp.merge)(memo, { [`${ns}_${k}`]: v }), {}, pairs)); // lodash provides already `toPairs` for objects. This creates pairs of index // positions and values for arrays, e.g.: // toAryPairs(['a', 'b']) => [[0, 'a'], [1, 'b']]. const toAryPairs = xs => (0, _fp.zip)((0, _fp.range)(0, (0, _fp.size)(xs)), xs); // Create a flat version of a nested unit, e.g.: // flatten({a: {b: 23, c: {a: 42}}, b: [1, 2], c: 23}) => // {a_b: 23, a_c_a: 42, b_0: 1, b_1: 2, c: 23} const flatten = unit => (0, _fp.reduce)((memo, [key, value]) => { const iter = (0, _fp.flow)([namespacePairsToObj(key), flatten]); const flattenObj = (0, _fp.flow)([_fp.toPairs, iter]); const flattenAry = (0, _fp.flow)([toAryPairs, iter]); switch (value) { case (0, _fp.isPlainObject)(value) ? value : null: { return (0, _fp.merge)(memo, flattenObj(value)); } case (0, _fp.isArray)(value) ? value : null: { return (0, _fp.merge)(memo, flattenAry(value)); } case (0, _fp.isDate)(value) ? value : null: { return (0, _fp.merge)(memo, { [key]: value.toISOString() }); } default: { return (0, _fp.merge)(memo, { [key]: value }); } } }, {}, (0, _fp.toPairs)(unit)); // `flattenAndExpand` iterates over the whole input data and creates flat // objects from nested objects, while collecting all keynames. For performance // reasons, this happens in one pass. // flattenAndExpand([{a: 23}, {a: 23, b: {c: 42}}]) => // [[{a: 23}, {a: 23, b_c: 42}], [a, b_c]] const flattenAndExpand = (0, _fp.reduce)(([flatUnits, uniqKeys], unit) => { const keyNames = (0, _fp.flow)([_fp.keys, (0, _fp.concat)(uniqKeys), _fp.uniq]); const flattenedUnit = flatten(unit); return [(0, _fp.concat)(flatUnits, flattenedUnit), keyNames(flattenedUnit)]; }, [[], []]); const exportPlugin = (val, { cfg, log }) => { const filename = (0, _fp.get)("csv.filename", cfg); const delimiter = (0, _fp.get)("csv.delimiter", cfg); const skipEmpty = (0, _fp.get)("csv.skip_empty", cfg); if (skipEmpty && (0, _fp.size)(val.data) === 0) { log.info("Data pipeline is empty. Skip the export."); return val; } log.info(`Converting to csv and writing to ${filename}.`); log.debug(`Converting ${(0, _fp.size)(val.data)} units to CSV.`); const [data, keyNames] = flattenAndExpand(val.data); // The template is an object with all possible keys. I use it later to expand // objects that miss some keys. const template = (0, _fp.reduce)((memo, k) => (0, _fp.merge)(memo, { [k]: null }), {}, keyNames); // Pipe the csv stream into the file. const csv = (0, _csvStringify.default)({ header: true, quotedString: true, delimiter }); csv.pipe(_fs.default.createWriteStream(filename)); // eslint-disable-next-line promise/avoid-new return new Promise((resolve, reject) => { csv.on("error", reject); csv.on("finish", () => resolve(val)); // Avoid lodash forEach to have eager evaluation. data.forEach(r => csv.write((0, _fp.merge)(template, r))); csv.end(); }); }; const plugin = _core.plugin.liftManyA2([_assertions.assertFilename, exportPlugin]); plugin.desc = "Export data units to a file in CSV format."; plugin.argv = { "csv.filename": { default: "out.csv", nargs: 1, desc: "The file name to write the CSV to" }, "csv.skip_empty": { type: "boolean", desc: "Skip export of empty data pipelines." } }; var _default = plugin; exports.default = _default;