blob: 8c64b998c2c82e947e418652d25bab176501850b [file] [log] [blame]
/*
Module difflib -- helpers for computing deltas between objects.
Function get_close_matches(word, possibilities, n=3, cutoff=0.6):
Use SequenceMatcher to return list of the best "good enough" matches.
Function context_diff(a, b):
For two lists of strings, return a delta in context diff format.
Function ndiff(a, b):
Return a delta: the difference between `a` and `b` (lists of strings).
Function restore(delta, which):
Return one of the two sequences that generated an ndiff delta.
Function unified_diff(a, b):
For two lists of strings, return a delta in unified diff format.
Class SequenceMatcher:
A flexible class for comparing pairs of sequences of any type.
Class Differ:
For producing human-readable deltas from sequences of lines of text.
Class HtmlDiff:
For producing HTML side by side comparison with change highlights.
*/
(function() {
var Differ, Heap, SequenceMatcher, arrayCmp, assert, calculateRatio, exports, floor, getCloseMatches, log, max, min, _countLeading;
floor = Math.floor, max = Math.max, min = Math.min;
Heap = require('heap').Heap;
assert = require('assert');
log = console.log.bind(console);
calculateRatio = function(matches, length) {
if (length) {
return 2.0 * matches / length;
} else {
return 1.0;
}
};
arrayCmp = function(a, b) {
var i, _ref;
for (i = 0, _ref = max(a.length, b.length); 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) {
if (a[i] < b[i]) return -1;
if (a[i] > b[i]) return 1;
}
return 0;
};
SequenceMatcher = (function() {
function SequenceMatcher(isjunk, a, b, autojunk) {
this.isjunk = isjunk;
if (a == null) a = '';
if (b == null) b = '';
this.autojunk = autojunk != null ? autojunk : true;
this.a = this.b = null;
this.setSeqs(a, b);
}
/*
Set the two sequences to be compared.
*/
SequenceMatcher.prototype.setSeqs = function(a, b) {
this.setSeq1(a);
return this.setSeq2(b);
};
/*
Set the first sequence to be compared.
The second sequence to be compared is not changed.
*/
SequenceMatcher.prototype.setSeq1 = function(a) {
if (a === this.a) return;
this.a = a;
return this.matchingBlocks = this.opcodes = null;
};
/*
Set the second sequence to be compared.
The first sequence to be compared is not changed.
*/
SequenceMatcher.prototype.setSeq2 = function(b) {
if (b === this.b) return;
this.b = b;
this.matchingBlocks = this.opcodes = null;
this.fullbcount = null;
return this._chainB();
};
SequenceMatcher.prototype._chainB = function() {
var b, b2j, elt, i, idxs, indices, isjunk, junk, n, ntest, popular, _i, _len, _len2, _ref;
b = this.b;
this.b2j = b2j = {};
for (i = 0, _len = b.length; i < _len; i++) {
elt = b[i];
indices = elt in b2j ? b2j[elt] : b2j[elt] = [];
indices.push(i);
}
junk = {};
isjunk = this.isjunk;
if (isjunk) {
_ref = Object.keys(b2j);
for (_i = 0, _len2 = _ref.length; _i < _len2; _i++) {
elt = _ref[_i];
if (isjunk(elt)) {
junk[elt] = true;
delete b2j[elt];
}
}
}
popular = {};
n = b.length;
if (this.autojunk && n >= 200) {
ntest = floor(n / 100) + 1;
for (elt in b2j) {
idxs = b2j[elt];
if (idxs.length > ntest) {
popular[elt] = true;
delete b2j[elt];
}
}
}
this.isbjunk = function(b) {
return b in junk;
};
return this.isbpopular = function(b) {
return b in popular;
};
};
/*
Find longest matching block in a[alo:ahi] and b[blo:bhi].
*/
SequenceMatcher.prototype.findLongestMatch = function(alo, ahi, blo, bhi) {
var a, b, b2j, besti, bestj, bestsize, i, isbjunk, j, j2len, k, newj2len, _i, _len, _ref, _ref2, _ref3, _ref4, _ref5, _ref6;
_ref = [this.a, this.b, this.b2j, this.isbjunk], a = _ref[0], b = _ref[1], b2j = _ref[2], isbjunk = _ref[3];
_ref2 = [alo, blo, 0], besti = _ref2[0], bestj = _ref2[1], bestsize = _ref2[2];
j2len = {};
for (i = alo; alo <= ahi ? i < ahi : i > ahi; alo <= ahi ? i++ : i--) {
newj2len = {};
_ref3 = (a[i] in b2j ? b2j[a[i]] : []);
for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
j = _ref3[_i];
if (j < blo) continue;
if (j >= bhi) break;
k = newj2len[j] = (j2len[j - 1] || 0) + 1;
if (k > bestsize) {
_ref4 = [i - k + 1, j - k + 1, k], besti = _ref4[0], bestj = _ref4[1], bestsize = _ref4[2];
}
}
j2len = newj2len;
}
while (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[besti - 1] === b[bestj - 1]) {
_ref5 = [besti - 1, bestj - 1, bestsize + 1], besti = _ref5[0], bestj = _ref5[1], bestsize = _ref5[2];
}
while (besti + bestsize < ahi && bestj + bestsize < bhi && !isbjunk(b[bestj + bestsize]) && a[besti + bestsize] === b[bestj + bestsize]) {
bestsize++;
}
while (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[besti - 1] === b[bestj - 1]) {
_ref6 = [besti - 1, bestj - 1, bestsize + 1], besti = _ref6[0], bestj = _ref6[1], bestsize = _ref6[2];
}
while (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b[bestj + bestsize]) && a[besti + bestsize] === b[bestj + bestsize]) {
bestsize++;
}
return [besti, bestj, bestsize];
};
/*
Return list of matches describing matching subsequences.
*/
SequenceMatcher.prototype.getMatchingBlocks = function() {
var ahi, alo, bhi, blo, i, i1, i2, j, j1, j2, k, k1, k2, la, lb, matchingBlocks, nonAdjacent, queue, x, _i, _len, _ref, _ref2, _ref3, _ref4, _ref5;
if (this.matchingBlocks) return this.matchingBlocks;
_ref = [this.a.length, this.b.length], la = _ref[0], lb = _ref[1];
queue = [[0, la, 0, lb]];
matchingBlocks = [];
while (queue.length) {
_ref2 = queue.pop(), alo = _ref2[0], ahi = _ref2[1], blo = _ref2[2], bhi = _ref2[3];
_ref3 = x = this.findLongestMatch(alo, ahi, blo, bhi), i = _ref3[0], j = _ref3[1], k = _ref3[2];
if (k) {
matchingBlocks.push(x);
if (alo < i && blo < j) queue.push([alo, i, blo, j]);
if (i + k < ahi && j + k < bhi) queue.push([i + k, ahi, j + k, bhi]);
}
}
matchingBlocks.sort(arrayCmp);
i1 = j1 = k1 = 0;
nonAdjacent = [];
for (_i = 0, _len = matchingBlocks.length; _i < _len; _i++) {
_ref4 = matchingBlocks[_i], i2 = _ref4[0], j2 = _ref4[1], k2 = _ref4[2];
if (i1 + k1 === i2 && j1 + k1 === j2) {
k1 += k2;
} else {
if (k1) nonAdjacent.push([i1, j1, k1]);
_ref5 = [i2, j2, k2], i1 = _ref5[0], j1 = _ref5[1], k1 = _ref5[2];
}
}
if (k1) nonAdjacent.push([i1, j1, k1]);
nonAdjacent.push([la, lb, 0]);
return this.matchingBlocks = nonAdjacent;
};
/*
Return list of 5-tuples describing how to turn a into b
*/
SequenceMatcher.prototype.getOpcodes = function() {
var ai, answer, bj, i, j, size, tag, _i, _len, _ref, _ref2, _ref3;
if (this.opcodes) return this.opcodes;
i = j = 0;
this.opcodes = answer = [];
_ref = this.getMatchingBlocks();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
_ref2 = _ref[_i], ai = _ref2[0], bj = _ref2[1], size = _ref2[2];
tag = '';
if (i < ai && j < bj) {
tag = 'replace';
} else if (i < ai) {
tag = 'delete';
} else if (j < bj) {
tag = 'insert';
}
if (tag) answer.push([tag, i, ai, j, bj]);
_ref3 = [ai + size, bj + size], i = _ref3[0], j = _ref3[1];
if (size) answer.push(['equal', ai, i, bj, j]);
}
return answer;
};
/*
Isolate change clusters by eliminating ranges with no changes.
XXX: not match
*/
SequenceMatcher.prototype.getGroupedOpcodes = function(n) {
var codes, group, groups, i1, i2, j1, j2, nn, tag, _i, _len, _ref, _ref2, _ref3, _ref4;
if (n == null) n = 3;
codes = this.getOpcodes();
if (!codes.length) codes = [['equal', 0, 1, 0, 1]];
if (codes[0][0] === 'equal') {
_ref = codes[0], tag = _ref[0], i1 = _ref[1], i2 = _ref[2], j1 = _ref[3], j2 = _ref[4];
codes[0] = [tag, max(i1, i2 - n), i2, max(j1, j2 - n), j2];
}
if (codes[codes.length - 1][0] === 'equal') {
_ref2 = codes[codes.length - 1], tag = _ref2[0], i1 = _ref2[1], i2 = _ref2[2], j1 = _ref2[3], j2 = _ref2[4];
codes[codes.length - 1] = [tag, i1, min(i2, i1 + n), j1, min(j2, j1 + n)];
}
nn = n + n;
groups = [];
group = [];
for (_i = 0, _len = codes.length; _i < _len; _i++) {
_ref3 = codes[_i], tag = _ref3[0], i1 = _ref3[1], i2 = _ref3[2], j1 = _ref3[3], j2 = _ref3[4];
if (tag === 'equal' && i2 - i1 > nn) {
group.push([tag, i1, min(i2, i1 + n), j1, min(j2, j1 + n)]);
groups.push(group);
group = [];
_ref4 = [max(i1, i2 - n), max(j1, j2 - n)], i1 = _ref4[0], j1 = _ref4[1];
}
group.push([tag, i1, i2, j1, j2]);
}
if (group.length && !(group.length === 1 && group[0][0] === 'equal')) {
groups.push(group);
}
return groups;
};
SequenceMatcher.prototype.ratio = function() {
var matches;
matches = this.getMatchingBlocks().reduce((function(sum, match) {
return sum += match[2];
}), 0);
return calculateRatio(matches, this.a.length + this.b.length);
};
SequenceMatcher.prototype.quickRatio = function() {
var avail, elt, fullbcount, matches, numb, _i, _j, _len, _len2, _ref, _ref2;
if (!this.fullbcount) {
this.fullbcount = fullbcount = {};
_ref = this.b;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
elt = _ref[_i];
fullbcount[elt] = (fullbcount[elt] || 0) + 1;
}
}
fullbcount = this.fullbcount;
avail = {};
matches = 0;
_ref2 = this.a;
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
elt = _ref2[_j];
if (elt in avail) {
numb = avail[elt];
} else {
numb = fullbcount[elt] || 0;
}
avail[elt] = numb - 1;
if (numb > 0) matches++;
}
return calculateRatio(matches, this.a.length + this.b.length);
};
SequenceMatcher.prototype.realQuickRatio = function() {
var la, lb, _ref;
_ref = [this.a.length, this.b.length], la = _ref[0], lb = _ref[1];
return calculateRatio(min(la, lb), la + lb);
};
return SequenceMatcher;
})();
getCloseMatches = function(word, possibilities, n, cutoff) {
var result, s, score, x, _i, _j, _len, _len2, _ref, _results;
if (n == null) n = 3;
if (cutoff == null) cutoff = 0.6;
if (!(n > 0)) throw new Error("n must be > 0: (" + n + ")");
if (!((0.0 <= cutoff && cutoff <= 1.0))) {
throw new Error("cutoff must be in [0.0, 1.0]: (" + cutoff + ")");
}
result = [];
s = new SequenceMatcher();
s.setSeq2(word);
for (_i = 0, _len = possibilities.length; _i < _len; _i++) {
x = possibilities[_i];
s.setSeq1(x);
if (s.realQuickRatio() >= cutoff && s.quickRatio() >= cutoff && s.ratio() >= cutoff) {
result.push([s.ratio(), x]);
}
}
result = Heap.nlargest(n, result);
_results = [];
for (_j = 0, _len2 = result.length; _j < _len2; _j++) {
_ref = result[_j], score = _ref[0], x = _ref[1];
_results.push(x);
}
return _results;
};
/*
Return number of `ch` characters at the start of `line`.
*/
_countLeading = function(line, ch) {
var i, n, _ref;
_ref = [0, line.length], i = _ref[0], n = _ref[1];
while (i < n && line[i] === ch) {
i++;
}
return i;
};
Differ = (function() {
function Differ(linejunk, charjunk) {
this.linejunk = linejunk;
this.charjunk = charjunk;
}
Differ.prototype.compare = function(a, b) {
var ahi, alo, bhi, blo, cruncher, g, line, lines, tag, _i, _j, _len, _len2, _ref, _ref2;
cruncher = new SequenceMatcher(this.linejunk, a, b);
lines = [];
_ref = cruncher.getOpcodes();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
_ref2 = _ref[_i], tag = _ref2[0], alo = _ref2[1], ahi = _ref2[2], blo = _ref2[3], bhi = _ref2[4];
switch (tag) {
case 'replace':
g = this._fancyReplace(a, alo, ahi, b, blo, bhi);
break;
case 'delete':
g = this._dump('-', a, alo, ahi);
break;
case 'insert':
g = this._dump('+', b, blo, bhi);
break;
case 'equal':
g = this._dump(' ', a, alo, ahi);
break;
default:
throw new Error("unknow tag (" + tag + ")");
}
for (_j = 0, _len2 = g.length; _j < _len2; _j++) {
line = g[_j];
lines.push(line);
}
}
return lines;
};
Differ.prototype._dump = function(tag, x, lo, hi) {
var i, _results;
_results = [];
for (i = lo; lo <= hi ? i < hi : i > hi; lo <= hi ? i++ : i--) {
_results.push("" + tag + " " + x[i]);
}
return _results;
};
Differ.prototype._plainReplace = function(a, alo, ahi, b, blo, bhi) {
var first, g, line, lines, second, _i, _j, _len, _len2, _ref;
assert(alo < ahi && blo < bhi);
if (bhi - blo < ahi - alo) {
first = this._dump('+', b, blo, bhi);
second = this._dump('-', a, alo, ahi);
} else {
first = this._dump('-', a, alo, ahi);
second = this._dump('+', b, blo, bhi);
}
lines = [];
_ref = [first, second];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
g = _ref[_i];
for (_j = 0, _len2 = g.length; _j < _len2; _j++) {
line = g[_j];
lines.push(line);
}
}
return lines;
};
Differ.prototype._fancyReplace = function(a, alo, ahi, b, blo, bhi) {
var aelt, ai, ai1, ai2, atags, belt, bestRatio, besti, bestj, bj, bj1, bj2, btags, cruncher, cutoff, eqi, eqj, i, j, la, lb, line, lines, tag, _i, _j, _k, _l, _len, _len2, _len3, _len4, _len5, _m, _ref, _ref10, _ref11, _ref12, _ref13, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9;
_ref = [0.74, 0.75], bestRatio = _ref[0], cutoff = _ref[1];
cruncher = new SequenceMatcher(this.charjunk);
_ref2 = [null, null], eqi = _ref2[0], eqj = _ref2[1];
lines = [];
for (j = blo; blo <= bhi ? j < bhi : j > bhi; blo <= bhi ? j++ : j--) {
bj = b[j];
cruncher.setSeq2(bj);
for (i = alo; alo <= ahi ? i < ahi : i > ahi; alo <= ahi ? i++ : i--) {
ai = a[i];
if (ai === bj) {
if (eqi === null) _ref3 = [i, j], eqi = _ref3[0], eqj = _ref3[1];
continue;
}
cruncher.setSeq1(ai);
if (cruncher.realQuickRatio() > bestRatio && cruncher.quickRatio() > bestRatio && cruncher.ratio() > bestRatio) {
_ref4 = [cruncher.ratio(), i, j], bestRatio = _ref4[0], besti = _ref4[1], bestj = _ref4[2];
}
}
}
if (bestRatio < cutoff) {
if (eqi === null) {
_ref5 = this._plainReplace(a, alo, ahi, b, blo, bhi);
for (_i = 0, _len = _ref5.length; _i < _len; _i++) {
line = _ref5[_i];
lines.push(line);
}
return lines;
}
_ref6 = [eqi, eqj, 1.0], besti = _ref6[0], bestj = _ref6[1], bestRatio = _ref6[2];
} else {
eqi = null;
}
_ref7 = this._fancyHelper(a, alo, besti, b, blo, bestj);
for (_j = 0, _len2 = _ref7.length; _j < _len2; _j++) {
line = _ref7[_j];
lines.push(line);
}
_ref8 = [a[besti], b[bestj]], aelt = _ref8[0], belt = _ref8[1];
if (eqi === null) {
atags = btags = '';
cruncher.setSeqs(aelt, belt);
_ref9 = cruncher.getOpcodes();
for (_k = 0, _len3 = _ref9.length; _k < _len3; _k++) {
_ref10 = _ref9[_k], tag = _ref10[0], ai1 = _ref10[1], ai2 = _ref10[2], bj1 = _ref10[3], bj2 = _ref10[4];
_ref11 = [ai2 - ai1, bj2 - bj1], la = _ref11[0], lb = _ref11[1];
switch (tag) {
case 'replace':
atags += Array(la + 1).join('^');
btags += Array(lb + 1).join('^');
break;
case 'delete':
atags += Array(la + 1).join('-');
break;
case 'insert':
btags += Array(lb + 1).join('+');
break;
case 'equal':
atags += Array(la + 1).join(' ');
btags += Array(lb + 1).join(' ');
break;
default:
throw new Error("unknow tag (" + tag + ")");
}
}
_ref12 = this._qformat(aelt, belt, atags, btags);
for (_l = 0, _len4 = _ref12.length; _l < _len4; _l++) {
line = _ref12[_l];
lines.push(line);
}
} else {
lines.push(' ' + aelt);
}
_ref13 = this._fancyHelper(a, besti + 1, ahi, b, bestj + 1, bhi);
for (_m = 0, _len5 = _ref13.length; _m < _len5; _m++) {
line = _ref13[_m];
lines.push(line);
}
return lines;
};
Differ.prototype._fancyHelper = function(a, alo, ahi, b, blo, bhi) {
var g;
g = [];
if (alo < ahi) {
if (blo < bhi) {
g = this._fancyReplace(a, alo, ahi, b, blo, bhi);
} else {
g = this._dump('-', a, alo, ahi);
}
} else if (blo < bhi) {
g = this._dump('+', b, blo, bhi);
}
return g;
};
Differ.prototype._qformat = function(aline, bline, atags, btags) {
var common, lines;
lines = [];
common = min(_countLeading(aline, '\t'), _countLeading(bline, '\t'));
common = min(common, _countLeading(atags.slice(0, common), ' '));
common = min(common, _countLeading(btags.slice(0, common), ' '));
atags = atags.slice(common).trimRight();
btags = btags.slice(common).trimRight();
lines.push('- ' + aline);
if (atags.length) {
lines.push("? " + (Array(common + 1).join('\t')) + atags + "\n");
}
lines.push('+ ' + bline);
if (btags.length) {
lines.push("? " + (Array(common + 1).join('\t')) + btags + "\n");
}
return lines;
};
return Differ;
})();
exports = (typeof module !== "undefined" && module !== null ? module.exports : void 0) || (window.difflib = {});
exports.SequenceMatcher = SequenceMatcher;
exports.getCloseMatches = getCloseMatches;
exports._countLeading = _countLeading;
exports.Differ = Differ;
}).call(this);