UNPKG

node-generate

Version:

Runs custom generators

186 lines (150 loc) 6.06 kB
/** * Module dependencies */ var util = require('util'); var _ = require('lodash'); _.defaults = require('merge-defaults'); var async = require('async'); var path = require('path'); var reportback = require('reportback')(); var pathRegexp = require('./util').pathRegexp; var rootGenerator = require('./rootGenerator'); var rootScope = require('./rootScope'); var generateTarget = require('./target'); /** * Run a generator given an existing scope. * * @param {Object} Generator * @param {Object} scope * @param {Switchback} cb */ function generate(Generator, scope, cb) { var sb = reportback.extend(cb, { error: cb.error, invalid: cb.invalid, alreadyExists: 'error' }); // Merge with root scope _.defaults(scope, rootScope); // TODO: validate args more thoroughly if (!_.isArray(scope.args)) { return sb(new Error('Invalid `scope.args` passed to generator: ' + util.inspect(scope.args))); } // Ensure that `rootPath` exists // TODO: Ensure that `rootPath` is reasonable // (i.e. within highest acceptable path-- prevents accidental smashing of file-system, etc.) if (!scope.rootPath) { return sb(new Error('Invalid `scope.rootPath` passed to generator: ' + util.inspect(scope.rootPath))); } // TODO: deprecate this: // // Alias first handful of arguments on scope object // for easy access and use as :params in `targets` keys // _.defaults(scope, { // arg0: scope.args[0], // arg1: scope.args[1], // arg2: scope.args[2], // arg3: scope.args[3] // }); // Resolve string shorthand for generator defs // to `{ generator: 'originalDef' }` if (typeof Generator === 'string') { var generatorName = Generator; Generator = { generator: generatorName }; } // Merge with root generator _.defaults(Generator, rootGenerator); // Run the generator's `before()` method proceeding Generator.before(scope, reportback.extend({ error: sb.error, invalid: sb.invalid, success: function() { // Emit output sb.log.verbose('Generating ' + util.inspect(Generator) + ' at ' + scope.rootPath + '...'); // Process all of the generator's targets concurrently async.each(Object.keys(Generator.targets), function(keyPath, async_each_cb) { var async_each_sb = reportback.extend(async_each_cb); // Create a new scope object for this target, // with references to the important bits of the original. // (depth will be passed-by-value, but that's what we want) // // Then generate the target, passing along a reference to // the base `generate` method to allow for recursive generators. var target = Generator.targets[keyPath]; if (!target) return async_each_sb(new Error('Generator error: Invalid target: {"' + keyPath + '": ' + util.inspect(target) + '}')); // Input tolerance if (keyPath === '') keyPath = '.'; // Interpret `keyPath` using express's parameterized route conventions, // first parsing params, then replacing them with their proper values from scope. var params = []; pathRegexp(keyPath, params); var err; var parsedKeyPath = _.reduce(params, function(keyPath, param) { try { var paramMatchExpr = ':' + param.name; var actualParamValue = scope[param.name]; if (!actualParamValue) { err = new Error( 'Generator error:\n' + 'A scope variable (`' + param.name + '`) was referenced in target: `' + keyPath + '`,\n' + 'but `' + param.name + '` does not exist in the generator\'s scope.' ); return false; } actualParamValue = String(actualParamValue); return keyPath.replace(paramMatchExpr, actualParamValue); } catch (e) { err = new Error('Generator error: Could not parse target key: ' + keyPath); err.message = e; return false; } }, keyPath); if (!parsedKeyPath) return async_each_sb(err); // Create path from `rootPath` to `keyPath` to use as the `rootPath` // for any generators or helpers in this target. // (use a copy so that child generators don't mutate the scope) var targetScope = _.merge({}, scope, { rootPath: path.resolve(scope.rootPath, parsedKeyPath), // Include reference to original keypath for error reporting keyPath: keyPath }); // If `target` is an array, run each item if (_.isArray(target)) { async.eachSeries(target, function(targetItem, async_eachSeries_cb) { generateTarget({ target: targetItem, parent: Generator, scope: _.cloneDeep(targetScope), recursiveGenerate: generate }, async_eachSeries_cb); }, async_each_sb); return; } // Otherwise, just run the single target generator/helper generateTarget({ target: target, parent: Generator, scope: targetScope, recursiveGenerate: generate }, async_each_sb); }, // </async.each.iterator> function done(err) { // Expose a `error` handler in generators if (err) { var errorFn = Generator.error || function defaultError(err, scope, _cb) { return _cb(err); }; return errorFn(err, scope, sb); } // Expose a `after` handler in generators (on success only) var afterFn = Generator.after || function defaultAfter(scope, _cb) { return _cb(); }; return afterFn(scope, sb); }); // </async.each> } // </Generator.before -> success> })); // </Generator.before> } module.exports = generate;