UNPKG

treediff

Version:

Compare and find/sync differences in file trees

267 lines (247 loc) 7.99 kB
'use strict'; 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;