UNPKG

fakeit

Version:

Command-line utility that generates fake data which can be output as JSON, YAML, CSON, or CSV formats based on models defined in YAML.

680 lines (578 loc) 23.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.DocumentBuilder = undefined; var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); var _getIterator2 = require('babel-runtime/core-js/get-iterator'); var _getIterator3 = _interopRequireDefault(_getIterator2); var _promise = require('babel-runtime/core-js/promise'); var _promise2 = _interopRequireDefault(_promise); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); exports.transformValueToType = transformValueToType; exports.getPaths = getPaths; exports.typeToValue = typeToValue; var _faker = require('faker'); var _faker2 = _interopRequireDefault(_faker); var _chance = require('chance'); var _chance2 = _interopRequireDefault(_chance); var _base = require('./base'); var _base2 = _interopRequireDefault(_base); var _utils = require('./utils'); var _lodash = require('lodash'); var _toJs = require('to-js'); var _toJs2 = _interopRequireDefault(_toJs); var _stream = require('stream'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var exit = false; process.on('SIGINT', function () { exit = true; process.exit(2); }); //// /// @name Documents /// @page api/documents //// /// @name DocumentsStream /// @description This class is used to generate all the documents for the passed models var DocumentsStream = function (_Base) { (0, _inherits3.default)(DocumentsStream, _Base); function DocumentsStream() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var globals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var inputs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var output = arguments[3]; (0, _classCallCheck3.default)(this, DocumentsStream); var _this = (0, _possibleConstructorReturn3.default)(this, (DocumentsStream.__proto__ || (0, _getPrototypeOf2.default)(DocumentsStream)).call(this, options)); // eslint-disable-line max-params _this.options = _toJs2.default.extend({ count: 0 }, _this.options); _this.globals = globals; _this.inputs = inputs; _this.total = 0; // set a reference to the output, really all we're going to use this for is // it's handle to the bucket _this.output = output; return _this; } ///# @name build ///# @description ///# This takes an array of models and builds them ///# @arg {array} models - Array of models ///# @returns {array} of documents ///# @async (0, _createClass3.default)(DocumentsStream, [{ key: 'build', value: function build(models) { var _this2 = this; models = _toJs2.default.clone(models); var self = this; // hold reference to the instance var spinners = {}; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = (0, _getIterator3.default)(models), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var _ref2 = _step.value; var name = _ref2.name; spinners[name] = this.spinner(name); } // get the next document to build and generate } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } var current_model = models.pop(); var current_model_document = void 0; var next = function next() { // if the current model is undefined, we've successfully processed all of the items in the array if (!current_model) { return null; } // if we've generated all of the documents for a model, set the next model if (current_model_document && current_model_document.generated === current_model.data.count) { current_model_document.runData(current_model.data.post_run, current_model); // call the post run function spinners[current_model.name].stop(); // stop the spinner current_model = models.pop(); // get the next model current_model_document = null; // reset the model document builder } // if we have a model build it's next document and return it if (current_model) { // make sure we have a DocumentBuilder if (!current_model_document) { current_model_document = new DocumentBuilder(_this2.options, _this2.globals, _this2.inputs); // get a model document builder spinners[current_model.name].start(); current_model_document.runData(current_model.data.pre_run, current_model); // call the pre_run function } return current_model_document.buildDocument(current_model); // return a built document } }; // setup reader var reader = new _stream.Readable({ read: function read() { // if we should exist, just stop if (exit) { this.push(null); return; } this.push(next()); if (current_model) { // eslint-disable-next-line max-len spinners[current_model.name].text = current_model.name + ' documents (' + commaify(current_model_document.generated) + ' / ' + commaify(current_model.data.count) + ')'; self.total += 1; } }, objectMode: true }); // helper to properly display large # var commaify = function commaify(value) { return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); }; // setup writer var writer = new _stream.Writable({ write: function write(chunk, encoding, callback) { // if we were passed more data than we actually needed, chunk is undefined, just call the callback if (!chunk) { callback(); return; } self.output.outputter.bucket.upsert(chunk.__key, chunk, function (err) { // eslint-disable-line no-underscore-dangle callback(err); // notify that the data was processed regardlress of succes / failure }); }, objectMode: true, highWaterMark: 16 // default for object mode is 16 }); // pipe the streams together reader.pipe(writer); // return a process that will resolve when the writer has finished return new _promise2.default(function (resolve, reject) { // if either the reader or the writer errors reject the promise reader.on('error', function (err) { console.error('Reader Error:', err); reader.destroy(err); reject(err); }); writer.on('error', function (err) { console.error('Writer Error:', err); reader.destroy(err); reject(err); }); // resolve ones the writer finishes writer.on('finish', function () { self.inputs = {}; self.globals = {}; resolve([]); }); }); } }]); return DocumentsStream; }(_base2.default); exports.default = DocumentsStream; var DocumentBuilder = exports.DocumentBuilder = function (_Base2) { (0, _inherits3.default)(DocumentBuilder, _Base2); function DocumentBuilder() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var globals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var inputs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; (0, _classCallCheck3.default)(this, DocumentBuilder); var _this3 = (0, _possibleConstructorReturn3.default)(this, (DocumentBuilder.__proto__ || (0, _getPrototypeOf2.default)(DocumentBuilder)).call(this, options)); _this3.options = _toJs2.default.extend({ count: 0 }, _this3.options); _this3.globals = globals; _this3.inputs = inputs; // set chance and faker without a seed by default // so it can still be used _this3.chance = new _chance2.default(); _this3.faker = _faker2.default; _this3.generated = 0; // hold the number of generated documents _this3.updateFakers(_this3.options.seed); return _this3; } ///# @name updateFakers ///# @description ///# If a usable seed is passed then this updates the instances of chance and faker to use a seed version ///# @arg {number, null} seed - The seed to use for this instance ///# @arg {number} modifier (0, _createClass3.default)(DocumentBuilder, [{ key: 'updateFakers', value: function updateFakers(seed) { // if the seed is a number then update // the instance of chance and faker if (typeof seed === 'number') { this.chance = new _chance2.default(seed); this.faker.seed(seed); } } ///# @name runData ///# @description used to run the different functions that the users can pass in into the data object ///# @arg {function} fn - The function to run ///# @arg {*} context - The `this` context ///# @arg {number} index [0] - The current index of the generated items ///# @returns {*} - What ever the function that runs returns }, { key: 'runData', value: function runData(fn, context) { var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; if (_toJs2.default.type(fn) === 'function') { try { return fn.call(context, null, this.globals, this.inputs, this.faker, this.chance, index, require); } catch (e) { e.message = fn.name + ' failed, ' + e.message; this.log('error', e); } } } ///# @name buildDocument ///# @description ///# builds a document ///# @arg {object} model - The model to build the document from ///# @arg {number} index [0] - The place in the list this item is being run from }, { key: 'buildDocument', value: function buildDocument(model) { var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var sub_doc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; index = index || this.generated || 0; var key_type = _toJs2.default.type(model.key); var paths = getPaths(model); // generate the initial values var doc = this.initializeDocument(model, paths); // if there is a pre_build function for the document call it this.runData(model.data.pre_build, doc, index); doc = this.buildObject(model, doc, paths, index); doc = this.postProcess(model, doc, paths, index); // if there is a post_build function for the document call it this.runData(model.data.post_build, doc, index); // build the key for the document var key = void 0; if (key_type === 'object') { model.key.data = model.key.data || {}; key = this.buildValue(model.key, typeToValue(model.key.type), doc, index); } else if (key_type === 'string') { key = (0, _lodash.get)(doc, model.key); } key = key || model.name + '_' + index; // @todo update this to use `new Map`, or `new WeakMap`; Object.defineProperty(doc, '__key', { value: key }); Object.defineProperty(doc, '__name', { value: model.name }); // only increment the generated if we're dealing with the parent document, not nested objects if (!sub_doc) { this.generated += 1; } return doc; } ///# @name initializeDocument ///# @description initializes a documents default values ///# @arg {object} model - The model to parse ///# @arg {object} paths - The paths to loop over ///# @returns {object} - The document with the defaults }, { key: 'initializeDocument', value: function initializeDocument(model, paths) { if (!paths || !paths.model || !paths.document) { paths = getPaths(model); } var doc = {}; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = (0, _getIterator3.default)(_toJs2.default.entries(paths.model)), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var _step2$value = (0, _slicedToArray3.default)(_step2.value, 2), i = _step2$value[0], str = _step2$value[1]; var key = paths.document[i]; // set a key for error messaging try { (0, _lodash.set)(doc, key, typeToValue((0, _lodash.get)(model, str).type)); } catch (e) { this.log('error', 'Initializing Properties in Model: "' + model.name + '" for Key: "' + key + '"\n', e); } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return doc; } ///# @name buildObject ///# @description builds an object based on a model ///# @arg {object} model - The model to parse ///# @arg {object} doc - The document to update ///# @arg {object} paths - The paths to loop over ///# @arg {number} index - The current index ///# @returns {object} - The document with the defaults }, { key: 'buildObject', value: function buildObject(model, doc, paths, index) { var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = (0, _getIterator3.default)(_toJs2.default.entries(paths.model)), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var _step3$value = (0, _slicedToArray3.default)(_step3.value, 2), i = _step3$value[0], str = _step3$value[1]; var key = paths.document[i]; // set a key for error messaging try { var value = this.buildValue((0, _lodash.get)(model, str), (0, _lodash.get)(doc, key), doc, index); (0, _lodash.set)(doc, key, value); } catch (e) { this.log('error', 'Building Properties in Model: "' + model.name + '" for Key: "' + key + '"\n', e); } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } return doc; } ///# @name buildValue ///# @description builds a single value based on a property definition ///# @arg {object} property - The property to run ///# To build a normal value ///# ```js ///# { ///# type: '', // 'object', 'structure', 'string', 'number', 'float', 'integer', etc.. ///# data: { ///# pre_build() {}, // optional ///# value: '', // optional ///# build() {}, // optional ///# fake: '', // optional ///# } ///# } ///# ``` ///# To build an array ///# ```js ///# { ///# type: 'array', ///# items: { ///# type: '', // 'object', 'structure', 'string', 'number', 'float', 'integer', etc.. ///# data: { ///# pre_build() {}, // optional ///# value: '', // optional ///# build() {}, // optional ///# fake: '', // optional ///# } ///# } ///# } ///# ``` ///# @arg {*} value - The default value ///# @arg {object} doc [{}] - The current document ///# @arg {number} index [0] - The place in the list this item is being run from ///# @return {*} - The result }, { key: 'buildValue', value: function buildValue(property, value) { var doc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; if (property.data) { if (property.data.pre_build) { value = this.runData(property.data.pre_build, doc, index); } if (property.data.value) { return property.data.value; } else if (property.data.build) { return this.runData(property.data.build, doc, index); } else if (property.data.fake) { return this.faker.fake(property.data.fake); } } else if (property.type === 'array' && property.items) { var _property$items$data = property.items.data, count = _property$items$data.count, min = _property$items$data.min, max = _property$items$data.max; if (count <= 0 && !!max) { count = this.chance.integer({ min: min, max: max }); } // builds a complex array if (property.items.type === 'object') { for (var i = 0; i < count; i++) { value[i] = this.buildDocument(property.items, index, true); } return value; } // builds a simple array for (var _i = 0; _i < count; _i++) { var result = this.buildValue(property.items, typeToValue(property.items.type), doc, index); if (result !== undefined) { // eslint-disable-line no-undefined value.push(result); } } return value; } return value; } ///# @name postProcess ///# @description Post process a document after generation ///# @arg {object} model - The model to parse ///# @arg {object} doc - The document to update ///# @arg {object} paths - The paths to loop over ///# @arg {number} index [0] - The current index ///# @returns {object} - The updated document }, { key: 'postProcess', value: function postProcess(model, doc, paths) { var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = (0, _getIterator3.default)(_toJs2.default.entries(paths.model)), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var _step4$value = (0, _slicedToArray3.default)(_step4.value, 2), i = _step4$value[0], str = _step4$value[1]; var key = paths.document[i]; // set a key for error messaging try { var _get = (0, _lodash.get)(model, str), _get$data = _get.data, data = _get$data === undefined ? {} : _get$data, _get$items = _get.items, items = _get$items === undefined ? {} : _get$items, type = _get.type; var value = (0, _lodash.get)(doc, key); // if there is a post_build block if (data.post_build) { var temp = this.runData(data.post_build, doc, index); if (temp != null) { value = temp; } } else if ((items.data || {}).post_build && items.type !== 'object' // if the type is an object it will run each item through this function already ) { for (var a = 0; a < value.length; a++) { var _temp = transformValueToType(items.type, this.runData(items.data.post_build, doc[key][a], index)); if (_temp != null) { value[a] = _temp; } } } (0, _lodash.set)(doc, key, transformValueToType(type, value)); } catch (e) { this.log('error', 'Transforming Properties in Model: "' + model.name + '" for Key: "' + key + '"\n', e); } } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } return doc; } }]); return DocumentBuilder; }(_base2.default); /// @name transformValueToType /// @description This will transform a value to the correct type /// @arg {string} type - The type to convert the value to /// @arg {*} value - The actual value /// @returns {*} - The converted value function transformValueToType(type, value) { if (type == null || value == null || type === 'array') { return value; } // if it is an integer make sure it is treated as such if ('number,integer,long'.indexOf(type) !== -1) { return parseInt(value); } // if it is a double / float make sure it is treated as such if ('double,float'.indexOf(type) !== -1) { return parseFloat(value); } // if it is a string make sure it is treated as such if (type === 'string') { return value.toString(); } // if it is a string make sure it is treated as such if ('boolean,bool'.indexOf(type) !== -1) { // if the value is a string that is 'false', '0', 'undefined', or 'null' as a string set a boolean false if (typeof value === 'string' && (value === 'false' || value === '0' || value === 'undefined' || value === 'null')) { return false; } return Boolean(value); } return value; } /// @name getPaths /// @description finds all the paths to be used /// @arg {object} obj - The object to be searched /// @returns {object} /// ```js /// { /// model: [], /// document: [], /// } /// ``` function getPaths(obj) { // finds all of the properties paths in a model var model = (0, _utils.objectSearch)(obj, /^properties\.([^.]+|(?!items\.).+properties\.[^.]+)$/).filter(function (str) { return !(str.indexOf('items.properties') !== -1); }); return { model: model, // finds all of the paths that will be used by a rendered document document: model.join(',').replace(/properties\./g, '').split(',') }; } /// @name typeToValue /// @description generates the initial value for a variable based on the data type /// @arg {string} type /// @returns {*} - What ever the value is that matches it. /// @raw-code function typeToValue(type) { var types = {}; types.string = ''; types.object = types.structure = {}; types.number = types.integer = types.double = types.long = types.float = 0; types.array = []; types.boolean = types.bool = false; return types[type] != null ? types[type] : null; }