@sugarcube/plugin-csv
Version:
CSV based input and output for SugarCube.
131 lines (105 loc) • 4.36 kB
JavaScript
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;
;