ember-introjs
Version:
An Ember Component for intro.js
246 lines (201 loc) • 6.39 kB
JavaScript
;
var fs = require('fs');
var path = require('path-posix');
var assign = require('object-assign');
var symlinkOrCopy = require('symlink-or-copy');
var Entry = require('./entry');
var logger = require('heimdalljs-logger')('fs-tree-diff:');
var util = require('./util');
var sortAndExpand = util.sortAndExpand;
var validateSortedUnique = util.validateSortedUnique;
var ARBITRARY_START_OF_TIME = 0;
var DEFAULT_DELEGATE = {
unlink: function(inputPath, outputPath, relativePath) {
try {
fs.unlinkSync(outputPath);
} catch (e) {
if (typeof e === 'object' && e !== null && e.code === 'ENOENT') {
return;
}
throw e;
}
},
rmdir: function(inputPath, outputPath, relativePath) {
fs.rmdirSync(outputPath);
},
mkdir: function(inputPath, outputPath, relativePath) {
fs.mkdirSync(outputPath);
},
change: function(inputPath, outputPath, relativePath) {
// We no-op if the platform can symlink, because we assume the output path
// is already linked via a prior create operation.
if (symlinkOrCopy.canSymlink) {
return;
}
fs.unlinkSync(outputPath);
symlinkOrCopy.sync(inputPath, outputPath);
},
create: function(inputPath, outputPath, relativePath) {
symlinkOrCopy.sync(inputPath, outputPath);
}
};
module.exports = FSTree;
function FSTree(options) {
options = options || {};
var entries = options.entries || [];
if (options.sortAndExpand) {
sortAndExpand(entries);
} else {
validateSortedUnique(entries);
}
this.entries = entries;
}
FSTree.fromPaths = function(paths, options) {
if (typeof options !== 'object') { options = {}; }
var entries = paths.map(function(path) {
return new Entry(path, 0, ARBITRARY_START_OF_TIME);
});
return new FSTree({
entries: entries,
sortAndExpand: options.sortAndExpand,
});
};
FSTree.fromEntries = function(entries, options) {
if (typeof options !== 'object') { options = {}; }
return new FSTree({
entries: entries,
sortAndExpand: options.sortAndExpand,
});
};
Object.defineProperty(FSTree.prototype, 'size', {
get: function() {
return this.entries.length;
}
});
FSTree.prototype.addEntries = function(entries, options) {
if (!Array.isArray(entries)) {
throw new TypeError('entries must be an array');
}
if (options && options.sortAndExpand) {
sortAndExpand(entries);
} else {
validateSortedUnique(entries);
}
var fromIndex = 0;
var toIndex = 0;
while (fromIndex < entries.length) {
while (toIndex < this.entries.length &&
this.entries[toIndex].relativePath < entries[fromIndex].relativePath) {
toIndex++;
}
if (toIndex < this.entries.length &&
this.entries[toIndex].relativePath === entries[fromIndex].relativePath) {
this.entries.splice(toIndex, 1, entries[fromIndex++]);
} else {
this.entries.splice(toIndex++, 0, entries[fromIndex++]);
}
}
};
FSTree.prototype.addPaths = function(paths, options) {
var entries = paths.map(function(path) {
return new Entry(path, 0, ARBITRARY_START_OF_TIME);
});
this.addEntries(entries, options);
}
FSTree.prototype.forEach = function(fn, context) {
this.entries.forEach(fn, context);
};
FSTree.prototype.calculatePatch = function(otherFSTree, isEqual) {
if (arguments.length > 1 && typeof isEqual !== 'function') {
throw new TypeError('calculatePatch\'s second argument must be a function');
}
if (typeof isEqual !== 'function') {
isEqual = FSTree.defaultIsEqual;
}
var ours = this.entries;
var theirs = otherFSTree.entries;
var additions = [];
var i = 0;
var j = 0;
var removals = [];
var command;
while (i < ours.length && j < theirs.length) {
var x = ours[i];
var y = theirs[j];
if (x.relativePath < y.relativePath) {
// ours
i++;
removals.push(removeCommand(x));
// remove additions
} else if (x.relativePath > y.relativePath) {
// theirs
j++;
additions.push(addCommand(y));
} else {
if (!isEqual(x, y)) {
command = updateCommand(y);
if (x.isDirectory()) {
removals.push(command);
} else {
additions.push(command);
}
}
// both are the same
i++; j++;
}
}
// cleanup ours
for (; i < ours.length; i++) {
removals.push(removeCommand(ours[i]));
}
// cleanup theirs
for (; j < theirs.length; j++) {
additions.push(addCommand(theirs[j]));
}
// operations = removals (in reverse) then additions
return removals.reverse().concat(additions);
};
FSTree.prototype.calculateAndApplyPatch = function(otherFSTree, input, output, delegate) {
var patch = this.calculatePatch(otherFSTree);
FSTree.applyPatch(input, output, patch, delegate);
};
FSTree.defaultIsEqual = function defaultIsEqual(entryA, entryB) {
if (entryA.isDirectory() && entryB.isDirectory()) {
// ignore directory changes by default
return true;
}
var equal = entryA.size === entryB.size &&
+entryA.mtime === +entryB.mtime &&
entryA.mode === entryB.mode;
if (!equal) {
logger.info('invalidation reason: \nbefore %o\n entryB %o', entryA, entryB);
}
return equal;
};
FSTree.applyPatch = function(input, output, patch, _delegate) {
var delegate = assign({}, DEFAULT_DELEGATE, _delegate);
for (var i = 0; i < patch.length; i++) {
applyOperation(input, output, patch[i], delegate);
}
};
function applyOperation(input, output, operation, delegate) {
var method = operation[0];
var relativePath = operation[1];
var inputPath = path.join(input, relativePath);
var outputPath = path.join(output, relativePath);
var delegateType = typeof delegate[method];
if (delegateType === 'function') {
delegate[method](inputPath, outputPath, relativePath);
} else {
throw new Error('Unable to apply patch operation: ' + method + '. The value of delegate.' + method + ' is of type ' + delegateType + ', and not a function. Check the `delegate` argument to `FSTree.prototype.applyPatch`.');
}
}
function addCommand(entry) {
return [entry.isDirectory() ? 'mkdir' : 'create', entry.relativePath, entry];
}
function removeCommand(entry) {
return [entry.isDirectory() ? 'rmdir' : 'unlink', entry.relativePath, entry];
}
function updateCommand(entry) {
return ['change', entry.relativePath, entry];
}