Split various utilities into standalone files (#952)

This commit is contained in:
Daniel Lo Nigro 2017-04-24 15:37:22 -07:00 committed by Paul Melnikow
parent 108533e08e
commit ccbdad69ba
5 changed files with 372 additions and 309 deletions

45
lib/color-formatters.js Normal file
View File

@ -0,0 +1,45 @@
/**
* Commonly-used functions for determining the colour to use for a badge,
* including colours based off download count, version number, etc.
*/
'use strict';
function versionFormatter(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' };
}
}
exports.version = versionFormatter;
function downloadCount(downloads) {
return floorCount(downloads, 10, 100, 1000);
}
exports.downloadCount = downloadCount;
function coveragePercentage(percentage) {
return floorCount(percentage, 80, 90, 100);
}
exports.coveragePercentage = coveragePercentage;
function floorCount(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';
}
}
exports.floorCount = floorCount;

181
lib/php-version.js Normal file
View File

@ -0,0 +1,181 @@
/**
* Utilities relating to PHP version numbers. This compares version numbers
* using the algorithm followed by Composer (see
* https://getcomposer.org/doc/04-schema.md#version).
*/
'use strict';
const {listCompare} = require('./version.js');
// 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 compare(v1, v2) {
// Omit the starting `v`.
var rawv1 = omitv(v1);
var rawv2 = omitv(v2);
try {
var v1data = numberedVersionData(rawv1);
var v2data = numberedVersionData(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;
}
exports.compare = compare;
function latest(versions) {
var latest = versions[0];
for (var i = 1; i < versions.length; i++) {
if (compare(latest, versions[i]) < 0) {
latest = versions[i];
}
}
return latest;
}
exports.latest = latest;
// Whether a version is stable.
function isStable(version) {
var rawVersion = omitv(version);
try {
var versionData = numberedVersionData(rawVersion);
} catch(e) {
return false;
}
// normal or patch
return (versionData.modifier === 3) || (versionData.modifier === 4);
}
exports.isStable = isStable;
// === Private helper functions ===
// Remove the starting v in a string.
function omitv(version) {
if (version.charCodeAt(0) === 118) { // v
return version.slice(1);
} else {
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;
}
}
// Take a version without the starting v.
// eg, '1.0.x-beta'
// Return { numbers: [1,0,something big], modifier: 2, modifierCount: 1 }
function numberedVersionData(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,
};
}

47
lib/text-formatters.js Normal file
View File

@ -0,0 +1,47 @@
/**
* Commonly-used functions for formatting text in badge labels. Includes
* ordinal numbers, currency codes, star ratings, etc.
*/
'use strict';
function starRating(rating) {
var stars = '';
while (stars.length < rating) { stars += '★'; }
while (stars.length < 5) { stars += '☆'; }
return stars;
}
exports.starRating = starRating;
// Convert ISO 4217 code to unicode string.
function currencyFromCode(code) {
return ({
CNY: '¥',
EUR: '€',
GBP: '₤',
USD: '$',
})[code] || code;
}
exports.currencyFromCode = currencyFromCode;
function ordinalNumber(n) {
var s=["ᵗʰ","ˢᵗ","ⁿᵈ","ʳᵈ"], v=n%100;
return n+(s[(v-20)%10]||s[v]||s[0]);
}
exports.ordinalNumber = ordinalNumber;
// 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;
}
exports.metric = metric;

80
lib/version.js Normal file
View File

@ -0,0 +1,80 @@
/**
* Utilities relating to generating badges relating to version numbers. Includes
* comparing versions to determine the latest, and determining the color to use
* for the badge based on whether the version is a stable release.
*
* For utilities specific to PHP version ranges, see php-version.js.
*/
'use strict';
const semver = require('semver');
// Given a list of versions (as strings), return the latest version.
// Return undefined if no version could be found.
function latest(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;
}
exports.latest = latest;
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;
}
exports.listCompare = listCompare;
// === Private helper functions ===
// 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;
}
// 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;
}

328
server.js
View File

@ -38,6 +38,25 @@ if (serverSecrets && serverSecrets.gh_client_id) {
githubAuth.setRoutes(camp);
}
const {latest: latestVersion} = require('./lib/version.js');
const {
compare: phpVersionCompare,
latest: phpLatestVersion,
isStable: phpStableVersion,
} = require('./lib/php-version.js');
const {
currencyFromCode,
metric,
ordinalNumber,
starRating,
} = require('./lib/text-formatters.js');
const {
coveragePercentage: coveragePercentageColor,
downloadCount: downloadCountColor,
floorCount: floorCountColor,
version: versionColor,
} = require('./lib/color-formatters.js');
var semver = require('semver');
var serverStartTime = new Date((new Date()).toGMTString());
@ -6466,23 +6485,6 @@ function regularUpdate(url, interval, scraper, cb) {
});
}
// 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) {
@ -6498,295 +6500,3 @@ function fetchFromSvg(request, url, cb) {
}
});
}
function ordinalNumber(n) {
var s=["ᵗʰ","ˢᵗ","ⁿᵈ","ʳᵈ"], v=n%100;
return n+(s[(v-20)%10]||s[v]||s[0]);
}
// Convert ISO 4217 code to unicode string.
function currencyFromCode(code) {
return ({
CNY: '¥',
EUR: '€',
GBP: '₤',
USD: '$',
})[code] || code;
}
function starRating(rating) {
var stars = '';
while (stars.length < rating) { stars += '★'; }
while (stars.length < 5) { stars += '☆'; }
return stars;
}
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(rawVersion);
} catch(e) {
return false;
}
// normal or patch
return (versionData.modifier === 3) || (versionData.modifier === 4);
}