// ==UserScript==
// @name GitHub - Linkify package.json dependencies
// @description Turns the names of packages into links to their homepages when looking at a package.json file
// @author James Skinner <spiralx@gmail.com> (http://github.com/spiralx)
// @namespace http://spiralx.org/
// @version 0.3.1
// @match *://github.com/*/package.json
// @match *://github.com/*%2Fpackage.json
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js
// ==/UserScript==
(function($) {
'use strict';
function Cache(key) {
this._key = key;
this._data = {};
this.load();
}
Cache.prototype = {
getValue: function(k) {
return this._data[k];
},
setValue: function(k, v) {
this._data[k] = v;
this.save();
},
deleteValue: function(k) {
if (k in this._data) {
delete this._data[k];
this.save();
}
},
hasValue: function(k) {
return k in this._data;
},
listValues: function() {
return Object.keys(this._data).sort();
},
clear: function() {
this._data = {};
this.save();
},
save: function() {
var s = JSON.stringify(this._data);
GM_setValue(this._key, s);
console.info('Cache(' + this._key + ') saved: ' + s);
},
load: function(s) {
try {
this._data = JSON.parse(s || GM_getValue(this._key));
}
catch (ex) {
this.clear();
}
},
edit: function() {
var res = window.prompt('Edit cached package URLs', JSON.stringify(this._data, null, 2));
if (res !== null) {
try {
this._data = res ? JSON.parse(res) : {};
this.save();
}
catch (ex) {
console.warn('Failed to update cache data: %s %o', ex.toString(), ex);
}
}
},
toString: function() {
return 'Cache(' + this._key + '): [' + this.listValues.join('\n') + ']';
},
dump: function() {
console.log('Cache(' + this._key + '):\n' + JSON.stringify(this._data, null, 2));
}
};
// --------------------------------------------------------------------------
function extractWebUrl(j) {
if (!j) {
return;
}
return (typeof j === 'string' ? j : j.url)
.replace(/^gitlab:(\S+)\/(\S+)$/, 'https://gitlab.com/$1/$2')
.replace(/^bitbucket:(\S+)\/(\S+)$/, 'https://bitbucket.com/$1/$2')
.replace(/^gist:(\S+?)$/, 'https://gist.github.com/$1')
.replace(/^([^\/\s]+)\/([^\/\s]+)$/, 'https://github.com/$1/$2')
.replace(/^git(\+https)?:\/\//, 'https://')
.replace(/(\/svn\/trunk|\.git|\/issues)$/, '');
}
// --------------------------------------------------------------------------
function getPackageLink(packageName, url, background) {
return $('<a>')
.text(packageName)
.attr('href', url)
.css('background-color', background || 'white');
}
// --------------------------------------------------------------------------
function getPackageData(packageName, $a) {
var url = 'http://registry.npmjs.com/' + packageName;
GM_xmlhttpRequest({
url: url,
method: 'GET',
onload: function(response) {
var data = JSON.parse(response.responseText),
homepage = extractWebUrl(data.repository) || extractWebUrl(data.homepage) || extractWebUrl(data.bugs);
if (homepage) {
$a
.attr('href', homepage)
.css('background-color', '#dfd');
cachedHomepages.setValue(packageName, homepage);
}
else {
console.warn(url + ': No homepage found!');
console.dir(data);
$a.css('background-color', '#fdd');
}
},
onerror: function(response) {
console.warn(url + ': ' + response.status + ' ' + response.statusText);
$a.css('background-color', '#fdd');
}
});
}
// --------------------------------------------------------------------------
var dependencyKeys = [
'dependencies',
'devDependencies',
'peerDependencies',
'bundleDependencies',
'optionalDependencies'
];
// --------------------------------------------------------------------------
var cachedHomepages = new Cache('github-linkify-urls');
// console.info(cachedHomepages);
var
$cont = $('.file .js-file-line-container'),
$strings = $cont.find('.pl-s:first-of-type'),
packageJson = JSON.parse($cont.text());
dependencyKeys.forEach(function(k) {
var dependencies = packageJson[k] || {};
Object.keys(dependencies).forEach(function(packageName) {
var homepage,
ns = '"' + packageName + '"',
$e = $strings.filter(function() {
return this.textContent.trim() === ns;
}),
$a;
if ($e.length === 0) {
console.warn('No matching string found for package "' + packageName + '"!');
return;
}
if (cachedHomepages.hasValue(packageName)) {
homepage = cachedHomepages.getValue(packageName);
// console.info('Found "' + homepage + '" for package "' + packageName + '" in cache');
$a = getPackageLink(packageName, homepage, '#dfd');
}
else {
homepage = 'http://www.npmjs.com/package/' + packageName;
// console.info('No homepage found for package "' + packageName + '"');
$a = getPackageLink(packageName, homepage);
getPackageData(packageName, $a);
}
$e
.empty()
.append('<span class="pl-pds">"</span>')
.append($a)
.append('<span class="pl-pds">"</span>');
});
});
// --------------------------------------------------------------------------
GM_registerMenuCommand('Edit cached packages', function() {
cachedHomepages.edit();
}, 'p');
})(jQuery);