Skip to content

Commit

Permalink
Code review comments:
Browse files Browse the repository at this point in the history
- Move AppVeyor implementation to separate file
- Add support for default badge data, for things like logos
- Remove try-catch block from AppVeyor code in favour of moving it further up the stack
- Explciitly name capture groups by using an array
  • Loading branch information
Daniel15 committed May 2, 2017
1 parent 23b0ea4 commit a57827d
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 91 deletions.
8 changes: 7 additions & 1 deletion lib/load-logos.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
var fs = require('fs');
var path = require('path');

let logos;
var loadLogos = function() {
if (logos) {
// Logos have already been loaded; just returned the cached version
return logos;
}

// Cache svg logos from disk in base64 string
var logos = {};
logos = {};
var logoDir = path.join(__dirname, '..', 'logo');
var logoFiles = fs.readdirSync(logoDir);
logoFiles.forEach(function(filename) {
Expand Down
162 changes: 72 additions & 90 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,78 @@ camp.notfound(/.*/, function(query, match, end, request) {
end(null, {template: '404.html'});
});

/**
* Registers a service in the shields.io system.
*
* `config` is an object containing these keys:
* - name: Name of the service
* - type: Type of the service (build, downloads, version, etc)
* - uri:
* - format: Regular expression to use for URIs for this service's badges
* - capture: Array of names for the capture groups in the regular
* expression. The handler will be passed an object containing
* the matches.
* - examples: Example URIs for this service. These should use the format
* specified in `uri`, and can be used to demonstrate how to use badges for
* this service.
* - handler: Asynchronous function to handle requests for this service. This
* function takes two arguments:
* 1. Capture groups from the `uri` regular expression
* 2. A `request` method for sending cached HTTP requests to third-party
* servers.
*
* Optional configuration:
* - defaultBadgeData: Default data for the badge. Can include things such as
* default logo, color, etc. These defaults will be used if the value is not
* explicitly overridden by either the handler or by the user via URL
* parameters.
*/
function registerService(config) {
// Regular expressions treat "/" specially, so we need to escape them
const escapedPath = config.uri.format.replace(/\//g, '\\/');
const fullRegex = '^' + escapedPath + '.(svg|png|gif|jpg|json)$';

camp.route(new RegExp(fullRegex),
cache((data, match, sendBadge, request) => {
// Assumes the final capture group is the extension
const format = match.pop();
const namedParams = {};
// TODO: This should probably warn if match.length !== config.uri.capture.length + 1
config.uri.capture.forEach((name, index) => {
// The first capture group is the entire match, so every index is + 1 here
namedParams[name] = match[index + 1];
});

const badgeData = getBadgeData(
config.type,
Object.assign({}, config.defaultBadgeData, data)
);
config.handler(namedParams, request.asPromise)
.then(serviceData => {
let text = badgeData.text;
if (serviceData.text) {
text[1] = serviceData.text;
}
Object.assign(badgeData, serviceData);
badgeData.text = text;
sendBadge(format, badgeData);
})
.catch(error => {
const text = badgeData.text;
text[1] = 'error';
badgeData.text = text;
sendBadge(format, badgeData);
});
}));
}

/**
* Registers multiple services. `services` is an array of service objects
* accepted by `registerService`.
*/
function registerServices(services) {
services.forEach(service => registerService(service));
}

// Vendors.

Expand Down Expand Up @@ -697,97 +768,8 @@ cache(function (data, match, sendBadge, request) {
});
}));

/**
* Registers a service in the shields.io system.
*
* `config` is an object containing these keys:
* - name: Name of the service
* - type: Type of the service (build, downloads, version, etc)
* - uri: Regular expression to use for URIs for this service's badges
* - examples: Example URIs for this service. These should use the format
* specified in `uri`, and can be used to demonstrate how to use badges for
* this service.
* - handler: Asynchronous function to handle requests for this service. This
* function takes two arguments:
* 1. Capture groups from the `uri` regular expression
* 2. A `request` method for sending cached HTTP requests to third-party
* servers.
*/
function registerService(config) {
// Regular expressions treat "/" specially, so we need to escape them
const escapedPath = config.uri.replace(/\//g, '\\/');
const fullRegex = '^' + escapedPath + '.(svg|png|gif|jpg|json)$';

camp.route(new RegExp(fullRegex),
cache(function(data, match, sendBadge, request) {
// Assumes the final capture group is the extension
const format = match.pop();
// Discard the first capture group as it's the entire match
const params = match.slice(1);

const badgeData = getBadgeData(config.type, data);
config.handler(params, request.asPromise)
.then(serviceData => {
let text = badgeData.text;
if (serviceData.text) {
text[1] = serviceData.text;
}
Object.assign(badgeData, serviceData);
badgeData.text = text;
sendBadge(format, badgeData);
})
.catch(error => {
console.error(error);
const text = badgeData.text;
text[1] = 'error';
badgeData.text = text;
sendBadge(format, badgeData);
});
}));
}

// AppVeyor CI integration.
registerService({
name: 'AppVeyor',
type: 'build',
uri: '/appveyor/ci/([^/]+/[^/]+)(?:/(.+))?',
examples: [
{uri: '/appveyor/ci/gruntjs/grunt'},
{
name: 'Branch',
params: '/appveyor/ci/gruntjs/grunt/master',
},
],
handler: async ([repo, branch], request) => {
let apiUrl = 'https://ci.appveyor.com/api/projects/' + repo;
if (branch != null) {
apiUrl += '/branch/' + branch;
}

let result;
try {
result = await request(apiUrl, {
headers: { 'Accept': 'application/json' }
});
} catch (e) {
return {text: 'inaccessible'};
}

try {
const data = JSON.parse(result.buffer);
const status = data.build.status;
if (status === 'success') {
return {text: 'passing', colorscheme: 'brightgreen'};
} else if (status !== 'running' && status !== 'queued') {
return {text: 'failing', colorscheme: 'red'};
} else {
return {text: status};
}
} catch(e) {
return {text: 'invalid'};
}
},
});
registerServices(require('./services/appveyor'));

function teamcity_badge(url, buildId, advanced, format, data, sendBadge) {
var apiUrl = url + '/app/rest/builds/buildType:(id:' +