Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scoped helpers environments from file-utils #378

Merged
merged 1 commit into from Nov 13, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/actions/actions.js
Expand Up @@ -119,7 +119,7 @@ function prepCopy(source, destination, process) {
actions.copy = function copy(source, destination, process) {

var file = prepCopy.call(this, source, destination, process);

try {
file.body = this.engine(file.body, this);
} catch (err) {
Expand Down
87 changes: 82 additions & 5 deletions lib/base.js
Expand Up @@ -6,12 +6,16 @@ var _ = require('lodash');
var async = require('async');
var findup = require('findup-sync');
var chalk = require('chalk');
var file = require('file-utils');

var engines = require('./util/engines');
var conflicter = require('./util/conflicter');
var Storage = require('./util/storage');
var actions = require('./actions/actions');

var noop = function () {};
var fileLogger = { write: noop, warn: noop };

// TOOD(mklabs): flesh out api, remove config (merge with options, or just remove the
// grunt config handling)

Expand All @@ -26,6 +30,7 @@ var actions = require('./actions/actions');
*/

var Base = module.exports = function Base(args, options) {
var self = this;
events.EventEmitter.call(this);

if (!Array.isArray(args)) {
Expand Down Expand Up @@ -89,6 +94,22 @@ var Base = module.exports = function Base(args, options) {
desc: 'Print generator\'s options and usage'
});

// Set the file-utils environments
// Set logger as a noop as logging is handled by the yeoman conflicter
this.src = file.createEnv({
base: this.sourceRoot(),
dest: this.destinationRoot(),
logger: fileLogger
});
this.dest = file.createEnv({
base: this.destinationRoot(),
dest: this.sourceRoot(),
logger: fileLogger
});

this.dest.registerValidationFilter('collision', this.getCollisionFilter());
this.src.registerValidationFilter('collision', this.getCollisionFilter());

// ensure source/destination path, can be configured from subclasses
this.sourceRoot(path.join(path.dirname(this.resolved), 'templates'));
};
Expand Down Expand Up @@ -538,16 +559,72 @@ Base.prototype._setStorage = function () {
};

/**
* Façace `actions.destinationRoot` on Base generator so it update the storage
* path when the path change.
*
* @param {String} rootPath
* Change the generator destination root directory.
* This method façade the lib/actions/actions.js method so it can update the storage and
* the file-utils environments.
* @param {String} rootPath new destination root path
* @return {String} destination root path
*/

Base.prototype.destinationRoot = function (rootPath) {
var root = actions.destinationRoot.call(this, rootPath);
if (rootPath) {

if (_.isString(rootPath)) {
this._setStorage();

// Update file helpers path bases
this.dest.setBase(root);
this.src.setDestBase(root);
}

return root;
};

/**
* Change the generator source root directory.
* This method façade the lib/actions/actions.js method so it can update the
* file-utils environments.
* @param {String} rootPath new source root path
* @return {String} source root path
*/

Base.prototype.sourceRoot = function (rootPath) {
var root = actions.sourceRoot.call(this, rootPath);

if (_.isString(rootPath)) {
// Update file helpers path bases
this.src.setBase(root);
this.dest.setDestBase(root);
}

return root;
};


/**
* Return a file Env validation filter checking for collision
*/

Base.prototype.getCollisionFilter = function () {
var self = this;
return function checkForCollisionFilter(output) {
var done = this.async();

self.checkForCollision(output.path, output.contents, function (err, config) {
if (err) {
done(false);
config.callback(err);
return this.emit('error', err);
}

if (!(/force|create/.test(config.status))) {
done('Skip modifications to ' + output.path);
}

done(true);
return config.callback();
});
}

};

18 changes: 17 additions & 1 deletion lib/test/helpers.js
Expand Up @@ -4,9 +4,9 @@ var rimraf = require('rimraf');
var mkdirp = require('mkdirp');
var util = require('util');
var assert = require('assert');
var _ = require('lodash');
var generators = require('../..');


// Mocha helpers
var helpers = module.exports;

Expand Down Expand Up @@ -147,6 +147,22 @@ helpers.assertTextEqual = function (value, expected) {
assert.equal(eol(value), eol(expected));
};

/**
* Assert an Object implement and interface
* @param {Object} obj subject implementing the façade
* @param {Object|Array} interface a façace, hash or array of keys to be implemented
*/

helpers.assertImplement = function(obj, interface) {
var methods = _.isArray(interface) ? interface : Object.keys(interface);
var pass = methods.reduce(function(rest, method) {
if (obj[method] != null) return true;
return rest;
}, false);

assert.ok(pass);
};

// Clean-up the test directory and ce into it.
// Call given callback when you're there.
//
Expand Down
3 changes: 3 additions & 0 deletions main.js
Expand Up @@ -32,6 +32,9 @@ var generators = module.exports = function createEnv(args, opts) {
// Reference general dependencies on the top level object
generators.inquirer = require('inquirer');

// Set global file utility
generators.file = require('file-utils');

// hoist up top level class the generator extend
generators.Base = require('./lib/base');
generators.NamedBase = require('./lib/named-base');
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -44,7 +44,8 @@
"chalk": "~0.2.0",
"text-table": "~0.2.0",
"download": "~0.1.6",
"request": "~2.27.0"
"request": "~2.27.0",
"file-utils": "~0.1.1"
},
"devDependencies": {
"mocha": "~1.13.0",
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/file-conflict.txt
@@ -0,0 +1 @@
initial content
89 changes: 87 additions & 2 deletions test/generators.js
@@ -1,7 +1,11 @@
/*global describe before it */
/*global describe, before, beforeEach, it */
var path = require('path');
var fs = require('fs');
var events = require('events');
var assert = require('assert');
var file = require('file-utils');
var helpers = require('../lib/test/helpers');
var sinon = require('sinon');

var generators = require('..');
var Environment = require('../lib/env');
Expand Down Expand Up @@ -36,7 +40,7 @@ describe('Generators', function () {
});

describe('yeoman.generators.Base', function () {
before(function () {
beforeEach(function () {
this.env = generators();
this.generator = new generators.Base({
env: this.env,
Expand All @@ -51,6 +55,87 @@ describe('Generators', function () {
this.generator.on('yay-o-man', done);
this.generator.emit('yay-o-man');
});

describe('.src', function () {
it('implement the file-utils interface', function () {
helpers.assertImplement(this.generator.src, file.constructor.prototype);
});

it('generator.sourcePath() update its source base', function () {
this.generator.sourceRoot('foo/src');
assert.ok(this.generator.src.fromBase('bar'), 'foo/src/bar');
});

it('generator.destinationPath() update its destination base', function () {
this.generator.destinationRoot('foo/src');
assert.ok(this.generator.src.fromDestBase('bar'), 'foo/src/bar');
});
});

describe('.dest', function () {
it('implement the file-utils interface', function () {
helpers.assertImplement(this.generator.dest, file.constructor.prototype);
});

it('generator.sourcePath() update its destination base', function () {
this.generator.sourceRoot('foo/src');
assert.ok(this.generator.src.fromDestBase('bar'), 'foo/src/bar');
});

it('generator.destinationPath() update its source base', function () {
this.generator.destinationRoot('foo/src');
assert.ok(this.generator.src.fromBase('bar'), 'foo/src/bar');
});

describe('conflict handler', function () {
var destRoot = path.join(__dirname, 'fixtures');
var target = path.join(destRoot, 'file-conflict.txt');
var initialFileContent = fs.readFileSync(target).toString();

beforeEach(function () {
this.generator.destinationRoot(destRoot);
assert.ok(file.exists(target));
helpers.assertTextEqual(initialFileContent, 'initial content\n');
});

it('aborting', function () {
// make sure the file exist
var fileContent = this.generator.dest.read('file-conflict.txt');
var checkForCollision = sinon.stub(this.generator, 'checkForCollision');

this.generator.dest.write('file-conflict.txt', 'some conficting content');

var cb = checkForCollision.args[0][2];
cb(null, {
status: 'abort',
callback: function () {}
});

assert.ok(checkForCollision.calledOnce);
assert.ok(fileContent, this.generator.dest.read('file-conflict.txt'));
});

it('allowing', function () {
// make sure the file exist
var fileContent = this.generator.dest.read('file-conflict.txt');
var checkForCollision = sinon.stub(this.generator, 'checkForCollision');

this.generator.dest.write('file-conflict.txt', 'some conficting content');

var cb = checkForCollision.args[0][2];
cb(null, {
status: 'create',
callback: function () {}
});

assert.ok(checkForCollision.calledOnce);
assert.ok('some conflicting content', this.generator.dest.read('file-conflict.txt'));

// reset content
fs.writeFileSync(target, initialFileContent);
});
});
});
});

describe('yeoman.generators.NamedBase', function () {
Expand Down