5705 lines
182 KiB
JavaScript
5705 lines
182 KiB
JavaScript
var secureServer = !!process.env.HTTPS;
|
|
var serverPort = +process.env.PORT || +process.argv[2] || (secureServer? 443: 80);
|
|
var bindAddress = process.env.BIND_ADDRESS || process.argv[3] || '::';
|
|
var infoSite = process.env.INFOSITE || "http://shields.io";
|
|
var Camp = require('camp');
|
|
var camp = Camp.start({
|
|
documentRoot: __dirname,
|
|
port: serverPort,
|
|
hostname: bindAddress,
|
|
secure: secureServer
|
|
});
|
|
Camp.log.unpipe('warn', 'stderr');
|
|
console.log('http://[::1]:' + serverPort + '/try.html');
|
|
var domain = require('domain');
|
|
var request = require('request');
|
|
var fs = require('fs');
|
|
var LruCache = require('./lru-cache.js');
|
|
var badge = require('./badge.js');
|
|
var svg2img = require('./svg-to-img.js');
|
|
var loadLogos = require('./load-logos.js');
|
|
var querystring = require('querystring');
|
|
var serverSecrets;
|
|
try {
|
|
// Everything that cannot be checked in but is useful server-side
|
|
// is stored in this JSON data.
|
|
serverSecrets = require('./secret.json');
|
|
} catch(e) { console.error('No secret data (secret.json, see server.js):', e); }
|
|
var semver = require('semver');
|
|
var serverStartTime = new Date((new Date()).toGMTString());
|
|
|
|
var validTemplates = ['default', 'plastic', 'flat', 'flat-square', 'social'];
|
|
var darkBackgroundTemplates = ['default', 'flat', 'flat-square'];
|
|
var logos = loadLogos();
|
|
|
|
// Analytics
|
|
|
|
// We can either use a process-wide object regularly saved to a JSON file,
|
|
// or a Redis equivalent (for multi-process / when the filesystem is unreliable.
|
|
var redis;
|
|
var useRedis = false;
|
|
if (process.env.REDISTOGO_URL) {
|
|
var redisToGo = require('url').parse(process.env.REDISTOGO_URL);
|
|
redis = require('redis').createClient(redisToGo.port, redisToGo.hostname);
|
|
redis.auth(redisToGo.auth.split(':')[1]);
|
|
useRedis = true;
|
|
}
|
|
|
|
var analytics = {};
|
|
|
|
var analyticsAutoSaveFileName = process.env.SHIELDS_ANALYTICS_FILE || './analytics.json';
|
|
var analyticsAutoSavePeriod = 10000;
|
|
setInterval(function analyticsAutoSave() {
|
|
if (useRedis) {
|
|
redis.set(analyticsAutoSaveFileName, JSON.stringify(analytics));
|
|
} else {
|
|
fs.writeFileSync(analyticsAutoSaveFileName, JSON.stringify(analytics));
|
|
}
|
|
}, analyticsAutoSavePeriod);
|
|
|
|
function defaultAnalytics() {
|
|
var analytics = Object.create(null);
|
|
// In case something happens on the 36th.
|
|
analytics.vendorMonthly = new Array(36);
|
|
resetMonthlyAnalytics(analytics.vendorMonthly);
|
|
analytics.rawMonthly = new Array(36);
|
|
resetMonthlyAnalytics(analytics.rawMonthly);
|
|
analytics.vendorFlatMonthly = new Array(36);
|
|
resetMonthlyAnalytics(analytics.vendorFlatMonthly);
|
|
analytics.rawFlatMonthly = new Array(36);
|
|
resetMonthlyAnalytics(analytics.rawFlatMonthly);
|
|
analytics.vendorFlatSquareMonthly = new Array(36);
|
|
resetMonthlyAnalytics(analytics.vendorFlatSquareMonthly);
|
|
analytics.rawFlatSquareMonthly = new Array(36);
|
|
resetMonthlyAnalytics(analytics.rawFlatSquareMonthly);
|
|
return analytics;
|
|
}
|
|
|
|
// Auto-load analytics.
|
|
function analyticsAutoLoad() {
|
|
var defaultAnalyticsObject = defaultAnalytics();
|
|
if (useRedis) {
|
|
redis.get(analyticsAutoSaveFileName, function(err, value) {
|
|
if (err == null && value != null) {
|
|
// if/try/return trick:
|
|
// if error, then the rest of the function is run.
|
|
try {
|
|
analytics = JSON.parse(value);
|
|
// Extend analytics with a new value.
|
|
for (var key in defaultAnalyticsObject) {
|
|
if (!(key in analytics)) {
|
|
analytics[key] = defaultAnalyticsObject[key];
|
|
}
|
|
}
|
|
return;
|
|
} catch(e) {
|
|
console.error('Invalid Redis analytics, resetting.');
|
|
console.error(e);
|
|
}
|
|
}
|
|
analytics = defaultAnalyticsObject;
|
|
});
|
|
} else {
|
|
// Not using Redis.
|
|
try {
|
|
analytics = JSON.parse(fs.readFileSync(analyticsAutoSaveFileName));
|
|
// Extend analytics with a new value.
|
|
for (var key in defaultAnalyticsObject) {
|
|
if (!(key in analytics)) {
|
|
analytics[key] = defaultAnalyticsObject[key];
|
|
}
|
|
}
|
|
} catch(e) {
|
|
if (e.code !== 'ENOENT') {
|
|
console.error('Invalid JSON file for analytics, resetting.');
|
|
console.error(e);
|
|
}
|
|
analytics = defaultAnalyticsObject;
|
|
}
|
|
}
|
|
}
|
|
|
|
var lastDay = (new Date()).getDate();
|
|
function resetMonthlyAnalytics(monthlyAnalytics) {
|
|
for (var i = 0; i < monthlyAnalytics.length; i++) {
|
|
monthlyAnalytics[i] = 0;
|
|
}
|
|
}
|
|
function incrMonthlyAnalytics(monthlyAnalytics) {
|
|
try {
|
|
var currentDay = (new Date()).getDate();
|
|
// If we changed month, reset empty days.
|
|
while (lastDay !== currentDay) {
|
|
// Assumption: at least a hit a month.
|
|
lastDay = (lastDay + 1) % monthlyAnalytics.length;
|
|
monthlyAnalytics[lastDay] = 0;
|
|
}
|
|
monthlyAnalytics[currentDay]++;
|
|
} catch(e) { console.error(e.stack); }
|
|
}
|
|
|
|
analyticsAutoLoad();
|
|
var suggest = require('./suggest.js');
|
|
camp.ajax.on('analytics/v1', function(json, end) { end(analytics); });
|
|
camp.ajax.on('suggest/v1', suggest);
|
|
|
|
// Cache
|
|
|
|
// We avoid calling the vendor's server for computation of the information in a
|
|
// number of badges.
|
|
var minAccuracy = 0.75;
|
|
|
|
// The quotient of (vendor) data change frequency by badge request frequency
|
|
// must be lower than this to trigger sending the cached data *before*
|
|
// updating our data from the vendor's server.
|
|
// Indeed, the accuracy of our badges are:
|
|
// A(Δt) = 1 - min(# data change over Δt, # requests over Δt)
|
|
// / (# requests over Δt)
|
|
// = 1 - max(1, df) / rf
|
|
var freqRatioMax = 1 - minAccuracy;
|
|
|
|
// Request cache size of 5MB (~5000 bytes/image).
|
|
var requestCache = new LruCache(1000);
|
|
|
|
// Deep error handling for vendor hooks.
|
|
var vendorDomain = domain.create();
|
|
vendorDomain.on('error', function(err) {
|
|
console.error('Vendor hook error:', err.stack);
|
|
});
|
|
|
|
|
|
function cache(f) {
|
|
return function getRequest(data, match, end, ask) {
|
|
if (data.maxAge !== undefined && /^[0-9]+$/.test(data.maxAge)) {
|
|
var maxAge = +data.maxAge;
|
|
} else {
|
|
// Cache management - no cache, so it won't be cached by GitHub's CDN.
|
|
ask.res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
}
|
|
var reqTime = new Date();
|
|
var date = (reqTime).toGMTString();
|
|
ask.res.setHeader('Expires', date); // Proxies, GitHub, see #221.
|
|
ask.res.setHeader('Date', date);
|
|
incrMonthlyAnalytics(analytics.vendorMonthly);
|
|
if (data.style === 'flat') {
|
|
incrMonthlyAnalytics(analytics.vendorFlatMonthly);
|
|
} else if (data.style === 'flat-square') {
|
|
incrMonthlyAnalytics(analytics.vendorFlatSquareMonthly);
|
|
}
|
|
|
|
var cacheIndex = match[0] + '?label=' + data.label + '&style=' + data.style
|
|
+ '&logo=' + data.logo + '&logoWidth=' + data.logoWidth
|
|
+ '&link=' + data.link;
|
|
// Should we return the data right away?
|
|
var cached = requestCache.get(cacheIndex);
|
|
var cachedVersionSent = false;
|
|
if (cached !== undefined) {
|
|
// A request was made not long ago.
|
|
var tooSoon = (+reqTime - cached.time) < cached.interval;
|
|
if (tooSoon || (cached.dataChange / cached.reqs <= freqRatioMax)) {
|
|
badge(cached.data.badgeData, makeSend(cached.data.format, ask.res, end));
|
|
cachedVersionSent = true;
|
|
// We do not wish to call the vendor servers.
|
|
if (tooSoon) { return; }
|
|
}
|
|
}
|
|
|
|
// In case our vendor servers are unresponsive.
|
|
var serverUnresponsive = false;
|
|
var serverResponsive = setTimeout(function() {
|
|
serverUnresponsive = true;
|
|
if (cachedVersionSent) { return; }
|
|
if (requestCache.has(cacheIndex)) {
|
|
var cached = requestCache.get(cacheIndex).data;
|
|
badge(cached.badgeData, makeSend(cached.format, ask.res, end));
|
|
return;
|
|
}
|
|
var badgeData = getBadgeData('vendor', data);
|
|
badgeData.text[1] = 'unresponsive';
|
|
var extension;
|
|
try {
|
|
extension = match[0].split('.').pop();
|
|
} catch(e) { extension = 'svg'; }
|
|
badge(badgeData, makeSend(extension, ask.res, end));
|
|
}, 25000);
|
|
|
|
// Only call vendor servers when last request is older than…
|
|
var cacheInterval = 5000; // milliseconds
|
|
var cachedRequest = function (uri, options, callback) {
|
|
if ((typeof options === 'function') && !callback) { callback = options; }
|
|
if (options && typeof options === 'object') {
|
|
options.uri = uri;
|
|
} else if (typeof uri === 'string') {
|
|
options = {uri:uri};
|
|
} else {
|
|
options = uri;
|
|
}
|
|
options.headers = options.headers || {};
|
|
options.headers['User-Agent'] = options.headers['User-Agent'] || 'Shields.io';
|
|
return request(options, function(err, res, json) {
|
|
if (res != null && res.headers != null) {
|
|
var cacheControl = res.headers['cache-control'];
|
|
if (cacheControl != null) {
|
|
var age = cacheControl.match(/max-age=([0-9]+)/);
|
|
if ((age != null) && (+age[1] === +age[1])) {
|
|
cacheInterval = +age[1] * 1000;
|
|
}
|
|
}
|
|
}
|
|
callback(err, res, json);
|
|
});
|
|
};
|
|
|
|
vendorDomain.run(function() {
|
|
f(data, match, function sendBadge(format, badgeData) {
|
|
if (serverUnresponsive) { return; }
|
|
clearTimeout(serverResponsive);
|
|
// Check for a change in the data.
|
|
var dataHasChanged = false;
|
|
if (cached !== undefined
|
|
&& cached.data.badgeData.text[1] !== badgeData.text[1]) {
|
|
dataHasChanged = true;
|
|
}
|
|
// Add format to badge data.
|
|
badgeData.format = format;
|
|
// Update information in the cache.
|
|
var updatedCache = {
|
|
reqs: cached? (cached.reqs + 1): 1,
|
|
dataChange: cached? (cached.dataChange + (dataHasChanged? 1: 0))
|
|
: 1,
|
|
time: +reqTime,
|
|
interval: cacheInterval,
|
|
data: { format: format, badgeData: badgeData }
|
|
};
|
|
requestCache.set(cacheIndex, updatedCache);
|
|
if (!cachedVersionSent) {
|
|
// Set the cache interval if specified.
|
|
if (maxAge !== undefined) {
|
|
ask.res.setHeader('Cache-Control', 'max-age=' + maxAge);
|
|
}
|
|
badge(badgeData, makeSend(format, ask.res, end));
|
|
}
|
|
}, cachedRequest);
|
|
});
|
|
};
|
|
}
|
|
|
|
camp.notfound(/.*/, function(query, match, end, request) {
|
|
end(null, {template: '404.html'});
|
|
});
|
|
|
|
|
|
|
|
// Vendors.
|
|
|
|
// JIRA issue integration
|
|
camp.route(/^\/jira\/issue\/(http(?:s)?)\/(.+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function (data, match, sendBadge, request) {
|
|
var protocol = match[1]; // eg, https
|
|
var host = match[2]; // eg, issues.apache.org/jira
|
|
var issueKey = match[3]; // eg, KAFKA-2896
|
|
var format = match[4];
|
|
|
|
var options = {
|
|
method: 'GET',
|
|
json: true,
|
|
uri: protocol + '://' + host + '/rest/api/2/issue/' +
|
|
encodeURIComponent(issueKey)
|
|
};
|
|
if (serverSecrets && serverSecrets.jira_username) {
|
|
options.auth = {
|
|
user: serverSecrets.jira_username,
|
|
pass: serverSecrets.jira_password
|
|
};
|
|
}
|
|
|
|
// map JIRA color names to closest shields color schemes
|
|
var colorMap = {
|
|
'medium-gray': 'lightgrey',
|
|
'green': 'green',
|
|
'yellow': 'yellow',
|
|
'brown': 'orange',
|
|
'warm-red': 'red',
|
|
'blue-gray': 'blue'
|
|
};
|
|
|
|
var badgeData = getBadgeData(issueKey, data);
|
|
request(options, function (err, res, json) {
|
|
if (err !== null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var jiraIssue = json;
|
|
if (jiraIssue.fields && jiraIssue.fields.status) {
|
|
if (jiraIssue.fields.status.name) {
|
|
badgeData.text[1] = jiraIssue.fields.status.name; // e.g. "In Development"
|
|
}
|
|
if (jiraIssue.fields.status.statusCategory) {
|
|
badgeData.colorscheme = colorMap[jiraIssue.fields.status.statusCategory.colorName] || 'lightgrey';
|
|
}
|
|
} else {
|
|
badgeData.text[1] = 'invalid';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch (e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Travis integration
|
|
camp.route(/^\/travis(-ci)?\/([^\/]+\/[^\/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[2]; // eg, espadrine/sc
|
|
var branch = match[3];
|
|
var format = match[4];
|
|
var options = {
|
|
method: 'HEAD',
|
|
uri: 'https://api.travis-ci.org/' + userRepo + '.svg',
|
|
};
|
|
if (branch != null) {
|
|
options.uri += '?branch=' + branch;
|
|
}
|
|
var badgeData = getBadgeData('build', data);
|
|
request(options, function(err, res) {
|
|
if (err != null) {
|
|
console.error('Travis error: ' + err.stack);
|
|
if (res) { console.error(''+res); }
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var state = res.headers['content-disposition']
|
|
.match(/filename="(.+)\.svg"/)[1];
|
|
badgeData.text[1] = state;
|
|
if (state === 'passing') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (state === 'failing') {
|
|
badgeData.colorscheme = 'red';
|
|
} else {
|
|
badgeData.text[1] = state;
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Shippable integration
|
|
camp.route(/^\/shippable\/([^\/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var project = match[1]; // eg, 54d119db5ab6cc13528ab183
|
|
var branch = match[2];
|
|
var format = match[3];
|
|
var url = 'https://api.shippable.com/projects/' + project + '/badge';
|
|
if (branch != null) {
|
|
url += '?branchName=' + branch;
|
|
}
|
|
var badgeData = getBadgeData('build', data);
|
|
fetchFromSvg(request, url, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
badgeData.text[1] = res;
|
|
if (res === 'shippable') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'passing';
|
|
} else if (res === 'unshippable') {
|
|
badgeData.colorscheme = 'red';
|
|
} else {
|
|
badgeData.text[1] = res;
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Wercker integration
|
|
camp.route(/^\/wercker\/ci\/([a-fA-F0-9]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var projectId = match[1]; // eg, `54330318b4ce963d50020750`
|
|
var format = match[2];
|
|
var options = {
|
|
method: 'GET',
|
|
json: true,
|
|
uri: 'https://app.wercker.com/getbuilds/' + projectId + '?limit=1'
|
|
};
|
|
var badgeData = getBadgeData('build', data);
|
|
request(options, function(err, res, json) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var build = json[0];
|
|
|
|
if (build.status === 'finished') {
|
|
if (build.result === 'passed') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'passing';
|
|
} else {
|
|
badgeData.colorscheme = 'red';
|
|
badgeData.text[1] = build.result;
|
|
}
|
|
} else {
|
|
badgeData.text[1] = build.status;
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Wercker V3 integration
|
|
camp.route(/^\/wercker\/ci\/(.+)\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var owner = match[1];
|
|
var application = match[2];
|
|
var format = match[3];
|
|
var options = {
|
|
method: 'GET',
|
|
json: true,
|
|
uri: 'https://app.wercker.com/api/v3/applications/' + owner + '/' + application + '/builds?limit=1'
|
|
};
|
|
var badgeData = getBadgeData('build', data);
|
|
request(options, function(err, res, json) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var build = json[0];
|
|
|
|
if (build.status === 'finished') {
|
|
if (build.result === 'passed') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'passing';
|
|
} else {
|
|
badgeData.colorscheme = 'red';
|
|
badgeData.text[1] = build.result;
|
|
}
|
|
} else {
|
|
badgeData.text[1] = build.status;
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Rust download and version integration
|
|
camp.route(/^\/crates\/(d|v|dv|l)\/([A-Za-z0-9_-]+)(?:\/([0-9.]+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function (data, match, sendBadge, request) {
|
|
var mode = match[1]; // d - downloads (total or for version), v - (latest) version, dv - downloads (for latest version)
|
|
var crate = match[2]; // crate name, e.g. rustc-serialize
|
|
var version = match[3]; // crate version in semver format, optional, e.g. 0.1.2
|
|
var format = match[4];
|
|
var modes = {
|
|
'd': {
|
|
name: 'downloads',
|
|
version: true,
|
|
process: function (data, badgeData) {
|
|
downloads = data.crate? data.crate.downloads: data.version.downloads;
|
|
version = data.version && data.version.num;
|
|
badgeData.text[1] = metric(downloads) + (version? ' version ' + version: '');
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
}
|
|
},
|
|
'dv': {
|
|
name: 'downloads',
|
|
version: true,
|
|
process: function (data, badgeData) {
|
|
downloads = data.version? data.version.downloads: data.versions[0].downloads;
|
|
version = data.version && data.version.num;
|
|
badgeData.text[1] = metric(downloads) + (version? ' version ' + version: ' latest version');
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
}
|
|
},
|
|
'v': {
|
|
name: 'crates.io',
|
|
version: true,
|
|
process: function (data, badgeData) {
|
|
version = data.version? data.version.num: data.crate.max_version;
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
}
|
|
},
|
|
'l': {
|
|
name: 'license',
|
|
version: false,
|
|
process: function (data, badgeData) {
|
|
badgeData.text[1] = data.crate.license;
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
}
|
|
};
|
|
var behavior = modes[mode];
|
|
var apiUrl = 'https://crates.io/api/v1/crates/' + crate;
|
|
if (version != null && behavior.version) {
|
|
apiUrl += '/' + version;
|
|
}
|
|
|
|
var badgeData = getBadgeData(behavior.name, data);
|
|
request(apiUrl, { headers: { 'Accept': 'application/json' } }, function (err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(badgeData, format);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
behavior.process(data, badgeData);
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch (e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// AppVeyor CI integration.
|
|
camp.route(/^\/appveyor\/ci\/([^\/]+\/[^\/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, `gruntjs/grunt`.
|
|
var branch = match[2];
|
|
var format = match[3];
|
|
var apiUrl = 'https://ci.appveyor.com/api/projects/' + repo;
|
|
if (branch != null) {
|
|
apiUrl += '/branch/' + branch;
|
|
}
|
|
var badgeData = getBadgeData('build', data);
|
|
request(apiUrl, { headers: { 'Accept': 'application/json' } }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var status = data.build.status;
|
|
if (status === 'success') {
|
|
badgeData.text[1] = 'passing';
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (status !== 'running' && status !== 'queued') {
|
|
badgeData.text[1] = 'failing';
|
|
badgeData.colorscheme = 'red';
|
|
} else {
|
|
badgeData.text[1] = status;
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
function teamcity_badge(url, buildId, advanced, format, data, sendBadge) {
|
|
var apiUrl = url + '/app/rest/builds/buildType:(id:' + buildId + ')?guest=1';
|
|
var badgeData = getBadgeData('build', data);
|
|
request(apiUrl, { headers: { 'Accept': 'application/json' } }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
if (advanced)
|
|
badgeData.text[1] = (data.statusText || data.status || '').toLowerCase();
|
|
else
|
|
badgeData.text[1] = (data.status || '').toLowerCase();
|
|
if (data.status === 'SUCCESS') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'passing';
|
|
} else {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Old url for CodeBetter TeamCity instance.
|
|
camp.route(/^\/teamcity\/codebetter\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var buildType = match[1]; // eg, `bt428`.
|
|
var format = match[2];
|
|
teamcity_badge('http://teamcity.codebetter.com', buildType, false, format, data, sendBadge);
|
|
}));
|
|
|
|
// Generic TeamCity instance
|
|
camp.route(/^\/teamcity\/(http|https)\/(.*)\/(s|e)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var scheme = match[1];
|
|
var serverUrl = match[2];
|
|
var advanced = (match[3] == 'e');
|
|
var buildType = match[4]; // eg, `bt428`.
|
|
var format = match[5];
|
|
teamcity_badge(scheme + '://' + serverUrl, buildType, advanced, format, data, sendBadge);
|
|
}));
|
|
|
|
// TeamCity CodeBetter code coverage
|
|
camp.route(/^\/teamcity\/coverage\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var buildType = match[1]; // eg, `bt428`.
|
|
var format = match[2];
|
|
var apiUrl = 'http://teamcity.codebetter.com/app/rest/builds/buildType:(id:' + buildType + ')/statistics?guest=1';
|
|
var badgeData = getBadgeData('coverage', data);
|
|
request(apiUrl, { headers: { 'Accept': 'application/json' } }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var covered;
|
|
var total;
|
|
|
|
data.property.forEach(function(property) {
|
|
if (property.name === 'CodeCoverageAbsSCovered') {
|
|
covered = property.value;
|
|
} else if (property.name === 'CodeCoverageAbsSTotal') {
|
|
total = property.value;
|
|
}
|
|
})
|
|
|
|
if (covered === undefined || total === undefined) {
|
|
badgeData.text[1] = 'malformed';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
var percentage = covered / total * 100;
|
|
badgeData.text[1] = percentage.toFixed(0) + '%';
|
|
badgeData.colorscheme = coveragePercentageColor(percentage);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// SonarQube code coverage
|
|
camp.route(/^\/sonar\/(http|https)\/(.*)\/(.*)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var scheme = match[1];
|
|
var serverUrl = match[2]; // eg, `sonar.qatools.ru`.
|
|
var buildType = match[3]; // eg, `ru.yandex.qatools.allure:allure-core:master`.
|
|
var metricName = match[4];
|
|
var format = match[5];
|
|
|
|
var sonarMetricName = metricName;
|
|
|
|
if (metricName === 'tech_debt') {
|
|
//special condition for backwards compatibility
|
|
sonarMetricName = 'sqale_debt_ratio';
|
|
}
|
|
|
|
var apiUrl = scheme + '://' + serverUrl + '/api/resources?resource=' + buildType
|
|
+ '&depth=0&metrics=' + encodeURIComponent(sonarMetricName) + '&includetrends=true';
|
|
|
|
var badgeData = getBadgeData(metricName.replace(/_/g, ' '), data);
|
|
|
|
request(apiUrl, { headers: { 'Accept': 'application/json' } }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
|
|
var value = data[0].msr[0].val;
|
|
|
|
if (value === undefined) {
|
|
badgeData.text[1] = 'unknown';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
if (metricName.indexOf('coverage') !== -1) {
|
|
badgeData.text[1] = value.toFixed(0) + '%';
|
|
badgeData.colorscheme = coveragePercentageColor(value);
|
|
} else if (/^\w+_violations$/.test(metricName)) {
|
|
badgeData.text[1] = value;
|
|
badgeData.colorscheme = 'brightgreen';
|
|
if (value > 0) {
|
|
if (metricName === 'blocker_violations') {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (metricName === 'critical_violations') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else if (metricName === 'major_violations') {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (metricName === 'minor_violations') {
|
|
badgeData.colorscheme = 'yellowgreen';
|
|
} else if (metricName === 'info_violations') {
|
|
badgeData.colorscheme = 'green';
|
|
}
|
|
}
|
|
|
|
} else if (metricName === 'fortify-security-rating') {
|
|
badgeData.text[1] = value + '/5';
|
|
|
|
if (value === 0) {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (value === 1) {
|
|
badgeData.colorscheme = 'orange';
|
|
} else if (value === 2) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (value === 3) {
|
|
badgeData.colorscheme = 'yellowgreen';
|
|
} else if (value === 4) {
|
|
badgeData.colorscheme = 'green';
|
|
} else if (value === 5) {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else {
|
|
badgeData.colorscheme = 'lightgrey';
|
|
}
|
|
} else if (metricName === 'sqale_debt_ratio' || metricName === 'tech_debt') {
|
|
// colors are based on sonarqube default rating grid and display colors
|
|
// [0,0.1) ==> A (green)
|
|
// [0.1,0.2) ==> B (yellowgreen)
|
|
// [0.2,0.5) ==> C (yellow)
|
|
// [0.5,1) ==> D (orange)
|
|
// [1,) ==> E (red)
|
|
badgeData.text[1] = value + '%';
|
|
if (value > 100) {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (value > 50) {
|
|
badgeData.colorscheme = 'orange';
|
|
} else if (value > 20) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (value > 10) {
|
|
badgeData.colorscheme = 'yellowgreen';
|
|
} else if (value > 0) {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else {
|
|
badgeData.colorscheme = 'lightgrey';
|
|
}
|
|
} else {
|
|
badgeData.text[1] = metric(value);
|
|
badgeData.colorscheme = 'brightgreen';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Coverity integration
|
|
camp.route(/^\/coverity\/scan\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var projectId = match[1]; // eg, `3997`
|
|
var format = match[2];
|
|
var url = 'https://scan.coverity.com/projects/' + projectId + '/badge.json';
|
|
var badgeData = getBadgeData('coverity', data);
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
badgeData.text[1] = data.message;
|
|
|
|
if (data.message === 'passed') {
|
|
badgeData.colorscheme = 'brightgreen'
|
|
badgeData.text[1] = 'passing';
|
|
} else if (/^passed .* new defects$/.test(data.message)) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (data.message === 'pending') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else if (data.message === 'failed') {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Coverity Code Advisor On Demand integration
|
|
camp.route(/^\/coverity\/ondemand\/(.+)\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var badgeType = match[1]; // One of the strings "streams" or "jobs"
|
|
var badgeTypeId = match[2]; // streamId or jobId
|
|
var format = match[3];
|
|
|
|
var badgeData = getBadgeData('coverity', data);
|
|
if ((badgeType == 'jobs' && badgeTypeId == 'JOB') ||
|
|
(badgeType == 'streams' && badgeTypeId == 'STREAM')) {
|
|
// Request is for a static demo badge
|
|
badgeData.text[1] = 'clean';
|
|
badgeData.colorscheme = 'green'
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
} else {
|
|
//
|
|
// Request is for a real badge; send request to Coverity On Demand API
|
|
// server to get the badge
|
|
//
|
|
// Example URLs for requests sent to Coverity On Demand are:
|
|
//
|
|
// https://api.ondemand.coverity.com/streams/44b25sjc9l3ntc2ngfi29tngro/badge
|
|
// https://api.ondemand.coverity.com/jobs/p4tmm8031t4i971r0im4s7lckk/badge
|
|
//
|
|
|
|
var url = 'https://api.ondemand.coverity.com/' +
|
|
badgeType + '/' + badgeTypeId + '/badge';
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
sendBadge(format, data);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}
|
|
}));
|
|
|
|
// Gratipay integration.
|
|
camp.route(/^\/(?:gittip|gratipay(\/user|\/team)?)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var type = match[1]; // eg, `user`.
|
|
var user = match[2]; // eg, `dougwilson`.
|
|
var format = match[3];
|
|
if (type === '') { type = '/user'; }
|
|
if (type === '/user') { user = '~' + user; }
|
|
var apiUrl = 'https://gratipay.com/' + user + '/public.json';
|
|
var badgeData = getBadgeData('tips', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.gratipay;
|
|
}
|
|
request(apiUrl, function dealWithData(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var receiving = data.receiving || data.taking;
|
|
if (receiving) {
|
|
badgeData.text[1] = '$' + metric(receiving) + '/week';
|
|
if (receiving === 0) {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (receiving < 10) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (receiving < 100) {
|
|
badgeData.colorscheme = 'green';
|
|
} else {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} else {
|
|
badgeData.text[1] = 'anonymous';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Libscore integration.
|
|
camp.route(/^\/libscore\/s\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var library = match[1]; // eg, `jQuery`.
|
|
var format = match[2];
|
|
var apiUrl = 'http://api.libscore.com/v1/libraries/' + library;
|
|
var badgeData = getBadgeData('libscore', data);
|
|
request(apiUrl, function dealWithData(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
badgeData.text[1] = metric(+data.count[data.count.length-1]);
|
|
badgeData.colorscheme = 'blue';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
|
|
|
|
// Bountysource integration.
|
|
camp.route(/^\/bountysource\/team\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var team = match[1]; // eg, `mozilla-core`.
|
|
var type = match[2]; // eg, `activity`.
|
|
var format = match[3];
|
|
var url = 'https://api.bountysource.com/teams/' + team;
|
|
var options = {
|
|
headers: { 'Accept': 'application/vnd.bountysource+json; version=2' } };
|
|
var badgeData = getBadgeData('bounties', data);
|
|
request(url, options, function dealWithData(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
if (type === 'activity') {
|
|
var activity = data.activity_total;
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = activity;
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// HHVM integration.
|
|
camp.route(/^\/hhvm\/([^\/]+\/[^\/]+)(\/.+)?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, `symfony/symfony`.
|
|
var branch = match[2];// eg, `/2.4.0.0`.
|
|
var format = match[3];
|
|
var apiUrl = 'http://hhvm.h4cc.de/badge/' + user + '.json';
|
|
if (branch) {
|
|
// Remove the leading slash.
|
|
apiUrl += '?branch=' + branch.slice(1);
|
|
}
|
|
var badgeData = getBadgeData('hhvm', data);
|
|
request(apiUrl, function dealWithData(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var status = data.hhvm_status;
|
|
if (status === 'not_tested') {
|
|
badgeData.colorscheme = 'red';
|
|
badgeData.text[1] = 'not tested';
|
|
} else if (status === 'partial') {
|
|
badgeData.colorscheme = 'yellow';
|
|
badgeData.text[1] = 'partially tested';
|
|
} else if (status === 'tested') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'tested';
|
|
} else {
|
|
badgeData.text[1] = 'maybe untested';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// SensioLabs.
|
|
camp.route(/^\/sensiolabs\/i\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var projectUuid = match[1];
|
|
var format = match[2];
|
|
var options = {
|
|
method: 'GET',
|
|
uri: 'https://insight.sensiolabs.com/api/projects/' + projectUuid,
|
|
headers: {
|
|
Accept: 'application/vnd.com.sensiolabs.insight+xml'
|
|
}
|
|
};
|
|
|
|
if (serverSecrets && serverSecrets.sl_insight_userUuid) {
|
|
options.auth = {
|
|
user: serverSecrets.sl_insight_userUuid,
|
|
pass: serverSecrets.sl_insight_apiToken
|
|
};
|
|
}
|
|
|
|
var badgeData = getBadgeData('check', data);
|
|
|
|
request(options, function(err, res, body) {
|
|
if (err != null || res.statusCode !== 200) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
var matchStatus = body.match(/\<status\>\<\!\[CDATA\[([a-z]+)\]\]\>\<\/status\>/im);
|
|
var matchGrade = body.match(/\<grade\>\<\!\[CDATA\[([a-z]+)\]\]\>\<\/grade\>/im);
|
|
|
|
if (matchStatus === null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
} else if (matchStatus[1] !== 'finished') {
|
|
badgeData.text[1] = 'pending';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
} else if (matchGrade === null) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
if (matchGrade[1] === 'platinum') {
|
|
badgeData.text[1] = 'platinum';
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (matchGrade[1] === 'gold') {
|
|
badgeData.text[1] = 'gold';
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (matchGrade[1] === 'silver') {
|
|
badgeData.text[1] = 'silver';
|
|
badgeData.colorscheme = 'lightgrey';
|
|
} else if (matchGrade[1] === 'bronze') {
|
|
badgeData.text[1] = 'bronze';
|
|
badgeData.colorscheme = 'orange';
|
|
} else if (matchGrade[1] === 'none') {
|
|
badgeData.text[1] = 'no medal';
|
|
badgeData.colorscheme = 'red';
|
|
} else {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
});
|
|
}));
|
|
|
|
// Packagist integration.
|
|
camp.route(/^\/packagist\/(dm|dd|dt)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // either `dm` or dt`.
|
|
var userRepo = match[2]; // eg, `doctrine/orm`.
|
|
var format = match[3];
|
|
var apiUrl = 'https://packagist.org/packages/' + userRepo + '.json';
|
|
var badgeData = getBadgeData('downloads', data);
|
|
if (userRepo.substr(-14) === '/:package_name') {
|
|
badgeData.text[1] = 'invalid';
|
|
return sendBadge(format, badgeData);
|
|
}
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
switch (info.charAt(1)) {
|
|
case 'm':
|
|
var downloads = data.package.downloads.monthly;
|
|
badgeData.text[1] = metric(downloads) + '/month';
|
|
break;
|
|
case 'd':
|
|
var downloads = data.package.downloads.daily;
|
|
badgeData.text[1] = metric(downloads) + '/day';
|
|
break;
|
|
case 't':
|
|
var downloads = data.package.downloads.total;
|
|
badgeData.text[1] = metric(downloads);
|
|
break;
|
|
}
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Packagist version integration.
|
|
camp.route(/^\/packagist\/(v|vpre)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // either `v` or `vpre`.
|
|
var userRepo = match[2]; // eg, `doctrine/orm`.
|
|
var format = match[3];
|
|
var apiUrl = 'https://packagist.org/packages/' + userRepo + '.json';
|
|
var badgeData = getBadgeData('packagist', data);
|
|
if (userRepo.substr(-14) === '/:package_name') {
|
|
badgeData.text[1] = 'invalid';
|
|
return sendBadge(format, badgeData);
|
|
}
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
|
|
var versionsData = data.package.versions;
|
|
var versions = Object.keys(versionsData);
|
|
|
|
// Map aliases (eg, dev-master).
|
|
var aliasesMap = {};
|
|
versions.forEach(function(version) {
|
|
var versionData = versionsData[version];
|
|
if (versionData.extra && versionData.extra['branch-alias'] &&
|
|
versionData.extra['branch-alias'][version]) {
|
|
// eg, version is 'dev-master', mapped to '2.0.x-dev'.
|
|
var validVersion = versionData.extra['branch-alias'][version];
|
|
if (aliasesMap[validVersion] === undefined ||
|
|
phpVersionCompare(aliasesMap[validVersion], validVersion) < 0) {
|
|
versions.push(validVersion);
|
|
aliasesMap[validVersion] = version;
|
|
}
|
|
}
|
|
});
|
|
versions = versions.filter(function(version) {
|
|
return !(/^dev-/.test(version));
|
|
});
|
|
|
|
var badgeText = null;
|
|
var badgeColor = null;
|
|
|
|
switch (info) {
|
|
case 'v':
|
|
var stableVersions = versions.filter(phpStableVersion);
|
|
var stableVersion = phpLatestVersion(stableVersions);
|
|
if (!stableVersion) {
|
|
stableVersion = phpLatestVersion(versions);
|
|
}
|
|
//if (!!aliasesMap[stableVersion]) {
|
|
// stableVersion = aliasesMap[stableVersion];
|
|
//}
|
|
var vdata = versionColor(stableVersion);
|
|
badgeText = vdata.version;
|
|
badgeColor = vdata.color;
|
|
break;
|
|
case 'vpre':
|
|
var unstableVersion = phpLatestVersion(versions);
|
|
//if (!!aliasesMap[unstableVersion]) {
|
|
// unstableVersion = aliasesMap[unstableVersion];
|
|
//}
|
|
var vdata = versionColor(unstableVersion);
|
|
badgeText = vdata.version;
|
|
badgeColor = 'orange';
|
|
break;
|
|
}
|
|
|
|
if (badgeText !== null) {
|
|
badgeData.text[1] = badgeText;
|
|
badgeData.colorscheme = badgeColor;
|
|
}
|
|
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Packagist license integration.
|
|
camp.route(/^\/packagist\/l\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1];
|
|
var format = match[2];
|
|
var apiUrl = 'https://packagist.org/packages/' + userRepo + '.json';
|
|
var badgeData = getBadgeData('license', data);
|
|
if (userRepo.substr(-14) === '/:package_name') {
|
|
badgeData.text[1] = 'invalid';
|
|
return sendBadge(format, badgeData);
|
|
}
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
// Note: if you change the latest version detection algorithm here,
|
|
// change it above (for the actual version badge).
|
|
var version;
|
|
var unstable = function(ver) { return /dev/.test(ver); };
|
|
// Grab the latest stable version, or an unstable
|
|
for (var versionName in data.package.versions) {
|
|
var current = data.package.versions[versionName];
|
|
|
|
if (version !== undefined) {
|
|
if (unstable(version.version) && !unstable(current.version)) {
|
|
version = current;
|
|
} else if (version.version_normalized < current.version_normalized) {
|
|
version = current;
|
|
}
|
|
} else {
|
|
version = current;
|
|
}
|
|
}
|
|
badgeData.text[1] = version.license[0];
|
|
badgeData.colorscheme = 'blue';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Package Control integration.
|
|
camp.route(/^\/packagecontrol\/(dm|dw|dd|dt)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // either `dm`, `dw`, `dd` or dt`.
|
|
var userRepo = match[2]; // eg, `Package%20Control`.
|
|
var format = match[3];
|
|
var apiUrl = 'https://packagecontrol.io/packages/' + userRepo + '.json';
|
|
var badgeData = getBadgeData('downloads', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var downloads = 0;
|
|
switch (info.charAt(1)) {
|
|
case 'm':
|
|
// daily downloads are separated by Operating System
|
|
var platforms = data.installs.daily.data;
|
|
platforms.forEach(function(platform) {
|
|
// loop through the first 30 days or 1 month
|
|
for (var i = 0; i < 30; i++) {
|
|
// add the downloads for that day for that platform
|
|
downloads += platform.totals[i];
|
|
}
|
|
});
|
|
badgeData.text[1] = metric(downloads) + '/month';
|
|
break;
|
|
case 'w':
|
|
// daily downloads are separated by Operating System
|
|
var platforms = data.installs.daily.data;
|
|
platforms.forEach(function(platform) {
|
|
// loop through the first 7 days or 1 week
|
|
for (var i = 0; i < 7; i++) {
|
|
// add the downloads for that day for that platform
|
|
downloads += platform.totals[i];
|
|
}
|
|
});
|
|
badgeData.text[1] = metric(downloads) + '/week';
|
|
break;
|
|
case 'd':
|
|
// daily downloads are separated by Operating System
|
|
var platforms = data.installs.daily.data;
|
|
platforms.forEach(function(platform) {
|
|
// use the downloads from yesterday
|
|
downloads += platform.totals[1];
|
|
});
|
|
badgeData.text[1] = metric(downloads) + '/day';
|
|
break;
|
|
case 't':
|
|
// all-time downloads are already compiled
|
|
downloads = data.installs.total;
|
|
badgeData.text[1] = metric(downloads);
|
|
break;
|
|
}
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// npm download integration.
|
|
camp.route(/^\/npm\/dm\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var pkg = encodeURIComponent(match[1]); // eg, "express" or "@user/express"
|
|
var format = match[2];
|
|
var apiUrl = 'https://api.npmjs.org/downloads/point/last-month/' + pkg;
|
|
var badgeData = getBadgeData('downloads', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var monthly = JSON.parse(buffer).downloads || 0;
|
|
badgeData.text[1] = metric(monthly) + '/month';
|
|
if (monthly === 0) {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (monthly < 10) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (monthly < 100) {
|
|
badgeData.colorscheme = 'yellowgreen';
|
|
} else if (monthly < 1000) {
|
|
badgeData.colorscheme = 'green';
|
|
} else {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// npm total download integration.
|
|
camp.route(/^\/npm\/dt\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function (data, match, sendBadge, request) {
|
|
var pkg = encodeURIComponent(match[1]); // eg, "express" or "@user/express"
|
|
var format = match[2];
|
|
var apiUrl = 'https://api.npmjs.org/downloads/range/1000-01-01:3000-01-01/' + pkg; // use huge range, will need to fix this in year 3000 :)
|
|
var badgeData = getBadgeData('downloads', data);
|
|
request(apiUrl, function (err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var totalDownloads = 0;
|
|
|
|
var downloads = JSON.parse(buffer).downloads || 0;
|
|
for (var index = 0; index < downloads.length; index++) {
|
|
totalDownloads = totalDownloads + downloads[index].downloads;
|
|
}
|
|
|
|
badgeData.text[1] = metric(totalDownloads);
|
|
if (totalDownloads === 0) {
|
|
badgeData.colorscheme = 'red';
|
|
} else {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch (e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// npm version integration.
|
|
camp.route(/^\/npm\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = encodeURIComponent(match[1]); // eg, "express" or "@user/express"
|
|
var format = match[2];
|
|
var apiUrl = 'https://registry.npmjs.org/-/package/' + repo + '/dist-tags';
|
|
var badgeData = getBadgeData('npm', data);
|
|
// Using the Accept header because of this bug:
|
|
// <https://github.com/npm/npmjs.org/issues/163>
|
|
request(apiUrl, { headers: { 'Accept': '*/*' } }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var version = data.latest;
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// npm license integration.
|
|
camp.route(/^\/npm\/l\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = encodeURIComponent(match[1]); // eg, "express" or "@user/express"
|
|
var format = match[2];
|
|
var apiUrl = 'http://registry.npmjs.org/' + repo + '/latest';
|
|
var badgeData = getBadgeData('license', data);
|
|
request(apiUrl, { headers: { 'Accept': '*/*' } }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var license = data.license;
|
|
if (Array.isArray(license)) {
|
|
license = license.join(', ');
|
|
} else if (typeof license == 'object') {
|
|
license = license.type;
|
|
}
|
|
badgeData.text[1] = license;
|
|
badgeData.colorscheme = 'blue';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// npm node version integration.
|
|
camp.route(/^\/node\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = encodeURIComponent(match[1]); // eg, "express" or "@user/express"
|
|
var format = match[2];
|
|
var apiUrl = 'https://registry.npmjs.org/' + repo + '/latest';
|
|
var badgeData = getBadgeData('node', data);
|
|
// Using the Accept header because of this bug:
|
|
// <https://github.com/npm/npmjs.org/issues/163>
|
|
request(apiUrl, { headers: { 'Accept': '*/*' } }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
if (data.engines && data.engines.node) {
|
|
var versionRange = data.engines.node;
|
|
badgeData.text[1] = versionRange;
|
|
regularUpdate('http://nodejs.org/dist/latest/SHASUMS256.txt',
|
|
(24 * 3600 * 1000),
|
|
function(shasums) {
|
|
// tarball index start, tarball index end
|
|
var taris = shasums.indexOf('node-v');
|
|
var tarie = shasums.indexOf('\n', taris);
|
|
var tarball = shasums.slice(taris, tarie);
|
|
var version = tarball.split('-')[1];
|
|
return version;
|
|
}, function(err, version) {
|
|
if (err != null) { sendBadge(format, badgeData); return; }
|
|
try {
|
|
if (semver.satisfies(version, versionRange)) {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (semver.gtr(version, versionRange)) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else {
|
|
badgeData.colorscheme = 'orange';
|
|
}
|
|
} catch(e) { }
|
|
sendBadge(format, badgeData);
|
|
});
|
|
} else {
|
|
sendBadge(format, badgeData);
|
|
}
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Bintray version integration
|
|
camp.route(/^\/bintray\/v\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var path = match[1]; // :subject/:repo/:package (e.g. asciidoctor/maven/asciidoctorj)
|
|
var format = match[2];
|
|
|
|
var options = {
|
|
method: 'GET',
|
|
uri: 'https://bintray.com/api/v1/packages/' + path + '/versions/_latest',
|
|
headers: {
|
|
Accept: 'application/json'
|
|
}
|
|
};
|
|
|
|
if (serverSecrets && serverSecrets.bintray_user) {
|
|
options.auth = {
|
|
user: serverSecrets.bintray_user,
|
|
pass: serverSecrets.bintray_apikey
|
|
}
|
|
}
|
|
|
|
var badgeData = getBadgeData('bintray', data);
|
|
request(options, function(err, res, buffer) {
|
|
if (err !== null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var vdata = versionColor(data.name);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = 'brightgreen';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Clojars version integration
|
|
camp.route(/^\/clojars\/v\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var clojar = match[1]; // eg, `prismic` or `foo/bar`.
|
|
var format = match[2];
|
|
var apiUrl = 'https://clojars.org/' + clojar + '/latest-version.json';
|
|
var badgeData = getBadgeData('clojars', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err !== null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var vdata = versionColor(data.version);
|
|
badgeData.text[1] = "[" + clojar + " \"" + data.version + "\"]";
|
|
badgeData.colorscheme = 'brightgreen';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Gem version integration.
|
|
camp.route(/^\/gem\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, `formatador`.
|
|
var format = match[2];
|
|
var apiUrl = 'https://rubygems.org/api/v1/gems/' + repo + '.json';
|
|
var badgeData = getBadgeData('gem', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var version = data.version;
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Gem download count
|
|
camp.route(/^\/gem\/(dt|dtv|dv)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // either dt, dtv or dv.
|
|
var repo = match[2]; // eg, "rails"
|
|
var splited_url = repo.split('/');
|
|
var repo = splited_url[0];
|
|
var version = (splited_url.length > 1)
|
|
? splited_url[splited_url.length - 1]
|
|
: null;
|
|
version = (version === "stable") ? version : semver.valid(version);
|
|
var format = match[3];
|
|
var badgeData = getBadgeData('downloads', data);
|
|
if (info === "dv"){
|
|
apiUrl = 'https://rubygems.org/api/v1/versions/' + repo + '.json';
|
|
} else {
|
|
var apiUrl = 'https://rubygems.org/api/v1/gems/' + repo + '.json';
|
|
}
|
|
var parameters = {
|
|
headers: {
|
|
'Accept': 'application/atom+json,application/json'
|
|
}
|
|
};
|
|
request(apiUrl, parameters, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
if (info === "dt") {
|
|
var downloads = metric(data.downloads);
|
|
} else if (info === "dtv") {
|
|
var downloads = metric(data.version_downloads) + " latest version";
|
|
} else if (info === "dv") {
|
|
var downloads = "invalid";
|
|
|
|
if (version !== null && version === "stable") {
|
|
|
|
var versions = data.filter(function(ver) {
|
|
return ver.prerelease === false;
|
|
}).map(function(ver) {
|
|
return ver.number;
|
|
});
|
|
// Found latest stable version.
|
|
var stable_version = latestVersion(versions);
|
|
var version_data = data.filter(function(ver) {
|
|
return ver.number === stable_version;
|
|
})[0];
|
|
downloads = metric(version_data.downloads_count) + " stable version";
|
|
|
|
} else if (version !== null) {
|
|
|
|
var version_data = data.filter(function(ver) {
|
|
return ver.number === version;
|
|
})[0];
|
|
|
|
downloads = metric(version_data.downloads_count)
|
|
+ " version " + version;
|
|
}
|
|
} else { var downloads = "invalid"; }
|
|
badgeData.text[1] = downloads;
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Gem owner stats
|
|
camp.route(/^\/gem\/u\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, "raphink"
|
|
var format = match[2];
|
|
var url = 'https://rubygems.org/api/v1/owners/' + user + '/gems.json';
|
|
var badgeData = getBadgeData('gems', data);
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(badgeData, format);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var count = data.length;
|
|
badgeData.colorscheme = floorCountColor(count, 10, 50, 100);
|
|
badgeData.text[1] = count;
|
|
sendBadge(format, badgeData);
|
|
} catch (e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
})
|
|
}));
|
|
|
|
|
|
// Gem ranking
|
|
camp.route(/^\/gem\/(rt|rd)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // either rt or rd
|
|
var repo = match[2]; // eg, "rspec-puppet-facts"
|
|
var format = match[3];
|
|
var url = 'http://bestgems.org/api/v1/gems/' + repo;
|
|
var totalRank = (info === 'rt');
|
|
var dailyRank = (info === 'rd');
|
|
if (totalRank) {
|
|
url += '/total_ranking.json';
|
|
} else if (dailyRank) {
|
|
url += '/daily_ranking.json';
|
|
}
|
|
var badgeData = getBadgeData('rank', data);
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(badgeData, format);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
if (totalRank) {
|
|
var rank = data[0].total_ranking;
|
|
} else if (dailyRank) {
|
|
var rank = data[0].daily_ranking;
|
|
}
|
|
var count = Math.floor(100000 / rank);
|
|
badgeData.colorscheme = floorCountColor(count, 10, 50, 100);
|
|
badgeData.text[1] = ordinalNumber(rank);
|
|
badgeData.text[1] += totalRank? '': ' daily';
|
|
sendBadge(format, badgeData);
|
|
} catch (e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
})
|
|
}));
|
|
|
|
// PyPI integration.
|
|
camp.route(/^\/pypi\/([^\/]+)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1];
|
|
var egg = match[2]; // eg, `gevent`, `Django`.
|
|
var format = match[3];
|
|
var apiUrl = 'https://pypi.python.org/pypi/' + egg + '/json';
|
|
var badgeData = getBadgeData('pypi', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
if (info.charAt(0) === 'd') {
|
|
badgeData.text[0] = getLabel('downloads', data);
|
|
switch (info.charAt(1)) {
|
|
case 'm':
|
|
var downloads = data.info.downloads.last_month;
|
|
badgeData.text[1] = metric(downloads) + '/month';
|
|
break;
|
|
case 'w':
|
|
var downloads = data.info.downloads.last_week;
|
|
badgeData.text[1] = metric(downloads) + '/week';
|
|
break;
|
|
case 'd':
|
|
var downloads = data.info.downloads.last_day;
|
|
badgeData.text[1] = metric(downloads) + '/day';
|
|
break;
|
|
}
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
sendBadge(format, badgeData);
|
|
} else if (info === 'v') {
|
|
var version = data.info.version;
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} else if (info === 'l') {
|
|
var license = data.info.license;
|
|
badgeData.text[0] = 'license';
|
|
if (license === null || license === 'UNKNOWN') {
|
|
badgeData.text[1] = 'Unknown';
|
|
} else {
|
|
badgeData.text[1] = license;
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} else if (info === 'wheel') {
|
|
var releases = data.releases[data.info.version];
|
|
var hasWheel = false;
|
|
for (var i = 0; i < releases.length; i++) {
|
|
if (releases[i].packagetype === 'wheel' ||
|
|
releases[i].packagetype === 'bdist_wheel') {
|
|
hasWheel = true;
|
|
break;
|
|
}
|
|
}
|
|
badgeData.text[0] = 'wheel';
|
|
badgeData.text[1] = hasWheel ? 'yes' : 'no';
|
|
badgeData.colorscheme = hasWheel ? 'brightgreen' : 'red';
|
|
sendBadge(format, badgeData);
|
|
} else if (info === 'format') {
|
|
var releases = data.releases[data.info.version];
|
|
var hasWheel = false;
|
|
var hasEgg = false;
|
|
for (var i = 0; i < releases.length; i++) {
|
|
if (releases[i].packagetype === 'wheel' ||
|
|
releases[i].packagetype === 'bdist_wheel') {
|
|
hasWheel = true;
|
|
break;
|
|
}
|
|
if (releases[i].packagetype === 'egg' ||
|
|
releases[i].packagetype === 'bdist_egg') {
|
|
hasEgg = true;
|
|
}
|
|
}
|
|
badgeData.text[0] = 'format';
|
|
if (hasWheel) {
|
|
badgeData.text[1] = 'wheel';
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (hasEgg) {
|
|
badgeData.text[1] = 'egg';
|
|
badgeData.colorscheme = 'red';
|
|
} else {
|
|
badgeData.text[1] = 'source';
|
|
badgeData.colorscheme = 'yellow';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} else if (info === 'pyversions') {
|
|
var versions = [];
|
|
var pattern = /^Programming Language \:\: Python \:\: ([\d\.]+)$/;
|
|
for (var i = 0; i < data.info.classifiers.length; i++) {
|
|
var matched = pattern.exec(data.info.classifiers[i]);
|
|
if (matched && matched[1]) {
|
|
versions.push(matched[1]);
|
|
}
|
|
}
|
|
// We only show v2 if eg. v2.4 does not appear.
|
|
// See https://github.com/badges/shields/pull/489 for more.
|
|
['2', '3'].forEach(function(version) {
|
|
var hasSubVersion = function(v) { return v.indexOf(version + '.') === 0; };
|
|
if (versions.some(hasSubVersion)) {
|
|
versions = versions.filter(function(v) { return v !== version; });
|
|
}
|
|
});
|
|
if (!versions.length) {
|
|
versions.push('not found');
|
|
}
|
|
badgeData.text[0] = 'python';
|
|
badgeData.text[1] = versions.sort().join(', ');
|
|
badgeData.colorscheme = 'blue';
|
|
sendBadge(format, badgeData);
|
|
} else if (info === 'implementation') {
|
|
var implementations = [];
|
|
var pattern = /^Programming Language \:\: Python \:\: Implementation \:\: (\S+)$/;
|
|
for (var i = 0; i < data.info.classifiers.length; i++) {
|
|
var matched = pattern.exec(data.info.classifiers[i]);
|
|
if (matched && matched[1]) {
|
|
implementations.push(matched[1].toLowerCase());
|
|
}
|
|
}
|
|
if (!implementations.length) {
|
|
implementations.push('cpython'); // assume CPython
|
|
}
|
|
badgeData.text[0] = 'implementation';
|
|
badgeData.text[1] = implementations.sort().join(', ');
|
|
badgeData.colorscheme = 'blue';
|
|
sendBadge(format, badgeData);
|
|
} else if (info === 'status') {
|
|
var pattern = /^Development Status \:\: ([1-7]) - (\S+)$/;
|
|
var statusColors = {
|
|
'1': 'red', '2': 'red', '3': 'red', '4': 'yellow',
|
|
'5': 'brightgreen', '6': 'brightgreen', '7': 'red'};
|
|
var statusCode = '1', statusText = 'unknown';
|
|
for (var i = 0; i < data.info.classifiers.length; i++) {
|
|
var matched = pattern.exec(data.info.classifiers[i]);
|
|
if (matched && matched[1] && matched[2]) {
|
|
statusCode = matched[1];
|
|
statusText = matched[2].toLowerCase().replace('-', '--');
|
|
if (statusText === 'production/stable') {
|
|
statusText = 'stable';
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
badgeData.text[0] = 'status';
|
|
badgeData.text[1] = statusText;
|
|
badgeData.colorscheme = statusColors[statusCode];
|
|
sendBadge(format, badgeData);
|
|
} else {
|
|
// That request is incorrect.
|
|
badgeData.text[1] = 'request unknown';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Dart's pub version integration.
|
|
camp.route(/^\/pub\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1]; // eg, "box2d"
|
|
var format = match[2];
|
|
var apiUrl = 'https://pub.dartlang.org/packages/' + userRepo + '.json';
|
|
var badgeData = getBadgeData('pub', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
// Grab the latest stable version, or an unstable
|
|
var versions = data.versions;
|
|
var version = latestVersion(versions);
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Hex.pm integration.
|
|
camp.route(/^\/hexpm\/([^\/]+)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1];
|
|
var repo = match[2]; // eg, `httpotion`.
|
|
var format = match[3];
|
|
var apiUrl = 'https://hex.pm/api/packages/' + repo;
|
|
var badgeData = getBadgeData('hex', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
if (info.charAt(0) === 'd') {
|
|
badgeData.text[0] = getLabel('downloads', data);
|
|
switch (info.charAt(1)) {
|
|
case 'w':
|
|
var downloads = data.downloads.week;
|
|
badgeData.text[1] = metric(downloads) + '/week';
|
|
break;
|
|
case 'd':
|
|
var downloads = data.downloads.day;
|
|
badgeData.text[1] = metric(downloads) + '/day';
|
|
break;
|
|
case 't':
|
|
var downloads = data.downloads.all;
|
|
badgeData.text[1] = metric(downloads);
|
|
break;
|
|
}
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
sendBadge(format, badgeData);
|
|
} else if (info === 'v') {
|
|
var version = data.releases[0].version;
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} else if (info == 'l') {
|
|
var license = (data.meta.licenses || []).join(', ');
|
|
badgeData.text[0] = 'license';
|
|
if ((data.meta.licenses || []).length > 1) badgeData.text[0] += 's';
|
|
if (license == '') {
|
|
badgeData.text[1] = 'Unknown';
|
|
} else {
|
|
badgeData.text[1] = license;
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
}
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Coveralls integration.
|
|
camp.route(/^\/coveralls\/([^\/]+\/[^\/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1]; // eg, `jekyll/jekyll`.
|
|
var branch = match[2];
|
|
var format = match[3];
|
|
var apiUrl = {
|
|
url: 'http://badge.coveralls.io/repos/' + userRepo + '/badge.png',
|
|
followRedirect: false,
|
|
method: 'HEAD',
|
|
};
|
|
if (branch) {
|
|
apiUrl.url += '?branch=' + branch;
|
|
}
|
|
var badgeData = getBadgeData('coverage', data);
|
|
request(apiUrl, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
// We should get a 302. Look inside the Location header.
|
|
var buffer = res.headers.location;
|
|
if (!buffer) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var score = buffer.split('_')[1].split('.')[0];
|
|
var percentage = parseInt(score);
|
|
if (percentage !== percentage) {
|
|
// It is NaN, treat it as unknown.
|
|
badgeData.text[1] = 'unknown';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
badgeData.text[1] = score + '%';
|
|
badgeData.colorscheme = coveragePercentageColor(percentage);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'malformed';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
}).on('error', function(e) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
});
|
|
}));
|
|
|
|
// Codecov integration.
|
|
camp.route(/^\/codecov\/c\/(?:token\/(\w+))?[+\/]?([^\/]+\/[^\/]+\/[^\/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var token = match[1];
|
|
var userRepo = match[2]; // eg, `github/codecov/example-python`.
|
|
var branch = match[3];
|
|
var format = match[4];
|
|
var apiUrl = {
|
|
url: 'https://codecov.io/' + userRepo + '/coverage.svg',
|
|
followRedirect: false,
|
|
method: 'HEAD',
|
|
};
|
|
// Query Params
|
|
queryParams = {};
|
|
if (branch) {
|
|
queryParams.branch = branch;
|
|
}
|
|
if (token) {
|
|
queryParams.token = token;
|
|
}
|
|
apiUrl.url += '?' + querystring.stringify(queryParams);
|
|
var badgeData = getBadgeData('coverage', data);
|
|
request(apiUrl, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
// X-Coverage header returns: n/a if 404/401 else range(0, 100).
|
|
// It can also yield a 302 Found with an "unknown" X-Coverage.
|
|
var coverage = res.headers['x-coverage'];
|
|
// Is `coverage` NaN when converted to number?
|
|
if (+coverage !== +coverage) {
|
|
badgeData.text[1] = 'unknown';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
badgeData.text[1] = coverage + '%';
|
|
badgeData.colorscheme = coveragePercentageColor(coverage);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'malformed';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
}).on('error', function(e) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
});
|
|
}));
|
|
|
|
// Code Climate coverage integration
|
|
camp.route(/^\/codeclimate\/coverage\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1]; // eg, `github/triAGENS/ashikawa-core`.
|
|
var format = match[2];
|
|
var options = {
|
|
method: 'HEAD',
|
|
uri: 'https://codeclimate.com/' + userRepo + '/coverage.png',
|
|
};
|
|
var badgeData = getBadgeData('coverage', data);
|
|
request(options, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var score = res.headers['content-disposition']
|
|
.match(/filename=".*coverage_(.+)\.png"/)[1];
|
|
if (!score) {
|
|
badgeData.text[1] = 'malformed';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
var percentage = parseInt(score);
|
|
if (percentage !== percentage) {
|
|
// It is NaN, treat it as unknown.
|
|
badgeData.text[1] = 'unknown';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
badgeData.text[1] = score + '%';
|
|
badgeData.colorscheme = coveragePercentageColor(percentage);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'not found';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Code Climate issues integration
|
|
camp.route(/^\/codeclimate\/issues\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1]; // eg, `github/me-and/mdf`.
|
|
var format = match[2];
|
|
var options = 'https://codeclimate.com/' + userRepo + '/badges/issue_count.svg'
|
|
var badgeData = getBadgeData('issues', data);
|
|
request(options, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var count = buffer.match(/>([0-9]+) issues?/)[1];
|
|
if (!count) {
|
|
badgeData.text[1] = 'malformed';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
badgeData.text[1] = count;
|
|
if (count == 0) {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (count < 5) {
|
|
badgeData.colorscheme = 'green';
|
|
} else if (count < 10) {
|
|
badgeData.colorscheme = 'yellowgreen';
|
|
} else if (count < 20) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Code Climate integration
|
|
camp.route(/^\/codeclimate\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1]; // eg, `github/kabisaict/flow`.
|
|
var format = match[2];
|
|
var options = {
|
|
method: 'HEAD',
|
|
uri: 'https://codeclimate.com/' + userRepo + '.png',
|
|
};
|
|
var badgeData = getBadgeData('code climate', data);
|
|
request(options, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var statusMatch = res.headers['content-disposition']
|
|
.match(/filename=".*code_climate-(.+)\.png"/);
|
|
if (!statusMatch) {
|
|
badgeData.text[1] = 'unknown';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
var state = statusMatch[1].replace('-', '.');
|
|
var score = +state;
|
|
badgeData.text[1] = state;
|
|
if (score == 4) {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (score > 3) {
|
|
badgeData.colorscheme = 'green';
|
|
} else if (score > 2) {
|
|
badgeData.colorscheme = 'yellowgreen';
|
|
} else if (score > 1) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'not found';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Scrutinizer coverage integration.
|
|
camp.route(/^\/scrutinizer\/coverage\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, g/phpmyadmin/phpmyadmin
|
|
var format = match[2];
|
|
// The repo may contain a branch, which would be unsuitable.
|
|
var repoParts = repo.split('/');
|
|
var branch = null;
|
|
// Normally, there are 2 slashes in `repo` when the branch isn't specified.
|
|
var slashesInRepo = 2;
|
|
if (repoParts[0] === 'gp') { slashesInRepo = 1; }
|
|
if ((repoParts.length - 1) > slashesInRepo) {
|
|
branch = repoParts.slice(slashesInRepo + 1).join('/');
|
|
repo = repoParts.slice(0, slashesInRepo + 1).join('/');
|
|
}
|
|
var apiUrl = 'https://scrutinizer-ci.com/api/repositories/' + repo;
|
|
var badgeData = getBadgeData('coverage', data);
|
|
request(apiUrl, {}, function(err, res, buffer) {
|
|
if (err !== null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
// Which branch are we dealing with?
|
|
if (branch === null) { branch = data.default_branch; }
|
|
var percentage = data.applications[branch].index._embedded
|
|
.project.metric_values['scrutinizer.test_coverage'] * 100;
|
|
badgeData.text[1] = percentage.toFixed(0) + '%';
|
|
badgeData.colorscheme = coveragePercentageColor(percentage);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Scrutinizer build integration.
|
|
camp.route(/^\/scrutinizer\/build\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, g/phpmyadmin/phpmyadmin
|
|
var format = match[2];
|
|
// The repo may contain a branch, which would be unsuitable.
|
|
var repoParts = repo.split('/');
|
|
var branch = null;
|
|
// Normally, there are 2 slashes in `repo` when the branch isn't specified.
|
|
var slashesInRepo = 2;
|
|
if (repoParts[0] === 'gp') { slashesInRepo = 1; }
|
|
if ((repoParts.length - 1) > slashesInRepo) {
|
|
branch = repoParts.slice(slashesInRepo + 1).join('/');
|
|
repo = repoParts.slice(0, slashesInRepo + 1).join('/');
|
|
}
|
|
var apiUrl = 'https://scrutinizer-ci.com/api/repositories/' + repo;
|
|
var badgeData = getBadgeData('build', data);
|
|
request(apiUrl, {}, function(err, res, buffer) {
|
|
if (err !== null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
// Which branch are we dealing with?
|
|
if (branch === null) { branch = data.default_branch; }
|
|
var res = data.applications[branch].build_status.status;
|
|
badgeData.text[1] = res;
|
|
if (res === 'passed') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'passing';
|
|
} else if (res === 'failed' || res === 'error') {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (res === 'pending') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else if (res === 'unknown') {
|
|
badgeData.colorscheme = 'gray';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Scrutinizer integration.
|
|
camp.route(/^\/scrutinizer\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, g/phpmyadmin/phpmyadmin
|
|
var format = match[2];
|
|
// The repo may contain a branch, which would be unsuitable.
|
|
var repoParts = repo.split('/');
|
|
var branch = null;
|
|
// Normally, there are 2 slashes in `repo` when the branch isn't specified.
|
|
var slashesInRepo = 2;
|
|
if (repoParts[0] === 'gp') { slashesInRepo = 1; }
|
|
if ((repoParts.length - 1) > slashesInRepo) {
|
|
branch = repoParts.slice(slashesInRepo + 1).join('/');
|
|
repo = repoParts.slice(0, slashesInRepo + 1).join('/');
|
|
}
|
|
var apiUrl = 'https://scrutinizer-ci.com/api/repositories/' + repo;
|
|
var badgeData = getBadgeData('code quality', data);
|
|
request(apiUrl, {}, function(err, res, buffer) {
|
|
if (err !== null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
// Which branch are we dealing with?
|
|
if (branch === null) { branch = data.default_branch; }
|
|
var score = data.applications[branch].index._embedded
|
|
.project.metric_values['scrutinizer.quality'];
|
|
score = Math.round(score * 100) / 100;
|
|
badgeData.text[1] = score;
|
|
badgeData.colorscheme = 'blue';
|
|
if (score > 9) {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (score > 7) {
|
|
badgeData.colorscheme = 'green';
|
|
} else if (score > 5) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (score > 4) {
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// David integration
|
|
camp.route(/^\/david\/(dev\/|optional\/|peer\/)?(.+?)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var dev = match[1];
|
|
if (dev != null) { dev = dev.slice(0, -1); } // 'dev', 'optional' or 'peer'.
|
|
// eg, `strongloop/express`, `webcomponents/generator-element`.
|
|
var userRepo = match[2];
|
|
var format = match[3];
|
|
var options = 'https://david-dm.org/' + userRepo + '/'
|
|
+ (dev ? (dev + '-') : '') + 'info.json';
|
|
var badgeData = getBadgeData( (dev? (dev+'D') :'d') + 'ependencies', data);
|
|
request(options, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var status = data.status;
|
|
if (status === 'insecure') {
|
|
badgeData.colorscheme = 'red';
|
|
status = 'insecure';
|
|
} else if (status === 'notsouptodate') {
|
|
badgeData.colorscheme = 'yellow';
|
|
status = 'up-to-date';
|
|
} else if (status === 'outofdate') {
|
|
badgeData.colorscheme = 'red';
|
|
status = 'out-of-date';
|
|
} else if (status === 'uptodate') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
status = 'up-to-date';
|
|
} else if (status === 'none') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
}
|
|
badgeData.text[1] = status;
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Gemnasium integration
|
|
camp.route(/^\/gemnasium\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1]; // eg, `jekyll/jekyll`.
|
|
var format = match[2];
|
|
var options = 'https://gemnasium.com/' + userRepo + '.svg';
|
|
var badgeData = getBadgeData('dependencies', data);
|
|
request(options, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var nameMatch = buffer.match(/(devD|d)ependencies/)[0];
|
|
var statusMatch = buffer.match(/'14'>(.+)<\/text>\s*<\/g>/)[1];
|
|
badgeData.text[0] = data.label || nameMatch;
|
|
badgeData.text[1] = statusMatch;
|
|
if (statusMatch === 'up-to-date') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (statusMatch === 'out-of-date') {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (statusMatch === 'up-to-date') {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (statusMatch === 'none') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else {
|
|
badgeData.text[1] = 'undefined';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
});
|
|
}));
|
|
|
|
// VersionEye integration
|
|
camp.route(/^\/versioneye\/d\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1]; // eg, `ruby/rails`.
|
|
var format = match[2];
|
|
var url = 'https://www.versioneye.com/' + userRepo + '/badge.svg';
|
|
var badgeData = getBadgeData('dependencies', data);
|
|
fetchFromSvg(request, url, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
badgeData.text[1] = res;
|
|
if (res === 'up to date') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (res === 'none') {
|
|
badgeData.colorscheme = 'green';
|
|
} else if (res === 'out of date') {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Codacy integration
|
|
camp.route(/^\/codacy\/([^\/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var projectId = match[1]; // eg. e27821fb6289410b8f58338c7e0bc686
|
|
var branch = match[2];
|
|
var format = match[3];
|
|
|
|
queryParams = {};
|
|
if (branch) {
|
|
queryParams.branch = branch;
|
|
}
|
|
var query = querystring.stringify(queryParams);
|
|
var url = 'https://www.codacy.com/project/badge/' + projectId + '?' + query;
|
|
var badgeData = getBadgeData('code quality', data);
|
|
fetchFromSvg(request, url, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
badgeData.text[1] = res;
|
|
if (res === 'A') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (res === 'B') {
|
|
badgeData.colorscheme = 'green';
|
|
} else if (res === 'C') {
|
|
badgeData.colorscheme = 'yellowgreen';
|
|
} else if (res === 'D') {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (res === 'E') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else if (res === 'F') {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (res === 'X') {
|
|
badgeData.text[1] = 'invalid';
|
|
badgeData.colorscheme = 'lightgrey';
|
|
} else {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Hackage version integration.
|
|
camp.route(/^\/hackage\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, `lens`.
|
|
var format = match[2];
|
|
var apiUrl = 'https://hackage.haskell.org/package/' + repo + '/' + repo + '.cabal';
|
|
var badgeData = getBadgeData('hackage', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var lines = buffer.split("\n");
|
|
var versionLines = lines.filter(function(e) {
|
|
return (/^version:/i).test(e) === true;
|
|
});
|
|
// We don't have to check length of versionLines, because if we throw,
|
|
// we'll render the 'invalid' badge below, which is the correct thing
|
|
// to do.
|
|
var version = versionLines[0].replace(/\s+/, '').split(/:/)[1];
|
|
badgeData.text[1] = 'v' + version;
|
|
if (version[0] === '0') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Hackage dependencies version integration.
|
|
camp.route(/^\/hackage-deps\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, `lens`.
|
|
var format = match[2];
|
|
var apiUrl = 'http://packdeps.haskellers.com/feed/' + repo;
|
|
var badgeData = getBadgeData('hackage-deps', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var outdatedStr = "Outdated dependencies for " + repo + " ";
|
|
if (buffer.indexOf(outdatedStr) >= 0) {
|
|
badgeData.text[1] = 'outdated';
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.text[1] = 'up-to-date';
|
|
badgeData.colorscheme = 'brightgreen';
|
|
}
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
});
|
|
}));
|
|
|
|
// CocoaPods version integration.
|
|
camp.route(/^\/cocoapods\/(v|p|l)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var type = match[1];
|
|
var spec = match[2]; // eg, AFNetworking
|
|
var format = match[3];
|
|
var apiUrl = 'https://trunk.cocoapods.org/api/v1/pods/' + spec + '/specs/latest';
|
|
var badgeData = getBadgeData('pod', data);
|
|
badgeData.colorscheme = null;
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var version = data.version;
|
|
var license;
|
|
if (typeof data.license === 'string') {
|
|
license = data.license;
|
|
} else { license = data.license.type; }
|
|
|
|
var platforms = Object.keys(data.platforms || {
|
|
'ios' : '5.0',
|
|
'osx' : '10.7'
|
|
}).join(' | ');
|
|
version = version.replace(/^v/, "");
|
|
if (type === 'v') {
|
|
badgeData.text[1] = version;
|
|
if (/^\d/.test(badgeData.text[1])) {
|
|
badgeData.text[1] = 'v' + version;
|
|
}
|
|
badgeData.colorB = '#5BA7E9';
|
|
} else if (type === 'p') {
|
|
badgeData.text[0] = 'platform';
|
|
badgeData.text[1] = platforms;
|
|
badgeData.colorB = '#989898';
|
|
} else if (type === 'l') {
|
|
badgeData.text[0] = 'license';
|
|
badgeData.text[1] = license;
|
|
badgeData.colorB = '#373737';
|
|
}
|
|
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
camp.route(/^\/cocoapods\/metrics\/doc-percent\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var spec = match[1]; // eg, AFNetworking
|
|
var format = match[2];
|
|
var apiUrl = 'http://metrics.cocoapods.org/api/v1/pods/' + spec;
|
|
var badgeData = getBadgeData('pod', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var percentage = data.cocoadocs.doc_percent;
|
|
badgeData.colorscheme = coveragePercentageColor(percentage);
|
|
badgeData.text[0] = 'docs';
|
|
badgeData.text[1] = percentage + '%'
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub tag integration.
|
|
camp.route(/^\/github\/tag\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, strongloop/express
|
|
var repo = match[2];
|
|
var format = match[3];
|
|
var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/tags';
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
apiUrl += '?client_id=' + serverSecrets.gh_client_id
|
|
+ '&client_secret=' + serverSecrets.gh_client_secret;
|
|
}
|
|
var badgeData = getBadgeData('tag', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
}
|
|
// A special User-Agent is required:
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
request(apiUrl, { headers: githubHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
var data = JSON.parse(buffer);
|
|
var versions = data.map(function(e) { return e.name; });
|
|
var tag = latestVersion(versions);
|
|
var vdata = versionColor(tag);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'none';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub release integration.
|
|
camp.route(/^\/github\/release\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, qubyte/rubidium
|
|
var repo = match[2];
|
|
var format = match[3];
|
|
var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/releases/latest';
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
apiUrl += '?client_id=' + serverSecrets.gh_client_id
|
|
+ '&client_secret=' + serverSecrets.gh_client_secret;
|
|
}
|
|
var badgeData = getBadgeData('release', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
}
|
|
// A special User-Agent is required:
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
request(apiUrl, { headers: githubHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
var data = JSON.parse(buffer);
|
|
var version = data.tag_name;
|
|
var prerelease = data.prerelease;
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = prerelease ? 'orange' : 'blue';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'none';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub commits since integration.
|
|
camp.route(/^\/github\/commits-since\/([^\/]+)\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, SubtitleEdit
|
|
var repo = match[2]; // eg, subtitleedit
|
|
var version = match[3]; // eg, 3.4.7
|
|
var format = match[4];
|
|
var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/compare/' + version + '...master';
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
apiUrl += '?client_id=' + serverSecrets.gh_client_id
|
|
+ '&client_secret=' + serverSecrets.gh_client_secret;
|
|
}
|
|
var badgeData = getBadgeData('commits since ' + version, data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
}
|
|
// A special User-Agent is required:
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
request(apiUrl, { headers: githubHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
var data = JSON.parse(buffer);
|
|
badgeData.text[1] = data.ahead_by;
|
|
badgeData.colorscheme = 'blue';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'none';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub release-download-count integration.
|
|
camp.route(/^\/github\/downloads\/([^\/]+)\/([^\/]+)(\/[^\/]+)?\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, qubyte/rubidium
|
|
var repo = match[2];
|
|
|
|
var tag = match[3]; //null for all releases
|
|
var asset_name = match[4].toLowerCase(); // eg. total, atom-amd64.deb, atom.x86_64.rpm
|
|
var format = match[5];
|
|
|
|
if (tag) { tag = tag.slice(1); }
|
|
|
|
var total = true;
|
|
if (tag) {
|
|
total = false;
|
|
}
|
|
|
|
var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/releases';
|
|
if (!total) {
|
|
var release_path = tag !== 'latest' ? 'tags/' + tag : 'latest';
|
|
apiUrl = apiUrl + '/' + release_path;
|
|
}
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
apiUrl += '?client_id=' + serverSecrets.gh_client_id
|
|
+ '&client_secret=' + serverSecrets.gh_client_secret;
|
|
}
|
|
var badgeData = getBadgeData('downloads', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
}
|
|
// A special User-Agent is required:
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
request(apiUrl, { headers: githubHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
return sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
var data = JSON.parse(buffer);
|
|
var downloads = 0;
|
|
|
|
var label;
|
|
if (total) {
|
|
data.forEach(function (tagData) {
|
|
tagData.assets.forEach(function (asset) {
|
|
if (asset_name === 'total' || asset_name === asset.name.toLowerCase()) {
|
|
downloads += asset.download_count;
|
|
}
|
|
});
|
|
});
|
|
|
|
label = 'total';
|
|
if (asset_name !== 'total') {
|
|
label += ' ' + '[' + asset_name + ']';
|
|
}
|
|
} else {
|
|
data.assets.forEach(function (asset) {
|
|
if (asset_name === 'total' || asset_name === asset.name.toLowerCase()) {
|
|
downloads += asset.download_count;
|
|
}
|
|
});
|
|
|
|
label = tag !== 'latest' ? tag : '';
|
|
if (asset_name !== 'total') {
|
|
label += ' ' + '[' + asset_name + ']';
|
|
}
|
|
}
|
|
badgeData.text[1] = metric(downloads) + ' ' + label;
|
|
badgeData.colorscheme = 'brightgreen';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'none';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub issues integration.
|
|
camp.route(/^\/github\/issues(-raw)?\/([^\/]+)\/([^\/]+)\/?([^\/]+)?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var isRaw = !!match[1];
|
|
var user = match[2]; // eg, badges
|
|
var repo = match[3]; // eg, shields
|
|
var ghLabel = match[4]; // eg, website
|
|
var format = match[5];
|
|
var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo;
|
|
var issuesApi = false; // Are we using the issues API instead of the repo one?
|
|
var query = {};
|
|
if (ghLabel !== undefined) {
|
|
apiUrl += '/issues';
|
|
query.labels = ghLabel;
|
|
issuesApi = true;
|
|
}
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
query.client_id = serverSecrets.gh_client_id;
|
|
query.client_secret = serverSecrets.gh_client_secret;
|
|
}
|
|
apiUrl += '?' + querystring.stringify(query);
|
|
|
|
var badgeData = getBadgeData('issues', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
}
|
|
// A special User-Agent is required:
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
request(apiUrl, { headers: githubHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
var data = JSON.parse(buffer);
|
|
if (issuesApi) {
|
|
var issues = data.length;
|
|
} else {
|
|
var issues = data.open_issues_count;
|
|
}
|
|
badgeData.text[1] = issues + (isRaw? '': ' open');
|
|
badgeData.colorscheme = issues ? 'yellow' : 'brightgreen';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub forks integration.
|
|
camp.route(/^\/github\/forks\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, qubyte/rubidium
|
|
var repo = match[2];
|
|
var format = match[3];
|
|
var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo;
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
apiUrl += '?client_id=' + serverSecrets.gh_client_id
|
|
+ '&client_secret=' + serverSecrets.gh_client_secret;
|
|
}
|
|
var badgeData = getBadgeData('forks', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
badgeData.links = [
|
|
'https://github.com/' + user + '/' + repo + '/fork',
|
|
'https://github.com/' + user + '/' + repo + '/network',
|
|
];
|
|
}
|
|
// A special User-Agent is required:
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
request(apiUrl, { headers: githubHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
var data = JSON.parse(buffer);
|
|
var forks = data.forks_count;
|
|
badgeData.text[1] = forks;
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#4183C4';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub stars integration.
|
|
camp.route(/^\/github\/stars\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, qubyte/rubidium
|
|
var repo = match[2];
|
|
var format = match[3];
|
|
var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo;
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
apiUrl += '?client_id=' + serverSecrets.gh_client_id
|
|
+ '&client_secret=' + serverSecrets.gh_client_secret;
|
|
}
|
|
var badgeData = getBadgeData('stars', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
badgeData.links = [
|
|
'https://github.com/' + user + '/' + repo,
|
|
'https://github.com/' + user + '/' + repo + '/stargazers',
|
|
];
|
|
}
|
|
// A special User-Agent is required:
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
request(apiUrl, { headers: githubHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
badgeData.text[1] = JSON.parse(buffer).stargazers_count;
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#4183C4';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub watchers integration.
|
|
camp.route(/^\/github\/watchers\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, qubyte/rubidium
|
|
var repo = match[2];
|
|
var format = match[3];
|
|
var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo;
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
apiUrl += '?client_id=' + serverSecrets.gh_client_id
|
|
+ '&client_secret=' + serverSecrets.gh_client_secret;
|
|
}
|
|
var badgeData = getBadgeData('watchers', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
badgeData.links = [
|
|
'https://github.com/' + user + '/' + repo,
|
|
'https://github.com/' + user + '/' + repo + '/watchers',
|
|
];
|
|
}
|
|
// A special User-Agent is required:
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
request(apiUrl, { headers: githubHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
badgeData.text[1] = JSON.parse(buffer).subscribers_count;
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#4183C4';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub user followers integration.
|
|
camp.route(/^\/github\/followers\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, qubyte
|
|
var format = match[2];
|
|
var apiUrl = 'https://api.github.com/users/' + user;
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
apiUrl += '?client_id=' + serverSecrets.gh_client_id
|
|
+ '&client_secret=' + serverSecrets.gh_client_secret;
|
|
}
|
|
var badgeData = getBadgeData('followers', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
}
|
|
// A special User-Agent is required:
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
request(apiUrl, { headers: githubHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
badgeData.text[1] = JSON.parse(buffer).followers;
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#4183C4';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// GitHub license integration.
|
|
camp.route(/^\/github\/license\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, mashape
|
|
var repo = match[2]; // eg, apistatus
|
|
var format = match[3];
|
|
var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo;
|
|
var badgeData = getBadgeData('license', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.github;
|
|
}
|
|
// Using our OAuth App secret grants us 5000 req/hour
|
|
// instead of the standard 60 req/hour.
|
|
if (serverSecrets) {
|
|
apiUrl += '?client_id=' + serverSecrets.gh_client_id
|
|
+ '&client_secret=' + serverSecrets.gh_client_secret;
|
|
}
|
|
// Custom user-agent and accept headers are required
|
|
// http://developer.github.com/v3/#user-agent-required
|
|
// https://developer.github.com/v3/licenses/
|
|
var customHeaders = {
|
|
'User-Agent': 'Shields.io',
|
|
'Accept': 'application/vnd.github.drax-preview+json'
|
|
};
|
|
request(apiUrl, { headers: customHeaders }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if ((+res.headers['x-ratelimit-remaining']) === 0) {
|
|
return; // Hope for the best in the cache.
|
|
} else if (res.statusCode === 404) {
|
|
badgeData.text[1] = 'repo not found';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
var body = JSON.parse(buffer);
|
|
if (body.license != null) {
|
|
badgeData.text[1] = body.license.name;
|
|
badgeData.colorscheme = 'blue';
|
|
sendBadge(format, badgeData);
|
|
} else {
|
|
badgeData.text[1] = 'unknown license';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Bitbucket issues integration.
|
|
camp.route(/^\/bitbucket\/issues(-raw)?\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var isRaw = !!match[1];
|
|
var user = match[2]; // eg, atlassian
|
|
var repo = match[3]; // eg, python-bitbucket
|
|
var format = match[4];
|
|
var apiUrl = 'https://bitbucket.org/api/1.0/repositories/' + user + '/' + repo
|
|
+ '/issues/?limit=0&status=new&status=open';
|
|
|
|
var badgeData = getBadgeData('issues', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var issues = data.count;
|
|
badgeData.text[1] = issues + (isRaw? '': ' open');
|
|
badgeData.colorscheme = issues ? 'yellow' : 'brightgreen';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Chef cookbook integration.
|
|
camp.route(/^\/cookbook\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var cookbook = match[1]; // eg, chef-sugar
|
|
var format = match[2];
|
|
var apiUrl = 'https://supermarket.getchef.com/api/v1/cookbooks/' + cookbook + '/versions/latest';
|
|
var badgeData = getBadgeData('cookbook', data);
|
|
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var version = data.version;
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
function mapNugetFeedv2(pattern, offset, getInfo) {
|
|
var vRegex = new RegExp('^\\/' + pattern + '\\/v\\/(.*)\\.(svg|png|gif|jpg|json)$');
|
|
var vPreRegex = new RegExp('^\\/' + pattern + '\\/vpre\\/(.*)\\.(svg|png|gif|jpg|json)$');
|
|
var dtRegex = new RegExp('^\\/' + pattern + '\\/dt\\/(.*)\\.(svg|png|gif|jpg|json)$');
|
|
|
|
function getNugetPackage(apiUrl, id, includePre, request, done) {
|
|
var filter = includePre ?
|
|
'Id eq \'' + id + '\' and IsAbsoluteLatestVersion eq true' :
|
|
'Id eq \'' + id + '\' and IsLatestVersion eq true';
|
|
var reqUrl = apiUrl + '/Packages()?$filter=' + encodeURIComponent(filter);
|
|
request(reqUrl,
|
|
{ headers: { 'Accept': 'application/atom+json,application/json' } },
|
|
function(err, res, buffer) {
|
|
if (err != null) {
|
|
done(err);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var result = data.d.results[0];
|
|
if (result == null) {
|
|
if (includePre === null) {
|
|
getNugetPackage(apiUrl, id, true, request, done);
|
|
} else {
|
|
done(new Error('Package not found in feed'));
|
|
}
|
|
} else {
|
|
done(null, result);
|
|
}
|
|
}
|
|
catch (e) {
|
|
done(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
camp.route(vRegex,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = getInfo(match);
|
|
var site = info.site; // eg, `Chocolatey`, or `YoloDev`
|
|
var repo = match[offset + 1]; // eg, `Nuget.Core`.
|
|
var format = match[offset + 2];
|
|
var apiUrl = info.feed;
|
|
var badgeData = getBadgeData(site, data);
|
|
getNugetPackage(apiUrl, repo, null, request, function(err, data) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var version = data.NormalizedVersion || data.Version;
|
|
badgeData.text[1] = 'v' + version;
|
|
if (version.indexOf('-') !== -1) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (version[0] === '0') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
camp.route(vPreRegex,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = getInfo(match);
|
|
var site = info.site; // eg, `Chocolatey`, or `YoloDev`
|
|
var repo = match[offset + 1]; // eg, `Nuget.Core`.
|
|
var format = match[offset + 2];
|
|
var apiUrl = info.feed;
|
|
var badgeData = getBadgeData(site, data);
|
|
getNugetPackage(apiUrl, repo, true, request, function(err, data) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var version = data.NormalizedVersion || data.Version;
|
|
badgeData.text[1] = 'v' + version;
|
|
if (version.indexOf('-') !== -1) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (version[0] === '0') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
camp.route(dtRegex,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = getInfo(match);
|
|
var site = info.site; // eg, `Chocolatey`, or `YoloDev`
|
|
var repo = match[offset+ 1]; // eg, `Nuget.Core`.
|
|
var format = match[offset + 2];
|
|
var apiUrl = info.feed;
|
|
var badgeData = getBadgeData(site, data);
|
|
getNugetPackage(apiUrl, repo, null, request, function(err, data) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var downloads = data.DownloadCount;
|
|
badgeData.text[1] = metric(downloads);
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
}
|
|
|
|
function mapNugetFeed(pattern, offset, getInfo) {
|
|
var vRegex = new RegExp('^\\/' + pattern + '\\/v\\/(.*)\\.(svg|png|gif|jpg|json)$');
|
|
var vPreRegex = new RegExp('^\\/' + pattern + '\\/vpre\\/(.*)\\.(svg|png|gif|jpg|json)$');
|
|
|
|
function getNugetVersion(apiUrl, id, includePre, request, done) {
|
|
var reqUrl = apiUrl + '/flatcontainer/' + id.toLowerCase() + '/index.json';
|
|
request(reqUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
done(err);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var versions = data.versions;
|
|
if (!includePre) {
|
|
// Remove prerelease versions.
|
|
filteredVersions = versions.filter(function(version) {
|
|
return !/-/.test(version);
|
|
});
|
|
if (filteredVersions.length > 0) {
|
|
versions = filteredVersions;
|
|
}
|
|
}
|
|
var lastVersion = versions[versions.length - 1];
|
|
done(null, lastVersion);
|
|
} catch (e) { done(e); }
|
|
});
|
|
}
|
|
|
|
camp.route(vRegex,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = getInfo(match);
|
|
var site = info.site; // eg, `Chocolatey`, or `YoloDev`
|
|
var repo = match[offset + 1]; // eg, `Nuget.Core`.
|
|
var format = match[offset + 2];
|
|
var apiUrl = info.feed;
|
|
var badgeData = getBadgeData(site, data);
|
|
getNugetVersion(apiUrl, repo, false, request, function(err, version) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
badgeData.text[1] = 'v' + version;
|
|
if (version.indexOf('-') !== -1) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (version[0] === '0') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
camp.route(vPreRegex,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = getInfo(match);
|
|
var site = info.site; // eg, `Chocolatey`, or `YoloDev`
|
|
var repo = match[offset + 1]; // eg, `Nuget.Core`.
|
|
var format = match[offset + 2];
|
|
var apiUrl = info.feed;
|
|
var badgeData = getBadgeData(site, data);
|
|
getNugetVersion(apiUrl, repo, true, request, function(err, version) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
badgeData.text[1] = 'v' + version;
|
|
if (version.indexOf('-') !== -1) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (version[0] === '0') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
}
|
|
|
|
// Chocolatey
|
|
mapNugetFeedv2('chocolatey', 0, function(match) {
|
|
return {
|
|
site: 'chocolatey',
|
|
feed: 'https://www.chocolatey.org/api/v2'
|
|
};
|
|
});
|
|
|
|
// NuGet
|
|
mapNugetFeed('nuget', 0, function(match) {
|
|
return {
|
|
site: 'nuget',
|
|
feed: 'https://api.nuget.org/v3'
|
|
};
|
|
});
|
|
|
|
// MyGet
|
|
mapNugetFeed('(.+\\.)?myget\\/(.*)', 2, function(match) {
|
|
var tenant = match[1] || 'www.'; // eg. dotnet
|
|
var feed = match[2];
|
|
return {
|
|
site: feed,
|
|
feed: 'https://' + tenant + 'myget.org/F/' + feed + '/api/v3'
|
|
};
|
|
});
|
|
|
|
// Puppet Forge modules
|
|
camp.route(/^\/puppetforge\/([^\/]+)\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // either `v`, `dt`, `e` or `f`
|
|
var user = match[2];
|
|
var module = match[3];
|
|
var format = match[4];
|
|
var options = {
|
|
json: true,
|
|
uri: 'https://forgeapi.puppetlabs.com/v3/modules/' + user + '-' + module
|
|
};
|
|
var badgeData = getBadgeData('puppetforge', data);
|
|
request(options, function dealWithData(err, res, json) {
|
|
if (err != null || (json.length !== undefined && json.length === 0)) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if (info === 'v') {
|
|
if (json.current_release) {
|
|
var vdata = versionColor(json.current_release.version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
} else {
|
|
badgeData.text[1] = 'none';
|
|
badgeData.colorscheme = 'lightgrey';
|
|
}
|
|
} else if (info === 'dt') {
|
|
var total = json.downloads;
|
|
badgeData.colorscheme = downloadCountColor(total);
|
|
badgeData.text[0] = 'downloads';
|
|
badgeData.text[1] = metric(total);
|
|
} else if (info === 'e') {
|
|
var endorsement = json.endorsement;
|
|
if (endorsement === 'approved') {
|
|
badgeData.colorscheme = 'green';
|
|
} else if (endorsement === 'supported') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
badgeData.text[0] = 'endorsement';
|
|
if (endorsement != null) {
|
|
badgeData.text[1] = endorsement;
|
|
} else {
|
|
badgeData.text[1] = 'none';
|
|
}
|
|
} else if (info === 'f') {
|
|
var feedback = json.feedback_score;
|
|
badgeData.text[0] = 'score';
|
|
if (feedback != null) {
|
|
badgeData.text[1] = feedback + '%';
|
|
badgeData.colorscheme = coveragePercentageColor(feedback);
|
|
} else {
|
|
badgeData.text[1] = 'unknown';
|
|
badgeData.colorscheme = 'lightgrey';
|
|
}
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Puppet Forge users
|
|
camp.route(/^\/puppetforge\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // either `rc` or `mc`
|
|
var user = match[2];
|
|
var format = match[3];
|
|
var options = {
|
|
json: true,
|
|
uri: 'https://forgeapi.puppetlabs.com/v3/users/' + user
|
|
};
|
|
var badgeData = getBadgeData('puppetforge', data);
|
|
request(options, function dealWithData(err, res, json) {
|
|
if (err != null || (json.length !== undefined && json.length === 0)) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if (info === 'rc') {
|
|
var releases = json.release_count;
|
|
badgeData.colorscheme = floorCountColor(releases, 10, 50, 100);
|
|
badgeData.text[0] = 'releases';
|
|
badgeData.text[1] = metric(releases);
|
|
} else if (info === 'mc') {
|
|
var modules = json.module_count;
|
|
badgeData.colorscheme = floorCountColor(modules, 5, 10, 50);
|
|
badgeData.text[0] = 'modules';
|
|
badgeData.text[1] = metric(modules);
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Jenkins build status integration
|
|
camp.route(/^\/jenkins(-ci)?\/s\/(http(s)?)\/((?:[^\/]+)(?:\/.+?)?)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var scheme = match[2]; // http(s)
|
|
var host = match[4]; // jenkins.qa.ubuntu.com
|
|
var job = match[5]; // precise-desktop-amd64_default
|
|
var format = match[6];
|
|
var options = {
|
|
json: true,
|
|
uri: scheme + '://' + host + '/job/' + job + '/api/json?tree=color'
|
|
};
|
|
|
|
if (serverSecrets && serverSecrets.jenkins_user) {
|
|
options.auth = {
|
|
user: serverSecrets.jenkins_user,
|
|
pass: serverSecrets.jenkins_pass
|
|
};
|
|
}
|
|
|
|
var badgeData = getBadgeData('build', data);
|
|
request(options, function(err, res, json) {
|
|
if (err !== null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (json.color === 'blue' || json.color === 'green') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'passing';
|
|
} else if (json.color === 'red') {
|
|
badgeData.colorscheme = 'red';
|
|
badgeData.text[1] = 'failing';
|
|
} else if (json.color === 'yellow') {
|
|
badgeData.colorscheme = 'yellow';
|
|
badgeData.text[1] = 'unstable';
|
|
} else if (json.color === 'grey' || json.color === 'disabled'
|
|
|| json.color === 'aborted' || json.color === 'notbuilt') {
|
|
badgeData.colorscheme = 'lightgrey';
|
|
badgeData.text[1] = 'not built';
|
|
} else {
|
|
badgeData.colorscheme = 'lightgrey';
|
|
badgeData.text[1] = 'building';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Jenkins tests integration
|
|
camp.route(/^\/jenkins(-ci)?\/t\/(http(s)?)\/((?:[^\/]+)(?:\/.+?)?)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var scheme = match[2]; // http(s)
|
|
var host = match[4]; // jenkins.qa.ubuntu.com
|
|
var job = match[5]; // precise-desktop-amd64_default
|
|
var format = match[6];
|
|
var options = {
|
|
json: true,
|
|
uri: scheme + '://' + host + '/job/' + job
|
|
+ '/lastBuild/api/json?tree=actions[failCount,skipCount,totalCount]'
|
|
};
|
|
|
|
if (serverSecrets && serverSecrets.jenkins_user) {
|
|
options.auth = {
|
|
user: serverSecrets.jenkins_user,
|
|
pass: serverSecrets.jenkins_pass
|
|
};
|
|
}
|
|
|
|
var badgeData = getBadgeData('tests', data);
|
|
request(options, function(err, res, json) {
|
|
if (err !== null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var testsObject = json.actions.filter(function (obj) {
|
|
return obj.hasOwnProperty('failCount');
|
|
})[0];
|
|
if (testsObject === undefined) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
var successfulTests = testsObject.totalCount
|
|
- (testsObject.failCount + testsObject.skipCount);
|
|
var percent = successfulTests / testsObject.totalCount;
|
|
badgeData.text[1] = successfulTests + ' / ' + testsObject.totalCount;
|
|
if (percent === 1) {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (percent === 0) {
|
|
badgeData.colorscheme = 'red';
|
|
} else {
|
|
badgeData.colorscheme = 'yellow';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Jenkins coverage integration
|
|
camp.route(/^\/jenkins(-ci)?\/c\/(http(s)?)\/((?:[^\/]+)(?:\/.+?)?)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var scheme = match[2]; // http(s)
|
|
var host = match[4]; // jenkins.qa.ubuntu.com
|
|
var job = match[5]; // precise-desktop-amd64_default
|
|
var format = match[6];
|
|
var options = {
|
|
json: true,
|
|
uri: scheme + '://' + host + '/job/' + job
|
|
+ '/lastBuild/cobertura/api/json?tree=results[elements[name,denominator,numerator,ratio]]'
|
|
};
|
|
|
|
if (serverSecrets && serverSecrets.jenkins_user) {
|
|
options.auth = {
|
|
user: serverSecrets.jenkins_user,
|
|
pass: serverSecrets.jenkins_pass
|
|
};
|
|
}
|
|
|
|
var badgeData = getBadgeData('coverage', data);
|
|
request(options, function(err, res, json) {
|
|
if (err !== null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var coverageObject = json.results.elements.filter(function (obj) {
|
|
return obj.name === 'Lines';
|
|
})[0];
|
|
if (coverageObject === undefined) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
var coverage = coverageObject.ratio;
|
|
if (+coverage !== +coverage) {
|
|
badgeData.text[1] = 'unknown';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
badgeData.text[1] = coverage.toFixed(0) + '%';
|
|
badgeData.colorscheme = coveragePercentageColor(coverage);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Ansible integration
|
|
camp.route(/^\/ansible\/(role)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var type = match[1]; // eg role
|
|
var roleId = match[2]; // eg 3078
|
|
var format = match[3];
|
|
var uri = 'https://galaxy.ansible.com/api/v1/roles/' + roleId + '/';
|
|
var options = {
|
|
json: true,
|
|
uri: 'https://galaxy.ansible.com/api/v1/roles/' + roleId + '/',
|
|
};
|
|
var badgeData = getBadgeData(type, data);
|
|
request(options, function(err, res, json) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if (type === 'role') {
|
|
badgeData.text[1] = json.namespace + '.' + json.name;
|
|
badgeData.colorscheme = 'blue';
|
|
} else {
|
|
badgeData.text[1] = 'unknown';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'errored';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
// Codeship.io integration
|
|
camp.route(/^\/codeship\/([^\/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var projectId = match[1]; // eg, `ab123456-00c0-0123-42de-6f98765g4h32`.
|
|
var format = match[3];
|
|
var branch = match[2];
|
|
var options = {
|
|
method: 'GET',
|
|
uri: 'https://codeship.com/projects/' + projectId + '/status' + (branch != null ? '?branch=' + branch : '')
|
|
};
|
|
var badgeData = getBadgeData('build', data);
|
|
request(options, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var statusMatch = res.headers['content-disposition']
|
|
.match(/filename="status_(.+)\.png"/);
|
|
if (!statusMatch) {
|
|
badgeData.text[1] = 'unknown';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
switch (statusMatch[1]) {
|
|
case 'success':
|
|
badgeData.text[1] = 'passing';
|
|
badgeData.colorscheme = 'brightgreen';
|
|
break;
|
|
case 'projectnotfound':
|
|
badgeData.text[1] = 'not found';
|
|
break;
|
|
case 'branchnotfound':
|
|
badgeData.text[1] = 'branch not found';
|
|
break;
|
|
case 'testing':
|
|
case 'waiting':
|
|
badgeData.text[1] = 'pending';
|
|
break;
|
|
case 'error':
|
|
badgeData.text[1] = 'failing';
|
|
badgeData.colorscheme = 'red';
|
|
break;
|
|
case 'stopped':
|
|
badgeData.text[1] = 'not built';
|
|
break;
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'not found';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Magnum CI integration
|
|
camp.route(/^\/magnumci\/ci\/([^\/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var projectId = match[1]; // E.g. 96ffb83fa700f069024921b0702e76ff
|
|
var branch = match[2]; // E.g. master
|
|
var format = match[3];
|
|
var options = {
|
|
method: 'GET',
|
|
uri: 'https://magnum-ci.com/status/' + projectId + '.png'
|
|
};
|
|
if (branch != null) {
|
|
options.uri += '?branch=' + branch;
|
|
}
|
|
var badgeData = getBadgeData('build', data);
|
|
request(options, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var statusMatch = res.headers['content-disposition']
|
|
.match(/filename="(.+)\.png"/);
|
|
if (!statusMatch) {
|
|
badgeData.text[1] = 'unknown';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
|
|
switch (statusMatch[1]) {
|
|
case 'pass':
|
|
badgeData.text[1] = 'passing';
|
|
badgeData.colorscheme = 'brightgreen';
|
|
break;
|
|
break;
|
|
case 'fail':
|
|
badgeData.text[1] = 'failing';
|
|
badgeData.colorscheme = 'red';
|
|
break;
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'not found';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Maven-Central artifact version integration
|
|
// API documentation: http://search.maven.org/#api
|
|
camp.route(/^\/maven-central\/v\/(.*)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var groupId = match[1]; // eg, `com.google.inject`
|
|
var artifactId = match[2]; // eg, `guice`
|
|
var format = match[3] || "gif"; // eg, `guice`
|
|
var query = "g:" + encodeURIComponent(groupId) + "+AND+a:" + encodeURIComponent(artifactId);
|
|
var apiUrl = 'https://search.maven.org/solrsearch/select?rows=1&q='+query;
|
|
var badgeData = getBadgeData('maven-central', data);
|
|
request(apiUrl, { headers: { 'Accept': 'application/json' } }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var version = data.response.docs[0].latestVersion;
|
|
badgeData.text[1] = 'v' + version;
|
|
if (version === '0' || /SNAPSHOT/.test(version)) {
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Bower version integration.
|
|
camp.route(/^\/bower\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, `bootstrap`.
|
|
var format = match[2];
|
|
var badgeData = getBadgeData('bower', data);
|
|
var bower = require('bower');
|
|
bower.commands.info(repo, 'version')
|
|
.on('error', function() {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
})
|
|
.on('end', function(version) {
|
|
try {
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'void';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Bower license integration.
|
|
camp.route(/^\/bower\/l\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, `bootstrap`.
|
|
var format = match[2];
|
|
var badgeData = getBadgeData('bower', data);
|
|
var bower = require('bower');
|
|
bower.commands.info(repo, 'license')
|
|
.on('error', function() {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
})
|
|
.on('end', function(license) {
|
|
try {
|
|
badgeData.text[1] = license;
|
|
badgeData.colorscheme = 'blue';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'void';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Wheelmap integration.
|
|
camp.route(/^\/wheelmap\/a\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var nodeId = match[1]; // eg, `2323004600`.
|
|
var format = match[2];
|
|
var options = {
|
|
method: 'GET',
|
|
json: true,
|
|
uri: 'http://wheelmap.org/nodes/' + nodeId + '.json'
|
|
};
|
|
var badgeData = getBadgeData('wheelmap', data);
|
|
request(options, function(err, res, json) {
|
|
try {
|
|
var accessibility = json.node.wheelchair;
|
|
badgeData.text[1] = accessibility;
|
|
if (accessibility === 'yes') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (accessibility === 'limited') {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (accessibility === 'no') {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'void';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// wordpress plugin version integration.
|
|
// example: https://img.shields.io/wordpress/plugin/v/akismet.svg for https://wordpress.org/plugins/akismet
|
|
camp.route(/^\/wordpress\/plugin\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var plugin = match[1]; // eg, `akismet`.
|
|
var format = match[2];
|
|
var apiUrl = 'http://api.wordpress.org/plugins/info/1.0/' + plugin + '.json';
|
|
var badgeData = getBadgeData('plugin', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var version = data.version;
|
|
badgeData.text[1] = 'v' + version;
|
|
if (version[0] === '0') {
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// wordpress plugin downloads integration.
|
|
// example: https://img.shields.io/wordpress/plugin/dt/akismet.svg for https://wordpress.org/plugins/akismet
|
|
camp.route(/^\/wordpress\/plugin\/dt\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var plugin = match[1]; // eg, `akismet`.
|
|
var format = match[2];
|
|
var apiUrl = 'http://api.wordpress.org/plugins/info/1.0/' + plugin + '.json';
|
|
var badgeData = getBadgeData('downloads', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var total = JSON.parse(buffer).downloaded;
|
|
badgeData.text[1] = metric(total);
|
|
if (total === 0) {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (total < 100) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (total < 1000) {
|
|
badgeData.colorscheme = 'yellowgreen';
|
|
} else if (total < 10000) {
|
|
badgeData.colorscheme = 'green';
|
|
} else {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// wordpress plugin rating integration.
|
|
// example: https://img.shields.io/wordpress/plugin/r/akismet.svg for https://wordpress.org/plugins/akismet
|
|
camp.route(/^\/wordpress\/plugin\/r\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var plugin = match[1]; // eg, `akismet`.
|
|
var format = match[2];
|
|
var apiUrl = 'http://api.wordpress.org/plugins/info/1.0/' + plugin + '.json';
|
|
var badgeData = getBadgeData('rating', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var rating = JSON.parse(buffer).rating;
|
|
rating = (rating/100)*5;
|
|
badgeData.text[1] = metric(Math.round(rating * 10) / 10) + ' stars';
|
|
if (rating === 0) {
|
|
badgeData.colorscheme = 'red';
|
|
} else if (rating < 2) {
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (rating < 3) {
|
|
badgeData.colorscheme = 'yellowgreen';
|
|
} else if (rating < 4) {
|
|
badgeData.colorscheme = 'green';
|
|
} else {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// wordpress version support integration.
|
|
// example: https://img.shields.io/wordpress/v/akismet.svg for https://wordpress.org/plugins/akismet
|
|
camp.route(/^\/wordpress\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var plugin = match[1]; // eg, `akismet`.
|
|
var format = match[2];
|
|
var apiUrl = 'http://api.wordpress.org/plugins/info/1.0/' + plugin + '.json';
|
|
var badgeData = getBadgeData('wordpress', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var pluginVersion = data.version;
|
|
if (data.tested) {
|
|
var testedVersion = data.tested.replace(/[^0-9.]/g,'');
|
|
badgeData.text[1] = testedVersion + ' tested';
|
|
var coreUrl = 'https://api.wordpress.org/core/version-check/1.7/';
|
|
request(coreUrl, function(err, res, response) {
|
|
try {
|
|
var versions = JSON.parse(response).offers.map(function(v) {
|
|
return v.version
|
|
});
|
|
if (err != null) { sendBadge(format, badgeData); return; }
|
|
var svTestedVersion = testedVersion.split('.').length == 2 ? testedVersion += '.0' : testedVersion;
|
|
var svVersion = versions[0].split('.').length == 2 ? versions[0] += '.0' : versions[0];
|
|
if (testedVersion == versions[0] || semver.gtr(svTestedVersion, svVersion)) {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (versions.indexOf(testedVersion) != -1) {
|
|
badgeData.colorscheme = 'orange';
|
|
} else {
|
|
badgeData.colorscheme = 'yellow';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
} else {
|
|
sendBadge(format, badgeData);
|
|
}
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// SourceForge integration.
|
|
camp.route(/^\/sourceforge\/([^\/]+)\/([^/]*)\/?(.*).(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // eg, 'dm'
|
|
var project = match[2]; // eg, 'sevenzip`.
|
|
var folder = match[3];
|
|
var format = match[4];
|
|
var apiUrl = 'http://sourceforge.net/projects/' + project + '/files/' + folder + '/stats/json';
|
|
var badgeData = getBadgeData('sourceforge', data);
|
|
var time_period, start_date, end_date;
|
|
if (info.charAt(0) === 'd') {
|
|
badgeData.text[0] = getLabel('downloads', data);
|
|
// get yesterday since today is incomplete
|
|
end_date = new Date((new Date()).getTime() - 1000*60*60*24);
|
|
switch (info.charAt(1)) {
|
|
case 'm':
|
|
start_date = new Date(end_date.getTime() - 1000*60*60*24*30);
|
|
time_period = '/month';
|
|
break;
|
|
case 'w':
|
|
start_date = new Date(end_date.getTime() - 1000*60*60*24*6); // 6, since date range is inclusive
|
|
time_period = '/week';
|
|
break;
|
|
case 'd':
|
|
start_date = end_date;
|
|
time_period = '/day';
|
|
break;
|
|
case 't':
|
|
start_date = new Date(99, 0, 1);
|
|
time_period = '/total';
|
|
break;
|
|
}
|
|
}
|
|
apiUrl += '?start_date=' + start_date.toISOString().slice(0,10) + '&end_date=' + end_date.toISOString().slice(0,10);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var downloads = data.total;
|
|
badgeData.text[1] = metric(downloads) + time_period;
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Requires.io status integration
|
|
camp.route(/^\/requires\/([^\/]+\/[^\/]+\/[^\/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1]; // eg, `github/celery/celery`.
|
|
var branch = match[2];
|
|
var format = match[3];
|
|
var uri = 'https://requires.io/api/v1/status/' + userRepo
|
|
if (branch != null) {
|
|
uri += '?branch=' + branch
|
|
}
|
|
var options = {
|
|
method: 'GET',
|
|
json: true,
|
|
uri: uri
|
|
};
|
|
var badgeData = getBadgeData('requirements', data);
|
|
request(options, function(err, res, json) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
badgeData.colorscheme = 'red';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if (json.status === 'up-to-date') {
|
|
badgeData.text[1] = 'up-to-date';
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (json.status === 'outdated') {
|
|
badgeData.text[1] = 'outdated';
|
|
badgeData.colorscheme = 'yellow';
|
|
} else if (json.status === 'insecure') {
|
|
badgeData.text[1] = 'insecure';
|
|
badgeData.colorscheme = 'red';
|
|
} else {
|
|
badgeData.text[1] = 'unknown';
|
|
badgeData.colorscheme = 'lightgrey';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid' + e.toString();
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
});
|
|
}));
|
|
|
|
// apm download integration.
|
|
camp.route(/^\/apm\/dm\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, `vim-mode`.
|
|
var format = match[2];
|
|
var apiUrl = 'https://atom.io/api/packages/' + repo;
|
|
var badgeData = getBadgeData('downloads', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var dls = JSON.parse(buffer).downloads;
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
badgeData.text[1] = metric(dls);
|
|
badgeData.colorscheme = 'green';
|
|
sendBadge(format, badgeData);
|
|
});
|
|
}));
|
|
|
|
// apm version integration.
|
|
camp.route(/^\/apm\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, `vim-mode`.
|
|
var format = match[2];
|
|
var apiUrl = 'https://atom.io/api/packages/' + repo;
|
|
var badgeData = getBadgeData('apm', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var releases = JSON.parse(buffer).releases;
|
|
var version = releases.latest;
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
badgeData.text[1] = 'v' + version;
|
|
badgeData.colorscheme = 'green';
|
|
sendBadge(format, badgeData);
|
|
});
|
|
}));
|
|
|
|
// apm license integration.
|
|
camp.route(/^\/apm\/l\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var repo = match[1]; // eg, `vim-mode`.
|
|
var format = match[2];
|
|
var apiUrl = 'https://atom.io/api/packages/' + repo;
|
|
var badgeData = getBadgeData('license', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var metadata = JSON.parse(buffer).metadata;
|
|
var license = metadata.license;
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
badgeData.text[1] = license;
|
|
badgeData.colorscheme = 'blue';
|
|
sendBadge(format, badgeData);
|
|
});
|
|
}));
|
|
|
|
// Talk integration
|
|
camp.route(/^\/talk\/t\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var roomHash = match[1]; // eg, 9c81ff703b
|
|
var format = match[2];
|
|
var url = 'https://guest.talk.ai/api/rooms/' + roomHash;
|
|
var badgeData = getBadgeData('talk', data);
|
|
request(url, function(err, res, buffer) {
|
|
try {
|
|
room = JSON.parse(buffer);
|
|
badgeData.text[1] = room.topic;
|
|
badgeData.colorscheme = room.color;
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// CircleCI build integration.
|
|
// https://circleci.com/api/v1/project/BrightFlair/PHP.Gt?circle-token=0a5143728784b263d9f0238b8d595522689b3af2&limit=1&filter=completed
|
|
camp.route(/^\/circleci\/(?:token\/(\w+))?[+\/]?project\/([^\/]+\/[^\/]+)(?:\/(.*))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var token = match[1]
|
|
var userRepo = match[2]; // eg, `BrightFlair/PHP.Gt`.
|
|
var branch = match[3];
|
|
var format = match[4];
|
|
|
|
// Base API URL
|
|
var apiUrl = 'https://circleci.com/api/v1/project/' + userRepo;
|
|
|
|
// Query Params
|
|
queryParams = {};
|
|
queryParams['limit'] = 1;
|
|
queryParams['filter'] = 'completed';
|
|
|
|
// Custom Banch if present
|
|
if(branch != null) {
|
|
apiUrl += "/tree/" + branch;
|
|
}
|
|
|
|
// Append Token to Query Params if present
|
|
if (token) {
|
|
queryParams['circle-token'] = token;
|
|
}
|
|
|
|
// Apprend query params to API URL
|
|
apiUrl += '?' + querystring.stringify(queryParams);
|
|
|
|
var badgeData = getBadgeData('build', data);
|
|
request(apiUrl, {json:true}, function(err, res, data) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var status = data[0].status;
|
|
switch(status) {
|
|
case 'success':
|
|
case 'fixed':
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'passing';
|
|
break;
|
|
|
|
case 'failed':
|
|
badgeData.colorscheme = 'red';
|
|
badgeData.text[1] = 'failed';
|
|
break;
|
|
|
|
case 'no_tests':
|
|
case 'scheduled':
|
|
case 'not_run':
|
|
badgeData.colorscheme = 'yellow';
|
|
default:
|
|
badgeData.text[1] = status.replace('_', ' ');
|
|
break;
|
|
}
|
|
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// CPAN integration.
|
|
camp.route(/^\/cpan\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // either `v` or `l`
|
|
var pkg = match[2]; // eg, Config-Augeas
|
|
var format = match[3];
|
|
var badgeData = getBadgeData('cpan', data);
|
|
var url = 'https://api.metacpan.org/v0/release/'+pkg;
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
|
|
if (info === 'v') {
|
|
var version = data.version;
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
} else if (info === 'l') {
|
|
var license = data.license[0];
|
|
badgeData.text[1] = license;
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// CTAN integration.
|
|
camp.route(/^\/ctan\/([^\/])\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1]; // either `v` or `l`
|
|
var pkg = match[2]; // eg, tex
|
|
var format = match[3];
|
|
var url = 'http://www.ctan.org/json/pkg/' + pkg;
|
|
var badgeData = getBadgeData('ctan', data);
|
|
request(url, function (err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(badgeData, format);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
|
|
if (info === 'v') {
|
|
var version = data.version.number;
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} else if (info === 'l') {
|
|
var license = data.license;
|
|
if (license === '') {
|
|
badgeData.text[1] = 'Unknown';
|
|
} else {
|
|
badgeData.text[1] = license;
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
}
|
|
} catch (e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
})}
|
|
));
|
|
|
|
// DUB download integration
|
|
camp.route(/^\/dub\/(dd|dw|dm|dt)\/([^\/]+)(?:\/([^\/]+))?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function (data, match, sendBadge, request) {
|
|
var info = match[1]; // downloads (dd - daily, dw - weekly, dm - monthly, dt - total)
|
|
var pkg = match[2]; // package name, e.g. vibe-d
|
|
var version = match[3]; // version (1.2.3 or latest)
|
|
var format = match[4];
|
|
var apiUrl = 'http://code.dlang.org/api/packages/'+pkg;
|
|
if (version) {
|
|
apiUrl += '/' + version;
|
|
}
|
|
apiUrl += '/stats';
|
|
var badgeData = getBadgeData('dub', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
if (info.charAt(0) === 'd') {
|
|
badgeData.text[0] = getLabel('downloads', data);
|
|
var downloads;
|
|
switch (info.charAt(1)) {
|
|
case 'm':
|
|
downloads = data.downloads.monthly;
|
|
badgeData.text[1] = metric(downloads) + '/month';
|
|
break;
|
|
case 'w':
|
|
downloads = data.downloads.weekly;
|
|
badgeData.text[1] = metric(downloads) + '/week';
|
|
break;
|
|
case 'd':
|
|
downloads = data.downloads.daily;
|
|
badgeData.text[1] = metric(downloads) + '/day';
|
|
break;
|
|
case 't':
|
|
downloads = data.downloads.total;
|
|
badgeData.text[1] = metric(downloads);
|
|
break;
|
|
}
|
|
if (version) {
|
|
badgeData.text[1] += ' ' + versionColor(version).version;
|
|
}
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
sendBadge(format, badgeData);
|
|
}
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// DUB license and version integration
|
|
camp.route(/^\/dub\/(v|l)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function (data, match, sendBadge, request) {
|
|
var info = match[1]; // (v - version, l - license)
|
|
var pkg = match[2]; // package name, e.g. vibe-d
|
|
var format = match[3];
|
|
var apiUrl = 'http://code.dlang.org/api/packages/' + pkg;
|
|
if (info === 'v') {
|
|
apiUrl += '/latest';
|
|
} else if (info === 'l') {
|
|
apiUrl += '/latest/info';
|
|
}
|
|
var badgeData = getBadgeData('dub', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
if (info === 'v') {
|
|
var vdata = versionColor(data);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
sendBadge(format, badgeData);
|
|
} else if (info == 'l') {
|
|
var license = data.info.license;
|
|
badgeData.text[0] = 'license';
|
|
if (license == null) {
|
|
badgeData.text[1] = 'Unknown';
|
|
} else {
|
|
badgeData.text[1] = license;
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
}
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Docker Hub stars integration.
|
|
camp.route(/^\/docker\/stars\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, mashape
|
|
var repo = match[2]; // eg, kong
|
|
var format = match[3];
|
|
if (user === '_') {
|
|
user = 'library';
|
|
}
|
|
var path = user + '/' + repo;
|
|
var url = 'https://hub.docker.com/v2/repositories/' + path + '/stars/count/';
|
|
var badgeData = getBadgeData('docker stars', data);
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var stars = +("" + buffer);
|
|
badgeData.text[1] = metric(stars);
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#008bb8';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Docker Hub pulls integration.
|
|
camp.route(/^\/docker\/pulls\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, mashape
|
|
var repo = match[2]; // eg, kong
|
|
var format = match[3];
|
|
if (user === '_') {
|
|
user = 'library';
|
|
}
|
|
var path = user + '/' + repo;
|
|
var url = 'https://hub.docker.com/v2/repositories/' + path;
|
|
var badgeData = getBadgeData('docker pulls', data);
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var pulls = data.pull_count;
|
|
badgeData.text[1] = metric(pulls);
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#008bb8';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Twitter integration.
|
|
camp.route(/^\/twitter\/url\/([^\/]+)\/(.+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var scheme = match[1]; // eg, https
|
|
var path = match[2]; // eg, shields.io
|
|
var format = match[3];
|
|
var page = encodeURIComponent(scheme + '://' + path);
|
|
// The URL API died: #568.
|
|
//var url = 'http://cdn.api.twitter.com/1/urls/count.json?url=' + page;
|
|
var badgeData = getBadgeData('tweet', data);
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.twitter;
|
|
badgeData.links = [
|
|
'https://twitter.com/intent/tweet?text=Wow:&url=' + page,
|
|
'https://twitter.com/search?q=' + page,
|
|
];
|
|
}
|
|
badgeData.text[1] = '';
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#55ACEE';
|
|
sendBadge(format, badgeData);
|
|
}));
|
|
|
|
// Twitter follow badge.
|
|
camp.route(/^\/twitter\/follow\/@?([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, shields_io
|
|
var format = match[2];
|
|
var options = {
|
|
url: 'http://cdn.syndication.twimg.com/widgets/followbutton/info.json?screen_names=' + user
|
|
};
|
|
var badgeData = getBadgeData('Follow @' + user, data);
|
|
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#55ACEE';
|
|
if (badgeData.template === 'social') {
|
|
badgeData.logo = badgeData.logo || logos.twitter;
|
|
}
|
|
badgeData.links = [
|
|
'https://twitter.com/intent/follow?screen_name=' + user,
|
|
'https://twitter.com/' + user + '/followers'
|
|
];
|
|
badgeData.text[1] = '';
|
|
request(options, function(err, res, buffer) {
|
|
if (err != null) {
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
// The data is formatted as an array.
|
|
var data = JSON.parse(buffer)[0];
|
|
// data.followers_count could be zero… don't just check if falsey.
|
|
if (data !== undefined && data.followers_count != null){
|
|
badgeData.text[1] = metric(data.followers_count);
|
|
}
|
|
} catch(e) {
|
|
console.error(e);
|
|
}
|
|
sendBadge(format, badgeData);
|
|
});
|
|
}));
|
|
|
|
// Snap CI build integration.
|
|
// https://snap-ci.com/snap-ci/snap-deploy/branch/master/build_image
|
|
camp.route(/^\/snap(-ci?)\/([^\/]+\/[^\/]+)(?:\/(.+))\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[2];
|
|
var branch = match[3];
|
|
var format = match[4];
|
|
var url = 'https://snap-ci.com/' + userRepo + '/branch/' + branch + '/build_image.svg';
|
|
|
|
var badgeData = getBadgeData('build', data);
|
|
fetchFromSvg(request, url, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
badgeData.text[1] = res.toLowerCase();
|
|
if (res === 'Passed') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'passing';
|
|
} else if (res === 'Failed') {
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Visual Studio Team Services build integration.
|
|
camp.route(/^\/vso\/build\/([^\/]+)\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var name = match[1]; // User name
|
|
var project = match[2]; // Project ID, e.g. 953a34b9-5966-4923-a48a-c41874cfb5f5
|
|
var build = match[3]; // Build definition ID, e.g. 1
|
|
var format = match[4];
|
|
var url = 'https://' + name + '.visualstudio.com/DefaultCollection/_apis/public/build/definitions/' + project + '/' + build + '/badge';
|
|
var badgeData = getBadgeData('build', data);
|
|
fetchFromSvg(request, url, function(err, res) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
badgeData.text[1] = res.toLowerCase();
|
|
if (res === 'succeeded') {
|
|
badgeData.colorscheme = 'brightgreen';
|
|
badgeData.text[1] = 'passing';
|
|
} else if (res === 'failed') {
|
|
badgeData.colorscheme = 'red';
|
|
badgeData.text[1] = 'failing';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// ImageLayers.io integration.
|
|
camp.route(/^\/imagelayers\/(image\-size|layers)\/([^\/]+)\/([^\/]+)\/([^\/]*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var type = match[1];
|
|
var user = match[2];
|
|
var repo = match[3];
|
|
var tag = match[4]
|
|
var format = match[5];
|
|
if (user === '_') {
|
|
user = 'library';
|
|
}
|
|
var path = user + '/' + repo;
|
|
var badgeData = getBadgeData(type, data);
|
|
var options = {
|
|
method: 'POST',
|
|
json: true,
|
|
body: {
|
|
"repos": [{"name": path, "tag": tag}]
|
|
},
|
|
uri: 'https://imagelayers.io:8888/registry/analyze'
|
|
};
|
|
request(options, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
if (type == 'image-size') {
|
|
size = metric(buffer[0].repo.size) + "B";
|
|
badgeData.text[0] = 'image size';
|
|
badgeData.text[1] = size;
|
|
} else if (type == 'layers') {
|
|
badgeData.text[1] = buffer[0].repo.count;
|
|
}
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#007ec6';
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Gitter room integration.
|
|
camp.route(/^\/gitter\/room\/([^\/]+\/[^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var userRepo = match[1];
|
|
var format = match[2];
|
|
|
|
var badgeData = getBadgeData('chat', data);
|
|
badgeData.text[1] = 'on gitter';
|
|
badgeData.colorscheme = 'brightgreen';
|
|
if (darkBackgroundTemplates.some(function(t) { return t === badgeData.template; })) {
|
|
badgeData.logo = badgeData.logo || logos['gitter-white'];
|
|
badgeData.logoWidth = 7;
|
|
}
|
|
sendBadge(format, badgeData);
|
|
}));
|
|
|
|
// homebrew integration
|
|
camp.route(/^\/homebrew\/v\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var pkg = match[1]; // eg. cake
|
|
var format = match[2];
|
|
var apiUrl = 'http://braumeister.org/formula/' + pkg + '/version';
|
|
|
|
var badgeData = getBadgeData('homebrew', data);
|
|
request(apiUrl, { headers: { 'Accept': 'application/json' } }, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
var version = data.stable;
|
|
|
|
var vdata = versionColor(version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// StackExchange integration.
|
|
camp.route(/^\/stackexchange\/([^\/]+)\/([^\/])\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var site = match[1]; // eg, stackoverflow
|
|
var info = match[2]; // either `r`
|
|
var item = match[3]; // eg, 232250
|
|
var format = match[4];
|
|
var path;
|
|
if (info === 'r') {
|
|
path = 'users/' + item;
|
|
} else if (info === 't') {
|
|
path = 'tags/' + item + '/info';
|
|
}
|
|
var options = {
|
|
method: 'GET',
|
|
uri: 'https://api.stackexchange.com/2.2/' + path + '?site=' + site,
|
|
gzip: true
|
|
}
|
|
var badgeData = getBadgeData(site, data);
|
|
request(options, function (err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(badgeData, format);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer.toString());
|
|
|
|
// IP rate limiting
|
|
if (data.error_name === 'throttle_violation') {
|
|
return; // Hope for the best in the cache.
|
|
}
|
|
|
|
if (info === 'r') {
|
|
var reputation = data.items[0].reputation;
|
|
badgeData.text[0] = site + ' reputation';
|
|
badgeData.text[1] = metric(reputation);
|
|
badgeData.colorscheme = floorCountColor(1000, 10000, 20000);
|
|
} else if (info === 't') {
|
|
var count = data.items[0].count;
|
|
badgeData.text[0] = site + ' ' + item + ' questions';
|
|
badgeData.text[1] = metric(count);
|
|
badgeData.colorscheme = floorCountColor(1000, 10000, 20000);
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
})}
|
|
));
|
|
|
|
// beerpay.io integration.
|
|
// e.g. JSON response: https://beerpay.io/api/v1/beerpay/projects/beerpay.io
|
|
// e.g. SVG badge: https://beerpay.io/beerpay/beerpay.io/badge.svg?style=flat-square
|
|
camp.route(/^\/beerpay\/(.*)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, beerpay
|
|
var project = match[2]; // eg, beerpay.io
|
|
var format = match[3];
|
|
|
|
var apiUrl = 'https://beerpay.io/api/v1/' + user + '/projects/' + project;
|
|
var badgeData = getBadgeData('beerpay', data);
|
|
|
|
request(apiUrl, function (err, res, buffer) {
|
|
if (err) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(badgeData, format);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
badgeData.text[1] = '$' + (data.total_amount || 0);
|
|
badgeData.colorscheme = 'red';
|
|
sendBadge(format, badgeData);
|
|
} catch (e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Maintenance integration.
|
|
camp.route(/^\/maintenance\/([^\/]+)\/([^\/]+)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var status = match[1]; // eg, yes
|
|
var year = +match[2]; // eg, 2016
|
|
var format = match[3];
|
|
var badgeData = getBadgeData('maintained?', data);
|
|
try {
|
|
var now = new Date();
|
|
var cy = now.getUTCFullYear(); // current year.
|
|
var m = now.getUTCMonth(); // month.
|
|
if (cy <= year) {
|
|
badgeData.text[1] = status;
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if ((cy === year + 1) && (m < 3)) {
|
|
badgeData.text[1] = 'stale (as of ' + cy + ')';
|
|
} else {
|
|
badgeData.text[1] = 'no!';
|
|
badgeData.colorscheme = 'red';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
console.error(e.stack);
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
}));
|
|
|
|
// bitHound integration
|
|
camp.route(/^\/bithound\/(code\/|dependencies\/|devDependencies\/)?(.+?)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var type = match[1].slice(0, -1);
|
|
var userRepo = match[2]; // eg, `github/rexxars/sse-channel`.
|
|
var format = match[3];
|
|
var apiUrl = 'https://www.bithound.io/api/' + userRepo + '/badge/' + type;
|
|
var badgeData = getBadgeData(type === 'devDependencies' ? 'dev dependencies' : type, data);
|
|
|
|
request(apiUrl, { headers: { 'Accept': 'application/json' } }, function(err, res, buffer) {
|
|
try {
|
|
var data = JSON.parse(buffer);
|
|
badgeData.text[1] = data.label;
|
|
badgeData.logo = logos['bithound'];
|
|
badgeData.logoWidth = 15;
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#' + data.color;
|
|
sendBadge(format, badgeData);
|
|
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Waffle.io integration
|
|
camp.route(/^\/waffle\/label\/([^\/]+)\/([^\/]+)\/?([^\/]+)?\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var user = match[1]; // eg, evancohen
|
|
var repo = match[2]; // eg, smart-mirror
|
|
var ghLabel = match[3] || 'ready'; // eg, in%20progress
|
|
var format = match[4];
|
|
var apiUrl = 'https://api.waffle.io/' + user + '/' + repo + '/cards';
|
|
var badgeData = getBadgeData('issues', data);
|
|
|
|
request(apiUrl, function(err, res, buffer) {
|
|
try {
|
|
var cards = JSON.parse(buffer);
|
|
if (cards.length === 0) {
|
|
badgeData.text[1] = 'absent';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
var count = 0;
|
|
var color;
|
|
for (var i = 0; i < cards.length; i++) {
|
|
var cardMetadata = cards[i].githubMetadata;
|
|
if (cardMetadata.labels && cardMetadata.labels.length > 0) {
|
|
for (var j = 0; j < cardMetadata.labels.length; j++) {
|
|
var label = cardMetadata.labels[j];
|
|
if (label.name === ghLabel) {
|
|
count++;
|
|
color = label.color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
badgeData.text[0] = data.label || ghLabel;
|
|
badgeData.text[1] = '' + count;
|
|
badgeData.colorscheme = null;
|
|
badgeData.colorB = '#' + (color || '78bdf2');
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Arch user repository (AUR) integration.
|
|
camp.route(/^\/aur\/(version|votes|license)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var info = match[1];
|
|
var pkg = match[2];
|
|
var format = match[3];
|
|
var apiUrl = 'https://aur.archlinux.org/rpc.php?type=info&arg=' + pkg;
|
|
var badgeData = getBadgeData('AUR', data);
|
|
request(apiUrl, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
try {
|
|
var data = JSON.parse(buffer).results;
|
|
if (info === 'version') {
|
|
var vdata = versionColor(data.Version);
|
|
badgeData.text[1] = vdata.version;
|
|
if (data.OutOfDate === null) {
|
|
badgeData.colorscheme = 'blue';
|
|
} else {
|
|
badgeData.colorscheme = 'orange';
|
|
}
|
|
} else if (info === 'votes') {
|
|
var votes = data.NumVotes;
|
|
badgeData.text[0] = "votes";
|
|
badgeData.text[1] = votes;
|
|
badgeData.colorscheme = floorCountColor(votes, 2, 20, 60);
|
|
} else if (info === 'license') {
|
|
var license = data.License;
|
|
badgeData.text[0] = "license";
|
|
badgeData.text[1] = license;
|
|
badgeData.colorscheme = 'blue';
|
|
}
|
|
sendBadge(format, badgeData);
|
|
} catch(e) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
}
|
|
});
|
|
}));
|
|
|
|
// Chrome web store integration
|
|
camp.route(/^\/chrome-web-store\/(v|d|price|rating|rating-count)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
|
cache(function(data, match, sendBadge, request) {
|
|
var type = match[1];
|
|
var storeId = match[2]; // eg, nimelepbpejjlbmoobocpfnjhihnpked
|
|
var format = match[3];
|
|
var badgeData = getBadgeData('chrome web store', data);
|
|
var url = 'https://chrome.google.com/webstore/detail/' + storeId + '?hl=en&gl=US';
|
|
var chromeWebStore = require('chrome-web-store-item-property');
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) {
|
|
badgeData.text[1] = 'inaccessible';
|
|
sendBadge(format, badgeData);
|
|
return;
|
|
}
|
|
chromeWebStore.convert(buffer)
|
|
.then(function (value) {
|
|
if (type === 'v') {
|
|
var vdata = versionColor(value.version);
|
|
badgeData.text[1] = vdata.version;
|
|
badgeData.colorscheme = vdata.color;
|
|
} else if (type === 'd') {
|
|
var downloads = value.interactionCount.UserDownloads;
|
|
badgeData.text[0] = 'downloads';
|
|
badgeData.text[1] = metric(downloads) + ' total';
|
|
badgeData.colorscheme = downloadCountColor(downloads);
|
|
} else if (type === 'price') {
|
|
badgeData.text[1] = value.price;
|
|
badgeData.colorscheme = 'brightgreen';
|
|
} else if (type === 'rating') {
|
|
var rating = Math.round(value.ratingValue * 100) / 100;
|
|
badgeData.text[0] = 'rating';
|
|
badgeData.text[1] = rating;
|
|
badgeData.colorscheme = floorCountColor(rating, 2, 3, 4);
|
|
} else if (type === 'rating-count') {
|
|
var ratingCount = value.ratingCount;
|
|
badgeData.text[0] = 'rating count';
|
|
badgeData.text[1] = metric(ratingCount) + ' total';
|
|
badgeData.colorscheme = floorCountColor(ratingCount, 5, 50, 500);
|
|
}
|
|
sendBadge(format, badgeData);
|
|
}).catch(function (err) {
|
|
badgeData.text[1] = 'invalid';
|
|
sendBadge(format, badgeData);
|
|
});
|
|
});
|
|
}));
|
|
|
|
// Any badge.
|
|
camp.route(/^\/(:|badge\/)(([^-]|--)*?)-(([^-]|--)*)-(([^-]|--)+)\.(svg|png|gif|jpg)$/,
|
|
function(data, match, end, ask) {
|
|
var subject = escapeFormat(match[2]);
|
|
var status = escapeFormat(match[4]);
|
|
var color = escapeFormat(match[6]);
|
|
var format = match[8];
|
|
|
|
incrMonthlyAnalytics(analytics.rawMonthly);
|
|
if (data.style === 'flat') {
|
|
incrMonthlyAnalytics(analytics.rawFlatMonthly);
|
|
} else if (data.style === 'flat-square') {
|
|
incrMonthlyAnalytics(analytics.rawFlatSquareMonthly);
|
|
}
|
|
|
|
// Cache management - the badge is constant.
|
|
var cacheDuration = (3600*24*1)|0; // 1 day.
|
|
ask.res.setHeader('Cache-Control', 'max-age=' + cacheDuration);
|
|
if (+(new Date(ask.req.headers['if-modified-since'])) >= +serverStartTime) {
|
|
ask.res.statusCode = 304;
|
|
ask.res.end(); // not modified.
|
|
return;
|
|
}
|
|
ask.res.setHeader('Last-Modified', serverStartTime.toGMTString());
|
|
|
|
// Badge creation.
|
|
try {
|
|
var badgeData = getBadgeData(subject, data);
|
|
badgeData.colorscheme = undefined;
|
|
if (data.label !== undefined) { badgeData.text[0] = '' + data.label; }
|
|
badgeData.text[1] = status;
|
|
if (sixHex(color)) {
|
|
badgeData.colorB = '#' + color;
|
|
} else {
|
|
badgeData.colorscheme = color;
|
|
}
|
|
if (data.style && validTemplates.indexOf(data.style) > -1) {
|
|
badgeData.template = data.style;
|
|
}
|
|
badge(badgeData, makeSend(format, ask.res, end));
|
|
} catch(e) {
|
|
console.error(e.stack);
|
|
badge({text: ['error', 'bad badge'], colorscheme: 'red'},
|
|
makeSend(format, ask.res, end));
|
|
}
|
|
});
|
|
|
|
// Any badge, old version.
|
|
camp.route(/^\/([^\/]+)\/(.+).png$/,
|
|
function(data, match, end, ask) {
|
|
var subject = match[1];
|
|
var status = match[2];
|
|
var color = data.color;
|
|
|
|
// Cache management - the badge is constant.
|
|
var cacheDuration = (3600*24*1)|0; // 1 day.
|
|
ask.res.setHeader('Cache-Control', 'max-age=' + cacheDuration);
|
|
if (+(new Date(ask.req.headers['if-modified-since'])) >= +serverStartTime) {
|
|
ask.res.statusCode = 304;
|
|
ask.res.end(); // not modified.
|
|
return;
|
|
}
|
|
ask.res.setHeader('Last-Modified', serverStartTime.toGMTString());
|
|
|
|
// Badge creation.
|
|
try {
|
|
var badgeData = {text: [subject, status]};
|
|
badgeData.colorscheme = color;
|
|
badge(badgeData, makeSend('png', ask.res, end));
|
|
} catch(e) {
|
|
badge({text: ['error', 'bad badge'], colorscheme: 'red'},
|
|
makeSend('png', ask.res, end));
|
|
}
|
|
});
|
|
|
|
// Redirect the root to the website.
|
|
camp.route(/^\/$/, function(data, match, end, ask) {
|
|
ask.res.statusCode = 302;
|
|
ask.res.setHeader('Location', infoSite);
|
|
ask.res.end();
|
|
});
|
|
|
|
// Escapes `t` using the format specified in
|
|
// <https://github.com/espadrine/gh-badges/issues/12#issuecomment-31518129>
|
|
function escapeFormat(t) {
|
|
return t
|
|
// Inline single underscore.
|
|
.replace(/([^_])_([^_])/g, '$1 $2')
|
|
// Leading or trailing underscore.
|
|
.replace(/([^_])_$/, '$1 ').replace(/^_([^_])/, ' $1')
|
|
// Double underscore and double dash.
|
|
.replace(/__/g, '_').replace(/--/g, '-');
|
|
}
|
|
|
|
function sixHex(s) { return /^[0-9a-fA-F]{6}$/.test(s); }
|
|
|
|
function getLabel(label, data) {
|
|
return data.label || label;
|
|
}
|
|
|
|
// data (URL query) can include `label`, `style`, `logo`, `logoWidth`, `link`.
|
|
// It can also include `maxAge`.
|
|
function getBadgeData(defaultLabel, data) {
|
|
var label = getLabel(defaultLabel, data);
|
|
var template = data.style || 'default';
|
|
if (data.style && validTemplates.indexOf(data.style) > -1) {
|
|
template = data.style;
|
|
};
|
|
if (!(Object(data.link) instanceof Array)) {
|
|
if (data.link === undefined) {
|
|
data.link = [];
|
|
} else {
|
|
data.link = [data.link];
|
|
}
|
|
}
|
|
|
|
if (data.logo !== undefined && !/^data:/.test(data.logo)) {
|
|
data.logo = 'data:' + data.logo;
|
|
}
|
|
|
|
return {
|
|
text: [label, 'n/a'],
|
|
colorscheme: 'lightgrey',
|
|
template: template,
|
|
logo: data.logo,
|
|
logoWidth: +data.logoWidth,
|
|
links: data.link,
|
|
};
|
|
}
|
|
|
|
function makeSend(format, askres, end) {
|
|
if (format === 'svg') {
|
|
return function(res) { sendSVG(res, askres, end); };
|
|
} else if (format === 'json') {
|
|
return function(res) { sendJSON(res, askres, end); };
|
|
} else {
|
|
return function(res) { sendOther(format, res, askres, end); };
|
|
}
|
|
}
|
|
|
|
function sendSVG(res, askres, end) {
|
|
askres.setHeader('Content-Type', 'image/svg+xml;charset=utf-8');
|
|
end(null, {template: streamFromString(res)});
|
|
}
|
|
|
|
function sendOther(format, res, askres, end) {
|
|
askres.setHeader('Content-Type', 'image/' + format);
|
|
svg2img(res, format, askres);
|
|
}
|
|
|
|
function sendJSON(res, askres, end) {
|
|
askres.setHeader('Content-Type', 'application/json');
|
|
askres.setHeader('Access-Control-Allow-Origin', '*');
|
|
end(null, {template: streamFromString(res)});
|
|
}
|
|
|
|
var stream = require('stream');
|
|
function streamFromString(str) {
|
|
var newStream = new stream.Readable();
|
|
newStream._read = function() { newStream.push(str); newStream.push(null); };
|
|
return newStream;
|
|
}
|
|
|
|
// Map from URL to { timestamp: last fetch time, interval: in milliseconds,
|
|
// data: data }.
|
|
var regularUpdateCache = Object.create(null);
|
|
// url: a string, scraper: a function that takes string data at that URL.
|
|
// interval: number in milliseconds.
|
|
// cb: a callback function that takes an error and data returned by the scraper.
|
|
function regularUpdate(url, interval, scraper, cb) {
|
|
var timestamp = Date.now();
|
|
var cache = regularUpdateCache[url];
|
|
if (cache != null &&
|
|
(timestamp - cache.timestamp) < interval) {
|
|
cb(null, regularUpdateCache[url].data);
|
|
return;
|
|
}
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) { cb(err); return; }
|
|
if (regularUpdateCache[url] == null) {
|
|
regularUpdateCache[url] = { timestamp: 0, data: 0 };
|
|
}
|
|
try {
|
|
var data = scraper(buffer);
|
|
} catch(e) { cb(e); return; }
|
|
regularUpdateCache[url].timestamp = timestamp;
|
|
regularUpdateCache[url].data = data;
|
|
cb(null, data);
|
|
});
|
|
}
|
|
|
|
var githubHeaders = {
|
|
'User-Agent': 'Shields.io',
|
|
'Accept': 'application/vnd.github.v3+json'
|
|
};
|
|
|
|
// Personal tokens allow access to GitHub private repositories.
|
|
// You can manage your personal GitHub token at
|
|
// <https://github.com/settings/tokens>.
|
|
if (serverSecrets && serverSecrets.gh_token) {
|
|
githubHeaders['Authorization'] = 'token ' + serverSecrets.gh_token;
|
|
}
|
|
|
|
// Given a number, string with appropriate unit in the metric system, SI.
|
|
// Note: numbers beyond the peta- cannot be represented as integers in JS.
|
|
var metricPrefix = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
|
var metricPower = metricPrefix
|
|
.map(function(a, i) { return Math.pow(1000, i + 1); });
|
|
function metric(n) {
|
|
for (var i = metricPrefix.length - 1; i >= 0; i--) {
|
|
var limit = metricPower[i];
|
|
if (n >= limit) {
|
|
n = Math.round(n / limit);
|
|
return ''+n + metricPrefix[i];
|
|
}
|
|
}
|
|
return ''+n;
|
|
}
|
|
|
|
|
|
// Get data from a svg-style badge.
|
|
// cb: function(err, string)
|
|
function fetchFromSvg(request, url, cb) {
|
|
request(url, function(err, res, buffer) {
|
|
if (err != null) { return cb(err); }
|
|
try {
|
|
var badge = buffer.replace(/(?:\r\n\s*|\r\s*|\n\s*)/g, '');
|
|
var match = />([^<>]+)<\/text><\/g>/.exec(badge);
|
|
if (!match) { return cb(Error('Cannot fetch from SVG:\n' + buffer)); }
|
|
cb(null, match[1]);
|
|
} catch(e) {
|
|
cb(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
function ordinalNumber(n) {
|
|
var s=["ᵗʰ","ˢᵗ","ⁿᵈ","ʳᵈ"], v=n%100;
|
|
return n+(s[(v-20)%10]||s[v]||s[0]);
|
|
}
|
|
|
|
function coveragePercentageColor(percentage) {
|
|
return floorCountColor(percentage, 80, 90, 100);
|
|
}
|
|
|
|
function downloadCountColor(downloads) {
|
|
return floorCountColor(downloads, 10, 100, 1000);
|
|
}
|
|
|
|
function floorCountColor(value, yellow, yellowgreen, green) {
|
|
if (value === 0) {
|
|
return 'red';
|
|
} else if (value < yellow) {
|
|
return 'yellow';
|
|
} else if (value < yellowgreen) {
|
|
return 'yellowgreen';
|
|
} else if (value < green) {
|
|
return 'green';
|
|
} else {
|
|
return 'brightgreen';
|
|
}
|
|
}
|
|
|
|
function versionColor(version) {
|
|
var first = version[0];
|
|
if (first === 'v') {
|
|
first = version[1];
|
|
} else if (/^[0-9]/.test(version)) {
|
|
version = 'v' + version;
|
|
}
|
|
if (first === '0' || (version.indexOf('-') !== -1)) {
|
|
return { version: version, color: 'orange' };
|
|
} else {
|
|
return { version: version, color: 'blue' };
|
|
}
|
|
}
|
|
|
|
// Take string versions.
|
|
// -1 if v1 < v2, 1 if v1 > v2, 0 otherwise.
|
|
function compareDottedVersion(v1, v2) {
|
|
var parts1 = /([0-9\.]+)(.*)$/.exec(v1);
|
|
var parts2 = /([0-9\.]+)(.*)$/.exec(v2);
|
|
if (parts1 != null && parts2 != null) {
|
|
var numbers1 = parts1[1];
|
|
var numbers2 = parts2[1];
|
|
var distinguisher1 = parts1[2];
|
|
var distinguisher2 = parts2[2];
|
|
var numlist1 = numbers1.split('.').map(function(e) { return +e; });
|
|
var numlist2 = numbers2.split('.').map(function(e) { return +e; });
|
|
var cmp = listCompare(numlist1, numlist2);
|
|
if (cmp !== 0) { return cmp; }
|
|
else { return distinguisher1 < distinguisher2? -1:
|
|
distinguisher1 > distinguisher2? 1: 0; }
|
|
}
|
|
return v1 < v2? -1: v1 > v2? 1: 0;
|
|
}
|
|
|
|
// Take a list of string versions.
|
|
// Return the latest, or undefined, if there are none.
|
|
function latestDottedVersion(versions) {
|
|
var len = versions.length;
|
|
if (len === 0) { return; }
|
|
var version = versions[0];
|
|
for (var i = 1; i < len; i++) {
|
|
if (compareDottedVersion(version, versions[i]) < 0) {
|
|
version = versions[i];
|
|
}
|
|
}
|
|
return version;
|
|
}
|
|
|
|
// Given a list of versions (as strings), return the latest version.
|
|
// Return undefined if no version could be found.
|
|
function latestVersion(versions) {
|
|
var version = '';
|
|
var origVersions = versions;
|
|
versions = versions.filter(function(version) {
|
|
return (/^v?[0-9]/).test(version);
|
|
});
|
|
try {
|
|
version = semver.maxSatisfying(versions, '');
|
|
} catch(e) {
|
|
version = latestDottedVersion(versions);
|
|
}
|
|
if (version === undefined) {
|
|
origVersions = origVersions.sort();
|
|
version = origVersions[origVersions.length - 1];
|
|
}
|
|
return version;
|
|
}
|
|
|
|
// Return a negative value if v1 < v2,
|
|
// zero if v1 = v2, a positive value otherwise.
|
|
function asciiVersionCompare(v1, v2) {
|
|
if (v1 < v2) {
|
|
return -1;
|
|
} else if (v1 > v2) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Remove the starting v in a string.
|
|
function omitv(version) {
|
|
if (version.charCodeAt(0) === 118) { // v
|
|
return version.slice(1);
|
|
} else {
|
|
return version;
|
|
}
|
|
}
|
|
|
|
// Take a version without the starting v.
|
|
// eg, '1.0.x-beta'
|
|
// Return { numbers: [1,0,something big], modifier: 2, modifierCount: 1 }
|
|
function phpNumberedVersionData(version) {
|
|
// A version has a numbered part and a modifier part
|
|
// (eg, 1.0.0-patch, 2.0.x-dev).
|
|
var parts = version.split('-');
|
|
var numbered = parts[0];
|
|
|
|
// Aliases that get caught here.
|
|
if (numbered === 'dev') {
|
|
return {
|
|
numbers: parts[1],
|
|
modifier: 5,
|
|
modifierCount: 1,
|
|
};
|
|
}
|
|
|
|
var modifierLevel = 3;
|
|
var modifierLevelCount = 0;
|
|
|
|
if (parts.length > 1) {
|
|
var modifier = parts[parts.length - 1];
|
|
var firstLetter = modifier.charCodeAt(0);
|
|
var modifierLevelCountString;
|
|
|
|
// Modifiers: alpha < beta < RC < normal < patch < dev
|
|
if (firstLetter === 97) { // a
|
|
modifierLevel = 0;
|
|
if (/^alpha/.test(modifier)) {
|
|
modifierLevelCountString = + (modifier.slice(5));
|
|
} else {
|
|
modifierLevelCountString = + (modifier.slice(1));
|
|
}
|
|
} else if (firstLetter === 98) { // b
|
|
modifierLevel = 1;
|
|
if (/^beta/.test(modifier)) {
|
|
modifierLevelCountString = + (modifier.slice(4));
|
|
} else {
|
|
modifierLevelCountString = + (modifier.slice(1));
|
|
}
|
|
} else if (firstLetter === 82) { // R
|
|
modifierLevel = 2;
|
|
modifierLevelCountString = + (modifier.slice(2));
|
|
} else if (firstLetter === 112) { // p
|
|
modifierLevel = 4;
|
|
if (/^patch/.test(modifier)) {
|
|
modifierLevelCountString = + (modifier.slice(5));
|
|
} else {
|
|
modifierLevelCountString = + (modifier.slice(1));
|
|
}
|
|
} else if (firstLetter === 100) { // d
|
|
modifierLevel = 5;
|
|
if (/^dev/.test(modifier)) {
|
|
modifierLevelCountString = + (modifier.slice(3));
|
|
} else {
|
|
modifierLevelCountString = + (modifier.slice(1));
|
|
}
|
|
}
|
|
|
|
// If we got the empty string, it defaults to a modifier count of 1.
|
|
if (!modifierLevelCountString) {
|
|
modifierLevelCount = 1;
|
|
} else {
|
|
modifierLevelCount = + modifierLevelCountString;
|
|
}
|
|
}
|
|
|
|
// Try to convert to a list of numbers.
|
|
var toNum = function(s) {
|
|
var n = +s;
|
|
if (n !== n) { // If n is NaN…
|
|
n = 0xffffffff;
|
|
}
|
|
return n;
|
|
};
|
|
var numberList = numbered.split('.').map(toNum);
|
|
|
|
return {
|
|
numbers: numberList,
|
|
modifier: modifierLevel,
|
|
modifierCount: modifierLevelCount,
|
|
};
|
|
}
|
|
|
|
function listCompare(a, b) {
|
|
var alen = a.length, blen = b.length;
|
|
for (var i = 0; i < alen; i++) {
|
|
if (a[i] < b[i]) {
|
|
return -1;
|
|
} else if (a[i] > b[i]) {
|
|
return 1;
|
|
}
|
|
}
|
|
return alen - blen;
|
|
}
|
|
|
|
// Return a negative value if v1 < v2,
|
|
// zero if v1 = v2,
|
|
// a positive value otherwise.
|
|
//
|
|
// See https://getcomposer.org/doc/04-schema.md#version
|
|
// and https://github.com/badges/shields/issues/319#issuecomment-74411045
|
|
function phpVersionCompare(v1, v2) {
|
|
// Omit the starting `v`.
|
|
var rawv1 = omitv(v1);
|
|
var rawv2 = omitv(v2);
|
|
try {
|
|
var v1data = phpNumberedVersionData(rawv1);
|
|
var v2data = phpNumberedVersionData(rawv2);
|
|
} catch(e) {
|
|
return asciiVersionCompare(rawv1, rawv2);
|
|
}
|
|
|
|
// Compare the numbered part (eg, 1.0.0 < 2.0.0).
|
|
var numbersCompare = listCompare(v1data.numbers, v2data.numbers);
|
|
if (numbersCompare !== 0) {
|
|
return numbersCompare;
|
|
}
|
|
|
|
// Compare the modifiers (eg, alpha < beta).
|
|
if (v1data.modifier < v2data.modifier) {
|
|
return -1;
|
|
} else if (v1data.modifier > v2data.modifier) {
|
|
return 1;
|
|
}
|
|
|
|
// Compare the modifier counts (eg, alpha1 < alpha3).
|
|
if (v1data.modifierCount < v2data.modifierCount) {
|
|
return -1;
|
|
} else if (v1data.modifierCount > v2data.modifierCount) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function phpLatestVersion(versions) {
|
|
var latest = versions[0];
|
|
for (var i = 1; i < versions.length; i++) {
|
|
if (phpVersionCompare(latest, versions[i]) < 0) {
|
|
latest = versions[i];
|
|
}
|
|
}
|
|
return latest;
|
|
}
|
|
|
|
// Whether a version is stable.
|
|
function phpStableVersion(version) {
|
|
var rawVersion = omitv(version);
|
|
try {
|
|
var versionData = phpNumberedVersionData(version);
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
// normal or patch
|
|
return (versionData.modifier === 3) || (versionData.modifier === 4);
|
|
}
|
|
|
|
// This searches the serverSecrets for a twitter consumer key
|
|
// and secret, and exchanges them for a bearer token to use for all requests.
|
|
function fetchTwitterToken() {
|
|
if(serverSecrets.twitter_consumer_key && serverSecrets.twitter_consumer_secret){
|
|
// fetch a bearer token good for this app session
|
|
// construct this bearer request with a base64 encoding of key:secret
|
|
// docs for this are here: https://dev.twitter.com/oauth/application-only
|
|
var twitter_bearer_credentials = escape(serverSecrets.twitter_consumer_key) + ':' + escape(serverSecrets.twitter_consumer_secret);
|
|
var options = {
|
|
url: 'https://api.twitter.com/oauth2/token',
|
|
headers: {
|
|
// is this the best way to base 64 encode a string?
|
|
Authorization: 'Basic '+(new Buffer(twitter_bearer_credentials)).toString('base64'),
|
|
'Content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
|
|
},
|
|
form: 'grant_type=client_credentials',
|
|
method: 'POST'
|
|
};
|
|
console.log('Fetching twitter bearer token...');
|
|
request(options,function(err,res,buffer){
|
|
if(err){
|
|
console.error('Error fetching twitter bearer token, error: ', err);
|
|
return;
|
|
}
|
|
try{
|
|
var data = JSON.parse(buffer);
|
|
if(data.token_type === 'bearer'){
|
|
serverSecrets.twitter_bearer_token = data.access_token;
|
|
console.log('Fetched twitter bearer token');
|
|
return;
|
|
}
|
|
console.error('Error fetching twitter bearer token, data: %j', data);
|
|
} catch(e) {
|
|
console.error('Error fetching twitter bearer token, buffer: %s, ', buffer, e);
|
|
}
|
|
});
|
|
}
|
|
}
|