Skip to content

Instantly share code, notes, and snippets.

@JosephPecoraro
Forked from ucnv/README.md
Created January 17, 2010 19:34
Show Gist options
  • Save JosephPecoraro/279533 to your computer and use it in GitHub Desktop.
Save JosephPecoraro/279533 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==
// Working for Safari/Webkit with GreaseKit
// A few minor tweaks. I also inlined the requires,
// since GreaseKit doesn't work with @require and
// I'd rather prevent the extra requests / remove deps.
// Missing Functions
// Supplied by Joseph Pecoraro
// NOTE: the "i in" syntax in case arrLike[i] is intended,
// it will correctly handle falsey values.
Array.map = function(arrLike, func) {
var arr = [];
for (var i=0; i in arrLike; ++i)
arr[i] = func(arrLike[i]);
return arr;
}
// @require http://gist.github.com/105908.txt
// Copyright (c) 2007, KUMAGAI Kentaro
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;}
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;}
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;};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';};
// @require http://github.com/cho45/jsdeferred/raw/master/jsdeferred.userscript.js
// JSDeferred 0.3.0 Copyright (c) 2007 cho45 ( www.lowreal.net )
// See http://coderepos.org/share/wiki/JSDeferred
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=((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(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:,/ _ / X";if(fun)d.callback.ok=fun;return d;};Deferred.next=Deferred.next_faster_way_readystatechange||Deferred.next_faster_way_Image||Deferred.next_default;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(f){var args=Array.prototype.slice.call(arguments,1);return Deferred.next(function(){return f.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){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){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,f){var i=0,end={},ret=null;return Deferred.next(function(){var t=(new Date()).getTime();divide:{do{if(i>=n)break divide;ret=f(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.Arguments=function(args){this.args=Array.prototype.slice.call(args,0)}
Deferred.connect=function(func,obj){if(!obj)obj={};var callbackArgIndex=obj.ok;var errorbackArgIndex=obj.ng;var target=obj.target;return function(){var d=new Deferred();d.next=function(fun){return this._post("ok",function(){fun.apply(this,(arguments[0]instanceof Deferred.Arguments)?arguments[0].args:arguments);})};var args=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.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.define=function(obj,list){if(!list)list=["parallel","wait","next","call","loop","repeat"];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 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"}})};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;};Deferred.Deferred=Deferred;Deferred.http=http;Deferred.xhttp=xhttp;return Deferred;}
(function() {
var $ = 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 http.get(url).next(function(res) {
var r = res.responseText.split(/\n/)[0].split(/\s/)[1];
return http.get(urlbase + r);
});
})
).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="gd" style="padding:2px;">' + diff.b.shift() + ' added</p>';
} else if(n < 0) {
return '<p class="gi" 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 http.get(urlbase + e.hash);
})
).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(/^(\+.*)$/mg, '<span class="gi">$1</span>')
.replace(/^(\-.*)$/mg, '<span class="gd">$1</span>')
.replace(/^(\@.*)$/mg, '<span class="gu">$1</span>')
.replace(/^(.*)\n/mg, '<div class="line">$1</div>');
}
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