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.

837 lines (693 loc) 28.2 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.Document = undefined; var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); var _regenerator = require('babel-runtime/regenerator'); var _regenerator2 = _interopRequireDefault(_regenerator); var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _promise = require('babel-runtime/core-js/promise'); var _promise2 = _interopRequireDefault(_promise); var _getIterator2 = require('babel-runtime/core-js/get-iterator'); var _getIterator3 = _interopRequireDefault(_getIterator2); 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); 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 Documents /// @description This class is used to generate all the documents for the passed models var Documents = function (_Base) { (0, _inherits3.default)(Documents, _Base); function Documents() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var documents = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var globals = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var inputs = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; (0, _classCallCheck3.default)(this, Documents); var _this = (0, _possibleConstructorReturn3.default)(this, (Documents.__proto__ || (0, _getPrototypeOf2.default)(Documents)).call(this, options)); _this.options = _toJs2.default.extend({ count: 0 }, _this.options); _this.documents = documents; _this.globals = globals; _this.inputs = inputs; _this.total = 0; 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)(Documents, [{ key: 'build', value: function build(models) { var _this2 = this; models = _toJs2.default.clone(models); var todo = models.map(function (model) { return model.file; }); var result = []; function finished(dependencies) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = (0, _getIterator3.default)(dependencies), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var file = _step.value; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = (0, _getIterator3.default)(models), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var model = _step2.value; if (model.file === file && !model.complete) { return false; } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return true; } this.on('run', function () { // don't run again if the program should exit if (exit) return; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = (0, _getIterator3.default)(models), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var model = _step3.value; var index = todo.indexOf(model.file); if (index > -1 && finished(model.data.dependencies)) { todo.splice(index, 1); var document = _this2.document(model); if (!model.is_dependency) { result.push(document); } } // if the model is complete and it's dependants are also completed is no longer needed then remove it if (model.complete && finished(model.dependants)) { delete _this2.documents[model.name]; } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } if (!todo.length) { _this2.emit('finished'); } }); this.emit('run'); return new _promise2.default(function (resolve, reject) { _this2.once('error', function (err) { reject(err); }); _this2.once('finished', function () { _promise2.default.all(result).then(function (res) { _this2.inputs = {}; resolve(res); }); }); }); } }, { key: 'document', value: function document(model) { var _this3 = this; var document = new Document(this.options, this.documents, this.globals, this.inputs); return document.build(model).then(function () { var result = void 0; if (!model.is_dependency) { result = _this3.emit('data', _this3.documents[model.name], model); } // update the total documents number _this3.total += _this3.documents[model.name].length; model.complete = true; _this3.emit('run'); return result; }).catch(function (err) { exit = true; process.exit(2); _this3.emit('error', err); }); } }]); return Documents; }(_base2.default); /// @name Document /// @description This is used to generate documents based off a model exports.default = Documents; var Document = exports.Document = function (_Base2) { (0, _inherits3.default)(Document, _Base2); function Document() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var documents = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var globals = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var inputs = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; (0, _classCallCheck3.default)(this, Document); var _this4 = (0, _possibleConstructorReturn3.default)(this, (Document.__proto__ || (0, _getPrototypeOf2.default)(Document)).call(this, options)); _this4.options = _toJs2.default.extend({ count: 0 }, _this4.options); _this4.documents = documents; _this4.globals = globals; _this4.inputs = inputs; // set chance and faker without a seed by default // so it can still be used _this4.chance = new _chance2.default(); _this4.faker = _faker2.default; _this4.updateFakers(_this4.options.seed); return _this4; } ///# @name build ///# @description ///# This builds the documents from the passed model ///# @arg {object} model - The model to generate data from ///# @returns {array} - The array of documents that were generated (0, _createClass3.default)(Document, [{ key: 'build', value: function () { var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(model) { var _this5 = this; var spinner, update, delay, built_docs_map; return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: if (!this.documents[model.name]) { this.documents[model.name] = []; } this.updateFakers(model.seed); if (!model.data) { model.data = { count: 1 }; } spinner = this.spinner('Documents ' + model.name); spinner.text = '' + model.name; update = function update() { spinner.text = model.name + ' documents (' + _this5.documents[model.name].length + '/' + model.data.count + ')'; }; // if there is a pre_run function call it this.runData(model.data.pre_run, model); // if the count is set then overwrite the model.data.count if (this.options.count) { model.data.count = this.options.count; } spinner.start(); delay = function delay(duration) { return new _promise2.default(function (resolve) { return setTimeout(resolve, duration); }); }; built_docs_map = {}; _context2.next = 13; return (0, _utils.pool)(model.data.count, function () { var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(i) { var doc; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (!exit) { _context.next = 2; break; } return _context.abrupt('return'); case 2: update(); // this allows the spinner to actually update the count and doesn't affect performance much _context.next = 5; return _this5.buildDocument(model, i); case 5: doc = _context.sent; update(); _context.next = 9; return delay(0); case 9: // only push the document if the key is not already taken /* eslint no-underscore-dangle: ["error", { "allow": ["__key"] }] */ if (!built_docs_map[doc.__key]) { built_docs_map[doc.__key] = true; _this5.documents[model.name].push(doc); } case 10: case 'end': return _context.stop(); } } }, _callee, _this5); })); return function (_x10) { return _ref2.apply(this, arguments); }; }(), this.options.spinners ? 75 : 1000).then(function () { built_docs_map = {}; }).catch(function (err) { spinner.fail(err); }); case 13: this.runData(model.data.post_run, model); update(); spinner.stop(); return _context2.abrupt('return', this.documents[model.name]); case 17: case 'end': return _context2.stop(); } } }, _callee2, this); })); function build(_x9) { return _ref.apply(this, arguments); } return build; }() ///# @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 }, { 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, this.documents, 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 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 }); 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 _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 { (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) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } 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 _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = (0, _getIterator3.default)(_toJs2.default.entries(paths.model)), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { var _step5$value = (0, _slicedToArray3.default)(_step5.value, 2), i = _step5$value[0], str = _step5$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) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5.return) { _iterator5.return(); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } 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); } 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 _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = (0, _getIterator3.default)(_toJs2.default.entries(paths.model)), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { var _step6$value = (0, _slicedToArray3.default)(_step6.value, 2), i = _step6$value[0], str = _step6$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) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6.return) { _iterator6.return(); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } return doc; } }]); return Document; }(_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; }