Skip to content

Commit

Permalink
Merge pull request #380 from SBoudrias/refactor-env
Browse files Browse the repository at this point in the history
Env refactoring
  • Loading branch information
passy committed Oct 27, 2013
2 parents 2dc784d + 8058ef1 commit 7aa8710
Show file tree
Hide file tree
Showing 5 changed files with 430 additions and 190 deletions.
121 changes: 64 additions & 57 deletions lib/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ util.inherits(Environment, events.EventEmitter);
* The `error` event is emitted with the error object, if no `error` listener
* is registered, then we throw the error.
*
* @param {Object} err
* @param {Object} err
* @return {Error} err
*/

Environment.prototype.error = function error(err) {
Expand All @@ -87,7 +88,7 @@ Environment.prototype.error = function error(err) {
throw err;
}

return this;
return err;
};

/**
Expand Down Expand Up @@ -193,23 +194,12 @@ Environment.prototype.help = function help(name) {
};

/**
* Registers a specific `generator` to this environment. A generator can be a
* simple function or an object extending from `Generators.Base`. The later
* method is favored as it allows you to specify options/arguments for
* self-documented generator with `USAGE:` and so on.
*
* In case of a simple function, the generator does show up in the `--help`
* output, but without any kind of arguments/options. You must document them
* manually with your `USAGE` file.
*
* In any case, the API available in generators is the same. Raw functions are
* executed within the context of a `new Generators.Base`.
* Registers a specific `generator` to this environment. This generator is stored under
* provided namespace, or a default namespace format if none if available.
*
* `register()` can also take Strings, in which case it is considered a
* filepath to `require()`.
*
* @param {String|Function} name
* @param {String} namespace
* @param {String} name - Filepath to the a generator or a NPM module name
* @param {String} namespace - Namespace under which register the generator (optionnal)
* @return {this}
*/

Environment.prototype.register = function register(name, namespace) {
Expand All @@ -218,43 +208,23 @@ Environment.prototype.register = function register(name, namespace) {
}

var self = this;
var filepath = name;
var ns;

if (filepath[0] === '.') {
filepath = path.resolve(filepath);
}

if (filepath[0] === '~') {
filepath = process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'] + filepath.slice(1);
}
var modulePath = this.resolveModulePath(name);
namespace || (namespace = this.namespace(modulePath));

var generatorProps = {};
generatorProps.resolved = require.resolve(filepath);
generatorProps.namespace = this.namespace(generatorProps.resolved);

ns = namespace || generatorProps.namespace || this.namespace(name);

if (!ns) {
if (!namespace) {
return this.error(new Error('Unable to determine namespace.'));
}

// normalize what we got, at this point if we were unable to find a
// namespace, we just consider the directory name under which this generator
// is found.
if (ns.indexOf('/') !== -1) {
ns = path.basename(path.dirname(ns));
}

Object.defineProperty(this.generators, ns, {
// TODO: Move to a store module?
Object.defineProperty(this.generators, namespace, {
get: function () {
var Generator = require(generatorProps.resolved);
Generator.resolved = generatorProps.resolved;
Generator.namespace = generatorProps.namespace;
var Generator = require(modulePath);
Generator.resolved = modulePath;
Generator.namespace = namespace;
return Generator;
},
set: function (val) {
Object.defineProperty(self.generators, ns, {
Object.defineProperty(self.generators, namespace, {
value: val,
enumerable: true,
configurable: true,
Expand All @@ -265,22 +235,35 @@ Environment.prototype.register = function register(name, namespace) {
configurable: true
});

debug('Registered %s (%s)', generatorProps.namespace, generatorProps.resolved);
debug('Registered %s (%s)', namespace, modulePath);
return this;
};

/**
* Register a stubbed generator to this environment. This method allow to register raw
* functions under the provided namespace. `registerStub` will enforce the function passed
* to extend the Base generator automatically.
*
* @param {Function} Generator - A Generator constructor or a simple function
* @param {String} namespace - Namespace under which register the generator
* @return {this}
*/

Environment.prototype.registerStub = function registerStub(Generator, namespace) {
if (!_.isFunction(Generator)) {
return this.error(new Error('You must provide a stub function to register.'));
}
if (!_.isString(namespace)) {
return this.error(new Error('You must provide a namespace to register.'));
}

var isRaw = function (generator) {
function isRaw(generator) {
generator = Object.getPrototypeOf(generator.prototype);
var methods = ['option', 'argument', 'hookFor', 'run'];
return methods.filter(function (method) {
return !!generator[method];
}).length !== methods.length;
};
}

if (isRaw(Generator)) {
var newGenerator = function () {
Expand Down Expand Up @@ -312,14 +295,17 @@ Environment.prototype.namespaces = function namespaces() {
* get `angular:common:all` then we get `angular:common` as a fallback (unless
* an `angular:common:all` generator is registered).
*
* @param {String} namespace
* @param {String} namespace
* @return {Generator} - the generator registered under the namespace
*/

Environment.prototype.get = function get(namespace) {
// Stop the recursive search if nothing is left
if (!namespace) {
return;
}

// TODO: Method to fetch from a store module?
var get = function (namespace) {
return this.generators[namespace];
}.bind(this);
Expand Down Expand Up @@ -348,7 +334,7 @@ Environment.prototype.create = function create(namespace, options) {

var Generator = this.get(namespace);

var args = options.arguments || options.args || this.arguments;
var args = options.arguments || options.args || _.clone(this.arguments);
args = Array.isArray(args) ? args : args.split(' ');

var opts = options.options || _.clone(this.options);
Expand Down Expand Up @@ -377,8 +363,8 @@ Environment.prototype.create = function create(namespace, options) {
* When the environment was unable to resolve a generator, an error is raised.
*
* @param {String|Array} args
* @param {Object} options
* @param {Function} done
* @param {Object} options
* @param {Function} done
*/

Environment.prototype.run = function run(args, options, done) {
Expand Down Expand Up @@ -415,6 +401,10 @@ Environment.prototype.run = function run(args, options, done) {
options: options
});

if (generator instanceof Error) {
return generator;
}

if (typeof done === 'function') {
generator.on('end', done);
}
Expand All @@ -426,10 +416,9 @@ Environment.prototype.run = function run(args, options, done) {
generator.on('start', this.emit.bind(this, 'generators:start'));
generator.on('start', this.emit.bind(this, name + ':start'));

var self = this;
generator.on('method', function (method) {
self.emit(name + ':' + method);
});
this.emit(name + ':' + method);
}.bind(this));

generator.on('end', this.emit.bind(this, name + ':end'));
generator.on('end', this.emit.bind(this, 'generators:end'));
Expand Down Expand Up @@ -742,3 +731,21 @@ Environment.prototype.remote = function remote(name, done) {
done();
});
};

/**
* Resolve a module path
* @param {String} moduleId - Filepath or module name
* @return {String} - The resolved path leading to the module
*/

Environment.prototype.resolveModulePath = function resolveModulePath(moduleId) {
if (moduleId[0] === '.') {
moduleId = path.resolve(moduleId)
}

if (moduleId[0] === '~') {
moduleId = process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'] + moduleId.slice(1);
}

return require.resolve(moduleId);
};
28 changes: 28 additions & 0 deletions test/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,34 @@ describe('yeoman.generators.Base', function () {
});
});

// Underscore String

// > http://epeli.github.com/underscore.string/
// > https://github.com/epeli/underscore.string#string-functions
//
// Underscore String set of utilities are very handy, especially in the
// context of Generators. We often want to humanize, dasherize or underscore
// a given variable.
//
// Since templates are invoked in the context of the Generator that render
// them, all these String helpers are then available directly from templates.
describe('Underscore String', function () {
it('has the whole Underscore String API available as prototype method', function () {
var dummy = new generators.Base([], {
env: generators(),
resolved: __filename
});
var str = require('underscore.string').exports();

Object.keys(str).forEach(function (prop) {
if (typeof str[prop] !== 'function') {
return;
}
assert.equal(typeof dummy._[prop], 'function');
}, this);
});
});

describe('generator.run(args, cb) regression', function () {
var events = [];
var resolveCalled = 0;
Expand Down
Loading

0 comments on commit 7aa8710

Please sign in to comment.