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
JavaScript
'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;
}