You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
4.3 KiB
JavaScript
194 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
//# vi: ft=javascript
|
|
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var exec = require('child_process').exec;
|
|
|
|
var _ = require('lodash');
|
|
var async = require('async');
|
|
var express = require('express');
|
|
var handlebars = require('handlebars');
|
|
|
|
var app = express();
|
|
var staticHandler = express['static'].bind(express);
|
|
|
|
app.use((function log() {
|
|
var text = '{{date}} {{req.method}} {{req.url}}';
|
|
var logTemplate = handlebars.compile(text);
|
|
|
|
return function (req, res, next) {
|
|
console.log(logTemplate({date: new Date().toISOString(), req: req}));
|
|
next();
|
|
};
|
|
}()));
|
|
|
|
function renderError(res, err) {
|
|
console.error(err);
|
|
res.writeHead(500, {'Content-Type': 'text/plain'});
|
|
res.write(err);
|
|
}
|
|
|
|
function pathToFile() {
|
|
return path.normalize(
|
|
path.join.apply(null, [__dirname, '..'].concat(_.toArray(arguments)))
|
|
);
|
|
}
|
|
|
|
function index(req, res) {
|
|
var indexFile = pathToFile('assets', 'index.html');
|
|
|
|
function compileServe(err, text) {
|
|
if (err) {
|
|
renderError(res, err);
|
|
return;
|
|
}
|
|
var tmpl = handlebars.compile(text);
|
|
res.send(tmpl());
|
|
}
|
|
|
|
fs.readFile(indexFile, {encoding: 'utf8'}, compileServe);
|
|
}
|
|
|
|
app.get('/', index);
|
|
|
|
function serveIfExists(path) {
|
|
return function _serveIfExists(cb) {
|
|
fs.exists(path, function (exists) {
|
|
if (!exists) {
|
|
cb(null, null);
|
|
return;
|
|
}
|
|
fs.readFile(path, {encoding: 'utf8'}, cb);
|
|
});
|
|
};
|
|
}
|
|
|
|
app.get('/slides', function showHandler(req, res) {
|
|
fs.readdir(pathToFile('data', 'slide'), function (err, files) {
|
|
files.sort();
|
|
var jsonFiles = _.filter(files, function (file) {
|
|
return /^\d+\.json$/.test(file);
|
|
});
|
|
|
|
res.send(_.map(jsonFiles, function (file) {
|
|
return {
|
|
id: parseInt(file.replace(/\.json$/, ''), 10)
|
|
};
|
|
}));
|
|
});
|
|
});
|
|
|
|
app.get(/slide\/(\d+)/, function showHandler(req, res, next) {
|
|
if (!req.xhr) {
|
|
index(req, res);
|
|
return;
|
|
}
|
|
|
|
var slideId = req.params[0];
|
|
var slidePath = pathToFile('data', 'slide', slideId + '.json');
|
|
|
|
fs.exists(slidePath, function (exists) {
|
|
if (!exists) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
fs.readFile(slidePath, {encoding: 'utf8'}, function (err, content) {
|
|
if (err) {
|
|
renderError(res, err);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var slide = JSON.parse(content);
|
|
} catch (err) {
|
|
renderError(res, err);
|
|
return;
|
|
}
|
|
|
|
async.parallel({
|
|
copy: serveIfExists(pathToFile('data', 'copy', slide.copy + '.md')),
|
|
code: serveIfExists(pathToFile('data', 'code', slide.code + '.js'))
|
|
}, function (err, slideContent) {
|
|
if (err) {
|
|
renderError(res, err);
|
|
return;
|
|
}
|
|
slideContent.id = parseInt(slideId, 10);
|
|
slideContent.title = slide.title || '';
|
|
res.send(slideContent);
|
|
});
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
app.get(/results\/([^\/]+)\/(\d\d)$/, function resultsHandler(req, res, next) {
|
|
var app = req.params[0];
|
|
var slideId = req.params[1];
|
|
|
|
if (!_.contains(['node', 'phantomjs-1.9.8', 'phantomjs-2.0.0'], app)) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
var slidePath = pathToFile('data', 'slide', slideId + '.json');
|
|
fs.exists(slidePath, function (exists) {
|
|
if (!exists) {
|
|
return next();
|
|
}
|
|
|
|
var noSlideError = new Error('No code for slide');
|
|
async.waterfall([
|
|
|
|
function (cb) {
|
|
fs.readFile(slidePath, {encoding: 'utf8'}, cb);
|
|
},
|
|
|
|
function (content, cb) {
|
|
try {
|
|
var slide = JSON.parse(content);
|
|
} catch (err) {
|
|
cb(err, null);
|
|
return;
|
|
}
|
|
cb(null, slide);
|
|
},
|
|
|
|
function (slide, cb) {
|
|
var codePath = pathToFile('data', 'code', slide.code + '.js');
|
|
fs.exists(codePath, function (exists) {
|
|
if (exists) {
|
|
cb(null, codePath);
|
|
} else {
|
|
cb(noSlideError);
|
|
}
|
|
});
|
|
},
|
|
|
|
function (path, cb) {
|
|
exec(app + ' ' + path, cb);
|
|
}
|
|
|
|
], function (err, stdout, stderr) {
|
|
if (err === noSlideError) {
|
|
next();
|
|
return;
|
|
}
|
|
if (err) {
|
|
renderError(res, err);
|
|
return;
|
|
}
|
|
res.send({stdout: stdout, stderr: stderr});
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
app.use('/data', staticHandler('data'));
|
|
app.use('/vendor', staticHandler('vendor'));
|
|
app.use('/static', staticHandler('static'));
|
|
|
|
app.listen(8000);
|