Skip to content

Instantly share code, notes, and snippets.

@tim-smart
Forked from ucnv/README.md
Created October 22, 2010 06:27
Show Gist options
  • Save tim-smart/640049 to your computer and use it in GitHub Desktop.
Save tim-smart/640049 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Diff for gist.github
// @description adds a diff function to http://gist.github.com/
// @namespace http://userscripts.org/users/40991
// @include http://gist.github.com/*
// @include https://gist.github.com/*
// ==/UserScript==
function D () {
function Deferred () { return (this instanceof Deferred) ? this.init() : new Deferred() }
Deferred.ok = function (x) { return x };
Deferred.ng = function (x) { throw x };
Deferred.prototype = {
init : function () {
this._next = null;
this.callback = {
ok: Deferred.ok,
ng: Deferred.ng
};
return this;
},
next : function (fun) { return this._post("ok", fun) },
error : function (fun) { return this._post("ng", fun) },
call : function (val) { return this._fire("ok", val) },
fail : function (err) { return this._fire("ng", err) },
cancel : function () {
(this.canceller || function () {})();
return this.init();
},
_post : function (okng, fun) {
this._next = new Deferred();
this._next.callback[okng] = fun;
return this._next;
},
_fire : function (okng, value) {
var next = "ok";
try {
value = this.callback[okng].call(this, value);
} catch (e) {
next = "ng";
value = e;
if (Deferred.onerror) Deferred.onerror(e);
}
if (value instanceof Deferred) {
value._next = this._next;
} else {
if (this._next) this._next._fire(next, value);
}
return this;
}
};
Deferred.next_default = function (fun) {
var d = new Deferred();
var id = setTimeout(function () { d.call() }, 0);
d.canceller = function () { clearTimeout(id) };
if (fun) d.callback.ok = fun;
return d;
};
Deferred.next_faster_way_readystatechange = ((typeof window === 'object') && (location.protocol == "http:") && !window.opera && /\bMSIE\b/.test(navigator.userAgent)) && function (fun) {
var d = new Deferred();
var t = new Date().getTime();
if (t - arguments.callee._prev_timeout_called < 150) {
var cancel = false;
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "javascript:";
script.onreadystatechange = function () {
if (!cancel) {
d.canceller();
d.call();
}
};
d.canceller = function () {
if (!cancel) {
cancel = true;
script.onreadystatechange = null;
document.body.removeChild(script);
}
};
document.body.appendChild(script);
} else {
arguments.callee._prev_timeout_called = t;
var id = setTimeout(function () { d.call() }, 0);
d.canceller = function () { clearTimeout(id) };
}
if (fun) d.callback.ok = fun;
return d;
};
Deferred.next_faster_way_Image = ((typeof window === 'object') && (typeof(Image) != "undefined") && document.addEventListener) && function (fun) {
var d = new Deferred();
var img = new Image();
var handler = function () {
d.canceller();
d.call();
};
img.addEventListener("load", handler, false);
img.addEventListener("error", handler, false);
d.canceller = function () {
img.removeEventListener("load", handler, false);
img.removeEventListener("error", handler, false);
};
img.src = "data:," + Math.random();
if (fun) d.callback.ok = fun;
return d;
};
Deferred.next_tick = (typeof process === 'object' && typeof process.nextTick === 'function') && function (fun) {
var d = new Deferred();
process.nextTick(function() { d.call() });
if (fun) d.callback.ok = fun;
return d;
}
Deferred.next = Deferred.next_faster_way_readystatechange ||
Deferred.next_faster_way_Image ||
Deferred.next_tick ||
Deferred.next_default;
Deferred.chain = function () {
var chain = next();
for (var i = 0, len = arguments.length; i < len; i++) (function (obj) {
switch (typeof obj) {
case "function":
var name = null;
try {
name = obj.toString().match(/^\s*function\s+([^\s()]+)/)[1];
} catch (e) { }
if (name != "error") {
chain = chain.next(obj);
} else {
chain = chain.error(obj);
}
break;
case "object":
chain = chain.next(function() { return parallel(obj) });
break;
default:
throw "unknown type in process chains";
}
})(arguments[i]);
return chain;
}
Deferred.wait = function (n) {
var d = new Deferred(), t = new Date();
var id = setTimeout(function () {
d.call((new Date).getTime() - t.getTime());
}, n * 1000);
d.canceller = function () { clearTimeout(id) };
return d;
};
Deferred.call = function (fun) {
var args = Array.prototype.slice.call(arguments, 1);
return Deferred.next(function () {
return fun.apply(this, args);
});
};
Deferred.parallel = function (dl) {
if (arguments.length > 1) dl = Array.prototype.slice.call(arguments);
var ret = new Deferred(), values = {}, num = 0;
for (var i in dl) if (dl.hasOwnProperty(i)) (function (d, i) {
if (typeof d == "function") d = next(d);
d.next(function (v) {
values[i] = v;
if (--num <= 0) {
if (dl instanceof Array) {
values.length = dl.length;
values = Array.prototype.slice.call(values, 0);
}
ret.call(values);
}
}).error(function (e) {
ret.fail(e);
});
num++;
})(dl[i], i);
if (!num) Deferred.next(function () { ret.call() });
ret.canceller = function () {
for (var i in dl) if (dl.hasOwnProperty(i)) {
dl[i].cancel();
}
};
return ret;
};
Deferred.earlier = function (dl) {
if (arguments.length > 1) dl = Array.prototype.slice.call(arguments);
var ret = new Deferred(), values = {}, num = 0;
for (var i in dl) if (dl.hasOwnProperty(i)) (function (d, i) {
d.next(function (v) {
values[i] = v;
if (dl instanceof Array) {
values.length = dl.length;
values = Array.prototype.slice.call(values, 0);
}
ret.canceller();
ret.call(values);
}).error(function (e) {
ret.fail(e);
});
num++;
})(dl[i], i);
if (!num) Deferred.next(function () { ret.call() });
ret.canceller = function () {
for (var i in dl) if (dl.hasOwnProperty(i)) {
dl[i].cancel();
}
};
return ret;
};
Deferred.loop = function (n, fun) {
var o = {
begin : n.begin || 0,
end : (typeof n.end == "number") ? n.end : n - 1,
step : n.step || 1,
last : false,
prev : null
};
var ret, step = o.step;
return Deferred.next(function () {
function _loop (i) {
if (i <= o.end) {
if ((i + step) > o.end) {
o.last = true;
o.step = o.end - i + 1;
}
o.prev = ret;
ret = fun.call(this, i, o);
if (ret instanceof Deferred) {
return ret.next(function (r) {
ret = r;
return Deferred.call(_loop, i + step);
});
} else {
return Deferred.call(_loop, i + step);
}
} else {
return ret;
}
}
return (o.begin <= o.end) ? Deferred.call(_loop, o.begin) : null;
});
};
Deferred.repeat = function (n, fun) {
var i = 0, end = {}, ret = null;
return Deferred.next(function () {
var t = (new Date()).getTime();
divide: {
do {
if (i >= n) break divide;
ret = fun(i++);
} while ((new Date()).getTime() - t < 20);
return Deferred.call(arguments.callee);
}
});
};
Deferred.register = function (name, fun) {
this.prototype[name] = function () {
var a = arguments;
return this.next(function () {
return fun.apply(this, a);
});
};
};
Deferred.register("loop", Deferred.loop);
Deferred.register("wait", Deferred.wait);
Deferred.connect = function (funo, options) {
var target, func, obj;
if (typeof arguments[1] == "string") {
target = arguments[0];
func = target[arguments[1]];
obj = arguments[2] || {};
} else {
func = arguments[0];
obj = arguments[1] || {};
target = obj.target;
}
var partialArgs = obj.args ? Array.prototype.slice.call(obj.args, 0) : [];
var callbackArgIndex = isFinite(obj.ok) ? obj.ok : obj.args ? obj.args.length : undefined;
var errorbackArgIndex = obj.ng;
return function () {
var d = new Deferred().next(function (args) {
var next = this._next.callback.ok;
this._next.callback.ok = function () {
return next.apply(this, args.args);
};
});
var args = partialArgs.concat(Array.prototype.slice.call(arguments, 0));
if (!(isFinite(callbackArgIndex) && callbackArgIndex !== null)) {
callbackArgIndex = args.length;
}
var callback = function () { d.call(new Deferred.Arguments(arguments)) };
args.splice(callbackArgIndex, 0, callback);
if (isFinite(errorbackArgIndex) && errorbackArgIndex !== null) {
var errorback = function () { d.fail(arguments) };
args.splice(errorbackArgIndex, 0, errorback);
}
Deferred.next(function () { func.apply(target, args) });
return d;
}
}
Deferred.Arguments = function (args) { this.args = Array.prototype.slice.call(args, 0) }
Deferred.retry = function (retryCount, funcDeferred, options) {
if (!options) options = {};
var wait = options.wait || 0;
var d = new Deferred();
var retry = function () {
var m = funcDeferred(retryCount);
m.
next(function (mes) {
d.call(mes);
}).
error(function (e) {
if (--retryCount <= 0) {
d.fail(['retry failed', e]);
} else {
setTimeout(retry, wait * 1000);
}
});
};
setTimeout(retry, 0);
return d;
}
Deferred.methods = ["parallel", "wait", "next", "call", "loop", "repeat", "chain"];
Deferred.define = function (obj, list) {
if (!list) list = Deferred.methods;
if (!obj) obj = (function getGlobal () { return this })();
for (var i = 0; i < list.length; i++) {
var n = list[i];
obj[n] = Deferred[n];
}
return Deferred;
};
function http (opts) {
var d = Deferred();
var req = new XMLHttpRequest();
req.open(opts.method, opts.url, true);
if (opts.headers) {
for (var k in opts.headers) if (opts.headers.hasOwnProperty(k)) {
req.setRequestHeader(k, opts.headers[k]);
}
}
req.onreadystatechange = function () {
if (req.readyState == 4) d.call(req);
};
req.send(opts.data || null);
d.xhr = req;
return d;
}
http.get = function (url) { return http({method:"get", url:url}) };
http.post = function (url, data) { return http({method:"post", url:url, data:data, headers:{"Content-Type":"application/x-www-form-urlencoded"}}) };
http.jsonp = function (url, params) {
if (!params) params = {};
var Global = (function () { return this })();
var d = Deferred();
var cbname = params["callback"];
if (!cbname) do {
cbname = "callback" + String(Math.random()).slice(2);
} while (typeof(Global[cbname]) != "undefined");
params["callback"] = cbname;
url += (url.indexOf("?") == -1) ? "?" : "&";
for (var name in params) if (params.hasOwnProperty(name)) {
url = url + encodeURIComponent(name) + "=" + encodeURIComponent(params[name]) + "&";
}
var script = document.createElement('script');
script.type = "text/javascript";
script.charset = "utf-8";
script.src = url;
document.body.appendChild(script);
Global[cbname] = function callback (data) {
delete Global[cbname];
document.body.removeChild(script);
d.call(data);
};
return d;
};
function xhttp (opts) {
var d = Deferred();
if (opts.onload) d = d.next(opts.onload);
if (opts.onerror) d = d.error(opts.onerror);
opts.onload = function (res) {
d.call(res);
};
opts.onerror = function (res) {
d.fail(res);
};
setTimeout(function () {
GM_xmlhttpRequest(opts);
}, 0);
return d;
}
xhttp.get = function (url) { return xhttp({method:"get", url:url}) };
xhttp.post = function (url, data) { return xhttp({method:"post", url:url, data:data, headers:{"Content-Type":"application/x-www-form-urlencoded"}}) };
Deferred.Deferred = Deferred;
Deferred.http = http;
Deferred.xhttp = (typeof(GM_xmlhttpRequest) == 'undefined') ? http : xhttp;
function Diff(B, A) {
this.path = {};
if ( A.length < B.length ) {
this.a = A;
this.b = B;
} else {
this.a = B;
this.b = A;
this.reverse = true;
}
// push dummy.
this.a.unshift(null);
this.b.unshift(null);
this.a.unshift(null);
this.b.unshift(null);
this.fp = {};
this.lcs = this.onp();
}
Diff.prototype.getM = function () { return this.a.length - 1; };
Diff.prototype.getN = function () { return this.b.length - 1; };
Diff.prototype.onp = function () {
var M = this.getM();
var N = this.getN();
var delta = N - M;
var p = 0;
do {
for (var k = -p ; k <= delta - 1 ; k++) {
var y = this.snake(k);
this.fp[k] = y;
}
for (var k = delta + p ; k >= delta + 1 ; k--) {
var y = this.snake(k);
this.fp[k] = y;
}
/////// k = delta
var k = delta;
var y = this.snake(k);
this.fp[k] = y;
p++;
} while ( this.fp[delta] != N );
return this.path[delta];
};
Diff.prototype.snake = function(k) {
var i = ( this.fp[k - 1] || -1 ) + 1;
var j = this.fp[k + 1] || -1;
var v;
if ( i > j ) {
if ( this.path[k - 1] )
this.path[k] = this.path[k - 1].slice(0);
v = 1;
} else {
if ( this.path[k + 1] )
this.path[k] = this.path[k + 1].slice(0);
v = -1;
}
if ( ! this.path[k] )
this.path[k] = [];
this.path[k].push(v);
var y = Math.max(i, j);
var x = y - k;
while (
( x < this.getM() && y < this.getN() ) &&
this.compare( this.a[x+1], this.b[y+1] )
) {
x++;
y++;
this.path[k].push(0);
}
return y;
};
Diff.prototype.compare = function (a, b) {
return a == b;
};
// UnifiedDiff class; added by ucnv
// http://gist.github.com/105908
function UnifiedDiff(B, A, l) {
this.diff = new Diff(B.split(/\r?\n/), A.split(/\r?\n/));
this.lines = new Array();
var a = (this.diff.reverse) ? this.diff.b : this.diff.a;
var b = (this.diff.reverse) ? this.diff.a : this.diff.b;
var r = (this.diff.reverse) ? function(e) { return e * -1 } : function(e) { return e };
var lcs = this.diff.lcs;
a.shift();
b.shift();
lcs.shift();
var marks = new Array(lcs.length);
for(var i = 1, p = false, n; i < marks.length; i++) {
n = lcs[i];
if(n != 0) {
if(!p) {
for(var j = 0; j <= l; j++) {
if(i - j >= 0 && !marks[i - j]) marks[i - j] = ' ';
}
}
marks[i] = (r(n) > 0) ? '-' : '+';
} else {
if(p) {
for(var j = 0; j < l; j++) {
if(i + j < marks.length) marks[i + j] = ' ';
}
}
}
p = (n != 0);
}
marks.push('');
for(var i = 1, printing = false, ap = 1, bp = 1, ac = 0, bc = 0, sp, m; i < marks.length; i++) {
m = marks[i];
if(!m) {
if(printing && sp >= 0) {
this.lines[sp] = this.lines[sp].replace(/\$ac/, ac).replace(/\$bc/, bc).replace(/\,1 /, '');
}
printing = false;
ac = bc = 0;
ap++;
bp++;
continue;
}
if(!printing) {
sp = this.lines.length;
this.lines.push('@@ -' + bp + ',$bc +' + ap + ',$ac @@');
printing = true;
}
if(m == ' ') {
this.lines.push(m + a[ap++]);
bp++;
ac++;
bc++;
} else if(m == '-') {
this.lines.push(m + b[bp++]);
bc++;
} else if(m == '+') {
this.lines.push(m + a[ap++]);
ac++;
}
}
}
UnifiedDiff.prototype.toString = function() {
return this.lines.join('\n') + '\n';
};
(function() {
var $ = unsafeWindow ? unsafeWindow.jQuery : window.jQuery;
var rev = $('#revisions li');
if(!rev.length || rev.length == 1) return;
rev.each(function() {
var r = $(this);
r.prepend(
$('<input type="checkbox" />')
.attr('name', 'diff')
.val(r.find('.id').attr('href'))
.bind('click', diffSelect)
);
});
$('#revisions').append(
$('<input type="button" />')
.attr('name', 'diffExec')
.attr('id', 'diffExec')
.val('Compare')
.bind('click', diffExec)
.attr('disabled', 'disabled')
);
function diffSelect(e) {
var me = e.target;
var c = $('#revisions li input:checked');
if(c.length > 2) c.each(function(i) { c[i].checked = (c[i] == me); });
$('#diffExec').attr('disabled', (c.length != 2));
}
function diffExec() {
if(!$('#diffView').length) $('#files').prepend('<div id="diffView"></div>');
var diffView = $('#diffView');
diffView.hide();
var selected = $('#revisions').find('input:checkbox:checked');
var link = selected.map(function() { return this.value.replace(/(https?:\/\/[^/]+\/)/, '$1raw/') });
var desc = selected.map(function() { return $(this).parent().text().replace(/\s+/g, ' '); });
var urlbase = link[0].slice(0, link[0].lastIndexOf('/') + 1)
with(D()) {
parallel(
Array.map.call(this, link, function(url) { // link is a jQuery object.
return xhttp.get(url + '/meta')
.next(function(res) {
var r = res.responseText.split(/\n/)[0].split(/\s/)[1];
return xhttp.get(urlbase + r + '/meta');
});
})
).next(function (res) {
var data = res.map(function(r) {
return r.responseText.split(/\n/).map(function(e) {
var v = e.split(' ')[2].split("\t");
return { hash: v[0], name: v[1] || '' };
});
});
var diffQueue = [], diffHtml = [];
var listChanged = (data[0].length != data[1].length);
data[0].forEach(function(d0) {
data[1].forEach(function(d1) {
listChanged = listChanged || (d0.hash == d1.hash && d0.name != d1.name);
if(d0.hash != d1.hash && d0.name == d1.name) diffQueue.push(d0, d1);
});
});
if(listChanged) {
var d = data.map(function(e) { return e.map(function(o) { return o.name }) });
var diff = new Diff(d[1], d[0]);
diff.a.shift(), diff.b.shift(), diff.lcs.shift();
var messages = diff.lcs.map(function(n) {
if(n > 0) {
return '<p class="gi" style="padding:2px;">' + diff.b.shift() + ' added</p>';
} else if(n < 0) {
return '<p class="gd" style="padding:2px;">' + diff.a.shift() + ' removed</p>';
} else {
diff.a.shift(), diff.b.shift();
return '';
}
});
diffHtml.push('<div class="file"><div class="data syntax">' + messages.join('') + '</div></div>');
}
parallel(
diffQueue.map(function(e) {
return xhttp.get(urlbase + e.hash + '/' + e.name);
})
).next(function(res) {
var format = function(contentB, contentA, includeLines, nameB, nameA) {
this.pre = this.pre || $('<pre></pre>');
var udiff = new UnifiedDiff(contentB, contentA, includeLines).toString();
udiff = '--- ' + nameB + '\n' + '+++ ' + nameA + '\n' + this.pre.text(udiff).html();
if(udiff.split(/\n/).length < 5000) { // ignore if the diff is too big.
udiff = udiff.replace(/^(.*)\n/mg, '<div class="line">$1</div>')
.replace(/">\+/mg, ' gi">+')
.replace(/">\-/mg, ' gd">-')
.replace(/">\@/mg, ' gu">@');
}
return '<div class="file"><div class="data syntax"><div class="highlight"><pre>'
+ udiff
+ '</pre></div></div></div>';
};
diffQueue.forEach(function(e, i, a) {
if(i % 2 == 0) return;
var contentA = res[i - 1].responseText;
var contentB = res[i].responseText;
var nameA = desc[0] + ' ' + diffQueue[i - 1].name;
var nameB = desc[1] + ' ' + e.name;
diffHtml.push(format(contentB, contentA, 3, nameB, nameA));
});
diffView.html(diffHtml.join('') || '<div>No difference.</div>').slideDown('normal');
});
}).error(console.log)
}
}
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment