var fs = require('fs'), nodePath = require('path'), _ = require('lodash'), async = require('async'), mkdirp = require('mkdirp'), // @todo: ES6: Change the sequence below to use object destructuring when Node v4 support is dropped joinPath = nodePath.join, parsePath = nodePath.parse, resolvePath = nodePath.resolve, /** * The root path specifier * * @const * @private * @type {string} */ E = '', /** * Default timestamp separator. * * @const * @private * @type {string} */ TS_SEP = '-', /** * Writes the specified content to a file at the provided path. * * @param {Object} path - A set of path details for file writing. * @param {String|Buffer} content - The content to be written to the file. * @param {Object} options - A set of options for the current file write. * @param {Function} cb - The callback invoked when the file writing operation has completed, with/without errors. */ writeFile = function (path, content, options, cb) { fs.writeFile(path.unparsed, content, function (err) { cb(_.set(err, 'help', `error writing file "${path.unparsed}" for ${options.name || 'unknown-source'}`), path); }); }, /** * Generate a timestamp from date * * @param {Date=} date - The timestamp used to mark the exported file. * @param {String=} separator - The optional string with which to separate different sections of the timestamp, * defaults to TS_SEP * @returns {String} - yyyy-mm-dd-HH-MM-SS-MS-0 */ timestamp = function (date, separator) { // use the iso string to ensure left padding and other stuff is taken care of return (date || new Date()).toISOString().replace(/[^\d]+/g, _.isString(separator) ? separator : TS_SEP); }; /** * Module whose job is to export a file which is in an export format. * * @param {Object} options - The set of file export options. * @param {String} options.path - The path to the exported file. * @param {String|Object} options.content - The JSON / stringified content that is to be written to the file. * @param {Function} done - The callback whose invocation marks the end of the file export routine. * @returns {*} */ module.exports = function (options, done) { // parse the path if one is available as string var path = _.isString(options.path) && parsePath(resolvePath(options.path)), content = _.isPlainObject(options.content) ? JSON.stringify(options.content, 0, 2) : (options.content || E); // if a path was not provided by user, we need to prepare the default path. but create the default path only if one // is provided. if (!path && _.isString(options.default)) { path = parsePath(options.default); // delete the path and directory if one is detected when parsing defaults path.root = E; path.dir = 'newman'; // append timestamp path.name = `${path.name}-${timestamp()}0`; // @todo make -0 become incremental if file name exists path.base = path.name + path.ext; } // final check that path is valid if (!(path && path.base)) { return; } // now sore the unparsed result back for quick re-use during writing and a single place for unparsing path.unparsed = joinPath(path.dir, path.base); // in case the path has a directory, ensure that the directory is available if (path.dir) { async.waterfall([ function (next) { mkdirp(path.dir) .then(() => { return next(); }) .catch((err) => { return next(_.set(err, 'help', `error creating path for file "${path.unparsed}" for ${options.name || 'unknown-source'}`)); }); }, function (next) { fs.stat(path.unparsed, function (err, stat) { // eslint-disable-line handle-callback-err next(null, stat); }); }, function (stat, next) { var target; // handle cases where the specified export path is a pre-existing directory if (stat && stat.isDirectory()) { target = parsePath(options.default); // append timestamp // @todo make -0 become incremental if file name exists target.name += '-' + timestamp() + '0'; target.base = target.name + target.ext; path.unparsed = joinPath(path.unparsed, target.base); } next(null, path); }, function (path, next) { writeFile(path, content, options, next); } ], done); } else { writeFile(path, content, options, done); } };