UNPKG

fakeit-facet

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.

820 lines (637 loc) 30.6 kB
"use strict"; var _Reflect$construct = require("@babel/runtime-corejs3/core-js-stable/reflect/construct"); var _sliceInstanceProperty = require("@babel/runtime-corejs3/core-js-stable/instance/slice"); var _Array$from = require("@babel/runtime-corejs3/core-js-stable/array/from"); var _Symbol = require("@babel/runtime-corejs3/core-js-stable/symbol"); var _getIteratorMethod = require("@babel/runtime-corejs3/core-js/get-iterator-method"); var _Array$isArray = require("@babel/runtime-corejs3/core-js-stable/array/is-array"); var _Object$defineProperty2 = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); _Object$defineProperty2(exports, "__esModule", { value: true }); exports["default"] = exports.Document = void 0; exports.getPaths = getPaths; exports.transformValueToType = transformValueToType; exports.typeToValue = typeToValue; var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator")); var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map")); var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of")); var _splice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/splice")); var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise")); var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); var _setTimeout2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set-timeout")); var _defineProperty = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/define-property")); var _entries = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/entries")); var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); var _parseInt2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/parse-int")); var _parseFloat2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/parse-float")); var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/slicedToArray")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass")); var _inherits2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/inherits")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/getPrototypeOf")); var _faker = _interopRequireDefault(require("faker")); var _chance = _interopRequireDefault(require("chance")); var _lodash = require("lodash"); var _toJs = _interopRequireDefault(require("to-js")); var _base = _interopRequireDefault(require("./base")); var _utils = require("./utils"); function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof _Symbol !== "undefined" && _getIteratorMethod(o) || o["@@iterator"]; if (!it) { if (_Array$isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { var _context14; if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = _sliceInstanceProperty(_context14 = Object.prototype.toString.call(o)).call(_context14, 8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return _Array$from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = _Reflect$construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !_Reflect$construct) return false; if (_Reflect$construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 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 = /*#__PURE__*/function (_Base) { (0, _inherits2["default"])(Documents, _Base); var _super = _createSuper(Documents); function Documents() { var _this; 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, _classCallCheck2["default"])(this, Documents); _this = _super.call(this, options); _this.options = _toJs["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, _createClass2["default"])(Documents, [{ key: "build", value: function build(models) { var _this2 = this; models = _toJs["default"].clone(models); var todo = (0, _map["default"])(models).call(models, function (model) { return model.file; }); var result = []; function finished(dependencies) { var _iterator = _createForOfIteratorHelper(dependencies), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var file = _step.value; var _iterator2 = _createForOfIteratorHelper(models), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var model = _step2.value; if (model.file === file && !model.complete) { return false; } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return true; } this.on('run', function () { // don't run again if the program should exit if (exit) return; var _iterator3 = _createForOfIteratorHelper(models), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var model = _step3.value; var index = (0, _indexOf["default"])(todo).call(todo, model.file); if (index > -1 && finished(model.data.dependencies)) { (0, _splice["default"])(todo).call(todo, 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) { _iterator3.e(err); } finally { _iterator3.f(); } if (!todo.length) { _this2.emit('finished'); } }); this.emit('run'); return new _promise["default"](function (resolve, reject) { _this2.once('error', function (err) { reject(err); }); _this2.once('finished', function () { _promise["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; 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; _this3.emit('error', err); process.exit(2); }); } }]); return Documents; }(_base["default"]); /// @name Document /// @description This is used to generate documents based off a model exports["default"] = Documents; var Document = /*#__PURE__*/function (_Base2) { (0, _inherits2["default"])(Document, _Base2); var _super2 = _createSuper(Document); function Document() { var _this4; 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, _classCallCheck2["default"])(this, Document); _this4 = _super2.call(this, options); _this4.options = _toJs["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 _chance["default"](); _this4.faker = _faker["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, _createClass2["default"])(Document, [{ key: "build", value: function () { var _build = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(model) { var _this5 = this; var spinner, update, delay, built_docs_map; return _regenerator["default"].wrap(function _callee2$(_context4) { while (1) { switch (_context4.prev = _context4.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 ".concat(model.name)); spinner.text = model.name; update = function update() { var _context, _context2; spinner.text = (0, _concat["default"])(_context = (0, _concat["default"])(_context2 = "".concat(model.name, " documents (")).call(_context2, _this5.documents[model.name].length, "/")).call(_context, 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 _promise["default"](function (resolve) { return (0, _setTimeout2["default"])(resolve, duration); }); }; built_docs_map = {}; _context4.next = 13; return (0, _utils.pool)(model.data.count, /*#__PURE__*/function () { var _ref = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(i) { var doc; return _regenerator["default"].wrap(function _callee$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: if (!exit) { _context3.next = 2; break; } return _context3.abrupt("return"); case 2: update(); // this allows the spinner to actually update the count and doesn't affect performance much _context3.next = 5; return _this5.buildDocument(model, i); case 5: doc = _context3.sent; update(); _context3.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 _context3.stop(); } } }, _callee); })); return function (_x2) { return _ref.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 _context4.abrupt("return", this.documents[model.name]); case 17: case "end": return _context4.stop(); } } }, _callee2, this); })); function build(_x) { return _build.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 _chance["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 (_toJs["default"].type(fn) === 'function') { try { return fn.call(context, this.documents, this.globals, this.inputs, this.faker, this.chance, index, require); } catch (e) { var _context5; e.message = (0, _concat["default"])(_context5 = "".concat(fn.name, " failed, ")).call(_context5, 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 _context6; var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var key_type = _toJs["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; 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 || (0, _concat["default"])(_context6 = "".concat(model.name, "_")).call(_context6, index); // TODO: update this to use `new Map`, or `new WeakMap`; (0, _defineProperty["default"])(doc, '__key', { value: key }); (0, _defineProperty["default"])(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 _iterator4 = _createForOfIteratorHelper((0, _entries["default"])(_toJs["default"]).call(_toJs["default"], paths.model)), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var _step4$value = (0, _slicedToArray2["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) { var _context7; this.log('error', (0, _concat["default"])(_context7 = "Initializing Properties in Model: \"".concat(model.name, "\" for Key: \"")).call(_context7, key, "\"\n"), e); } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } 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 _iterator5 = _createForOfIteratorHelper((0, _entries["default"])(_toJs["default"]).call(_toJs["default"], paths.model)), _step5; try { for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) { var _step5$value = (0, _slicedToArray2["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) { var _context8; this.log('error', (0, _concat["default"])(_context8 = "Building Properties in Model: \"".concat(model.name, "\" for Key: \"")).call(_context8, key, "\"\n"), e); } } } catch (err) { _iterator5.e(err); } finally { _iterator5.f(); } 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; } if (property.data.build) { return this.runData(property.data.build, doc, index); } if (property.data.fake) { return this.faker.fake(property.data.fake); } } else if (property.type === 'array' && property.items) { var count = property.items.data.count; var _property$items$data = property.items.data, 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 _iterator6 = _createForOfIteratorHelper((0, _entries["default"])(_toJs["default"]).call(_toJs["default"], paths.model)), _step6; try { for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) { var _step6$value = (0, _slicedToArray2["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 === void 0 ? {} : _get$data, _get$items = _get.items, items = _get$items === void 0 ? {} : _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) { var _context9; this.log('error', (0, _concat["default"])(_context9 = "Transforming Properties in Model: \"".concat(model.name, "\" for Key: \"")).call(_context9, key, "\"\n"), e); } } } catch (err) { _iterator6.e(err); } finally { _iterator6.f(); } return doc; } }]); return Document; }(_base["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 exports.Document = Document; function transformValueToType(type, value) { var _context10, _context11, _context12; if (type == null || value == null || type === 'array') { return value; } // if it is an integer make sure it is treated as such if ((0, _includes["default"])(_context10 = 'number,integer,long').call(_context10, type)) { return (0, _parseInt2["default"])(value); } // if it is a double / float make sure it is treated as such if ((0, _includes["default"])(_context11 = 'double,float').call(_context11, type)) { return (0, _parseFloat2["default"])(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 ((0, _includes["default"])(_context12 = 'boolean,bool').call(_context12, type)) { // 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) { var _context13; // finds all of the properties paths in a model var model = (0, _filter["default"])(_context13 = (0, _utils.objectSearch)(obj, /^properties\.([^.]+|(?!items\.).+properties\.[^.]+)$/)).call(_context13, function (str) { return !(0, _includes["default"])(str).call(str, 'items.properties'); }); 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; }