generator-modx-package
Version:
A yeoman generator for MODX Revolution packages.
211 lines (177 loc) • 5.21 kB
JavaScript
var logger = process.logging || require('./log');
var fs = require('fs');
var path = require('path');
var events = require('events');
var diff = require('diff');
var prompt = require('../actions/prompt');
var log = logger('conflicter');
var async = require('async');
var isBinaryFile = require('isbinaryfile');
var chalk = require('chalk');
var conflicter = module.exports = Object.create(events.EventEmitter.prototype);
conflicter.conflicts = [];
conflicter.add = function add(conflict) {
if (typeof conflict === 'string') {
conflict = {
file: conflict,
content: fs.readFileSync(conflict, 'utf8')
};
}
if (!conflict.file) {
throw new Error('Missing conflict.file option');
}
if (conflict.content === undefined) {
throw new Error('Missing conflict.content option');
}
this.conflicts.push(conflict);
return this;
};
conflicter.reset = function reset() {
this.conflicts = [];
return this;
};
conflicter.pop = function pop() {
return this.conflicts.pop();
};
conflicter.shift = function shift() {
return this.conflicts.shift();
};
conflicter.resolve = function resolve(cb) {
var resolveConflicts = function (conflict) {
return function (next) {
if (!conflict) {
return next();
}
conflicter.collision(conflict.file, conflict.content, function (status) {
conflicter.emit('resolved:' + conflict.file, {
status: status,
callback: next
});
});
};
};
async.series(this.conflicts.map(resolveConflicts), function (err) {
if (err) {
cb();
return self.emit('error', err);
}
conflicter.reset();
cb();
}.bind(this));
};
conflicter._ask = function (filepath, content, cb) {
// for this particular use case, might use prompt module directly to avoid
// the additional "Are you sure?" prompt
var self = this;
var rfilepath = path.relative(process.cwd(), path.resolve(filepath));
var config = [{
type: 'expand',
message: 'Overwrite ' + rfilepath + '?',
choices: [{
key: 'y',
name: 'overwrite',
value: function (cb) {
log.force(rfilepath);
return cb('force');
}
}, {
key: 'n',
name: 'do not overwrite',
value: function (cb) {
log.skip(rfilepath);
return cb('skip');
}
}, {
key: 'a',
name: 'overwrite this and all others',
value: function (cb) {
log.force(rfilepath);
self.force = true;
return cb('force');
}
}, {
key: 'x',
name: 'abort',
value: function (cb) {
log.writeln('Aborting ...');
return process.exit(0);
}
}, {
key: 'd',
name: 'show the differences between the old and the new',
value: function (cb) {
console.log(conflicter.diff(fs.readFileSync(filepath, 'utf8'), content));
return self._ask(filepath, content, cb);
}
}],
name: 'overwrite'
}];
process.nextTick(function () {
this.emit('prompt', config);
this.emit('conflict', filepath);
}.bind(this));
prompt(config, function (result) {
result.overwrite(function (action) {
cb(action);
});
});
};
conflicter.collision = function collision(filepath, content, cb) {
var self = this;
var rfilepath = path.relative(process.cwd(), path.resolve(filepath));
if (!fs.existsSync(filepath)) {
log.create(rfilepath);
return cb('create');
}
if (!fs.statSync(path.resolve(filepath)).isDirectory()) {
var encoding = null;
if (!isBinaryFile(path.resolve(filepath))) {
encoding = 'utf8';
}
var actual = fs.readFileSync(path.resolve(filepath), encoding);
// In case of binary content, `actual` and `content` are `Buffer` objects,
// we just can't compare those 2 objects with standard `===`,
// so we convert each binary content to an hexadecimal string first, and then compare them with standard `===`
//
// For not binary content, we can directly compare the 2 strings this way
if ((!encoding && (actual.toString('hex') === content.toString('hex'))) ||
(actual === content)) {
log.identical(rfilepath);
return cb('identical');
}
}
if (self.force) {
log.force(rfilepath);
return cb('force');
}
log.conflict(rfilepath);
conflicter._ask(filepath, content, cb);
};
conflicter.colorDiffAdded = chalk.black.bgGreen;
conflicter.colorDiffRemoved = chalk.bgRed;
// below is borrowed code from visionmedia's excellent mocha (and its reporter)
conflicter.diff = function _diff(actual, expected) {
var msg = diff.diffLines(actual, expected).map(function (str) {
if (str.added) {
return conflicter.colorLines('Added', str.value);
}
if (str.removed) {
return conflicter.colorLines('Removed', str.value);
}
return str.value;
}).join('');
// legend
msg = '\n' +
conflicter.colorDiffRemoved('removed') +
' ' +
conflicter.colorDiffAdded('added') +
'\n\n' +
msg +
'\n';
return msg;
};
conflicter.colorLines = function colorLines(name, str) {
return str.split('\n').map(function (str) {
return conflicter['colorDiff' + name](str);
}).join('\n');
};