/** * File system utilities. * @module app/utils/file_utils */ import filesystem from "fs"; import parseCSV from "csv-parse"; import stringifyCSVCallback from "csv-stringify"; /** * Helper function to load CSV from a file * @param {string} inputFile path to the file * @return {Promise} promise which will resolve with the parsed CSV */ const csvReadOptions = { columns: true, }; export const loadCSV = (inputFile, options = {}) => new Promise((resolve, reject) => { if (!filesystem.existsSync(inputFile)) { return reject("inputFile does not exist"); } const csvOptions = { ...csvReadOptions, ...options, }; filesystem.readFile(inputFile, "utf8", (error, text) => { if (error) { return reject(`Error reading file: ${error}`); } try { parseCSV(text, csvOptions, function (err, data) { if (err) { return reject("Error parsing JSON"); } resolve(data); }); } catch { reject("Error parsing JSON"); } }); }); /** * Helper function to stringify an array-of-arrays to a CSV (with Promise interface) * @param {Array[]} rows array of arrays * @return {Promise} promise which will resolve with the stringified CSV */ export const stringifyCSV = (rows) => new Promise((resolve, reject) => { stringifyCSVCallback(rows, (error, output) => { if (error) { reject(error); } else { resolve(output); } }); }); /** * Helper function to attempt to mitigate malicious CSV data * @param {Array[]} rows array of arrays * @return {Array[]} the sanitized input */ export const sanitizeCSV = (rows) => rows.map((row) => row.map((cell) => (cell && typeof cell === "string" ? "'" + cell : cell)) ); /** * Helper function to load JSON from a file * @param {string} inputFile path to the file * @return {Promise} promise which will resolve with the parsed JSON */ export const loadJSON = (inputFile) => new Promise((resolve, reject) => { if (!filesystem.existsSync(inputFile)) { return reject("inputFile does not exist"); } filesystem.readFile(inputFile, "utf8", (error, text) => { if (error) { return reject(`Error reading file: ${error}`); } try { const data = JSON.parse(text); resolve(data); } catch { reject("Error parsing JSON"); } }); }); const writeFileOptions = { replace: true, }; /** * Helper to write a string to a file * @param {string} outputFile the file to write to * @param {string|string[]} data the data to write * @param {Object} options options, by default will overwrite the existing file * @return {Promise} promise which will resolve when the file is saved */ export const writeFile = (outputFile, data, options = {}) => { options = { ...writeFileOptions, ...options }; return new Promise((resolve, reject) => { if (filesystem.existsSync(outputFile) && !options.replace) { return reject("outputFile exists"); } if (Array.isArray(data)) { data = data.join("\n"); } filesystem.writeFile(outputFile, data, { encoding: "utf8" }, (error) => { if (error) { return reject(`Error writing file: ${error}`); } resolve(); }); }); }; const writeJSONOptions = { ...writeFileOptions, indent: true, }; /** * Helper to write JSON data to a file * @param {string} outputFile the file to write to * @param {Object} data the data to write * @param {Object} options options, by default will overwrite the existing file * @return {Promise} promise which will resolve when the file is saved */ export const writeJSON = (outputFile, data, options = {}) => { options = { ...writeJSONOptions, ...options }; return new Promise((resolve, reject) => { let json; try { if (options.indent) { json = JSON.stringify(data, false, 2); } else { json = JSON.stringify(data); } } catch { return reject("couldn't stringify JSON"); } return writeFile(outputFile, json, options); }); };