treediff
Version:
Compare and find/sync differences in file trees
267 lines (247 loc) • 7.99 kB
JavaScript
;
var Path, TreeDiff, _, async, fs, isStream, minimatch;
minimatch = require('minimatch');
isStream = require('isstream');
async = require('async');
Path = require('path');
fs = require('fs');
_ = require('lodash');
TreeDiff = (function() {
function TreeDiff() {
this._configurations = {};
this._mappers = {};
this._filters = [];
this._transferers = {};
this.registerMapper('local', require('./mapper'));
this.registerTransferer('local', require('./transferer'));
this.registerFilter('**/.DS_Store', 'ignore');
}
TreeDiff.prototype.registerConfiguration = function(name, configuration) {
return this._configurations[name] = configuration;
};
TreeDiff.prototype.registerMapper = function(name, module) {
return this._mappers[name] = module;
};
TreeDiff.prototype.registerTransferer = function(name, module) {
return this._transferers[name] = module;
};
TreeDiff.prototype.registerFilter = function(filter, mode) {
if (mode == null) {
mode = 'ignore';
}
return this._filters.push({
value: filter,
mode: mode
});
};
TreeDiff.prototype.config = function(name) {
return this._configurations[name];
};
TreeDiff.prototype._applyFilter = function(filter, map) {
var filterKey, i, key, len, ref, results1;
filterKey = function(key) {
var entry, sep;
entry = map.product[key];
sep = '';
if (entry.isDirectory && key.charAt(key.length - 1) !== Path.sep) {
sep = Path.sep;
}
if (minimatch(key + sep, filter.value)) {
map.product[key].filter = filter.mode;
return map.length--;
}
};
ref = Object.keys(map.product);
results1 = [];
for (i = 0, len = ref.length; i < len; i++) {
key = ref[i];
results1.push(filterKey(key));
}
return results1;
};
TreeDiff.prototype.map = function(options, callback) {
var Mapper;
Mapper = this._mappers[options.type];
if (Mapper == null) {
return callback(new Error("Unknown map type: '" + options.type + "'"));
}
return new Mapper(this, options, (function(_this) {
return function(err, map) {
var filter, i, len, ref;
if (err != null) {
return callback(err);
}
if (map == null) {
map = {};
}
if (map.product == null) {
map.product = {};
}
ref = _this._filters;
for (i = 0, len = ref.length; i < len; i++) {
filter = ref[i];
_this._applyFilter(filter, map);
}
map = _.merge(map, options);
return callback(null, map);
};
})(this));
};
TreeDiff.prototype.sync = function(mapA, mapB, callback) {
var getInputType;
getInputType = function(input) {
if (isStream(input)) {
return 'stream';
} else {
return typeof input;
}
};
return this.compare(mapA, mapB, (function(_this) {
return function(err, difference, mapA, mapB) {
var deleteFiles, transferFiles, transfererA, transfererB;
transfererA = new _this._transferers[mapA.type](_this, mapA);
transfererB = new _this._transferers[mapB.type](_this, mapB);
transferFiles = function(paths, callback) {
return async.eachLimit(paths, 10, function(path, next) {
var readOptions;
readOptions = {
path: path,
allowStreams: transfererA.allowStreams,
entry: mapB.product[path],
other: mapA,
difference: difference
};
return transfererB.read(readOptions, function(err, readable) {
var writeOptions;
if (err != null) {
return next(err);
}
writeOptions = {
path: path,
input: readable,
inputType: getInputType(readable),
entry: mapB.product[path],
other: mapB,
difference: difference
};
return transfererA.write(writeOptions, next);
});
}, callback);
};
deleteFiles = function(paths, callback) {
return async.each(paths, function(path, next) {
var deleteOptions;
deleteOptions = {
path: path,
entry: mapA.product[path]
};
return transfererA["delete"](deleteOptions, next);
}, callback);
};
return async.auto({
pre: function(onPrepareComplete) {
return async.each([transfererA, transfererB], function(transferer, next) {
if (transferer.prepare != null) {
return transferer.prepare(next);
} else {
return next();
}
}, onPrepareComplete);
},
add: [
'pre', function(onAddComplete) {
return transferFiles(difference.added, onAddComplete);
}
],
mod: [
'pre', function(onModComplete) {
return transferFiles(difference.modified, onModComplete);
}
],
del: [
'pre', function(onDelComplete) {
var toDelete;
toDelete = difference.deleted.concat(difference.ignored);
return deleteFiles(toDelete, onDelComplete);
}
],
fin: [
'pre', 'add', 'mod', 'del', function(onFinishComplete) {
return async.each([transfererA, transfererB], function(transferer, next) {
if (transferer.finish != null) {
return transferer.finish(next);
} else {
return next();
}
}, onFinishComplete);
}
]
}, function(err, results) {
return callback(err, difference, mapA, mapB);
});
};
})(this));
};
TreeDiff.prototype.compare = function(mapA, mapB, callback) {
var resolveMap;
resolveMap = (function(_this) {
return function(input, next) {
if (input.map != null) {
return next(null, input.map);
} else {
return _this.map(input, next);
}
};
})(this);
return async.map([mapA, mapB], resolveMap, function(err, results) {
var difference, i, j, key, keyA, keyB, len, len1, modifiedA, modifiedB, productA, productB, ref, ref1;
if (err != null) {
return callback(err);
}
productA = results[0].product;
productB = results[1].product;
difference = {
added: [],
modified: [],
deleted: [],
ignored: []
};
ref = Object.keys(productA);
for (i = 0, len = ref.length; i < len; i++) {
key = ref[i];
keyA = productA[key];
keyB = productB[key];
if (keyB != null) {
if (keyA.filter != null) {
continue;
}
modifiedA = keyA.modified.getTime();
modifiedB = keyB.modified.getTime();
if (keyB.size !== keyA.size || modifiedB > modifiedA) {
difference.modified.push(key);
}
} else if (keyA.filter === 'ignore') {
difference.ignored.push(key);
} else {
difference.deleted.push(key);
}
}
ref1 = Object.keys(productB);
for (j = 0, len1 = ref1.length; j < len1; j++) {
key = ref1[j];
if (productA[key] == null) {
if (productB[key].filter != null) {
continue;
}
difference.added.push(key);
}
}
difference.total = [difference.modified, difference.added, difference.deleted].reduce(function(sum, arr) {
return sum += arr.length;
}, 0);
return callback(null, difference, results[0], results[1]);
});
};
return TreeDiff;
})();
module.exports = TreeDiff;