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