Skip to content

Instantly share code, notes, and snippets.

@sixlettervariables
Created August 20, 2014 19:45
Show Gist options
  • Save sixlettervariables/94ae9a7e8450df8b9359 to your computer and use it in GitHub Desktop.
Save sixlettervariables/94ae9a7e8450df8b9359 to your computer and use it in GitHub Desktop.
Enrouten with swaggerize-express support
/**
* Rewrite of express-enrouten's directory handler to use 'routeify()'
*
*/
'use strict';
var path = require('path');
var express = require('express');
var routeify = require('./routeify');
module.exports = function directory(router, basedir) {
var routes, options;
options = {
fileChooser: isFileModule
};
routes = routeify(basedir, options);
Object.keys(routes).forEach(function (mountpath) {
var impl, filename, subrouter;
filename = routes[mountpath];
impl = require(pathToModuleId(filename));
if (typeof impl === 'function' && impl.length === 1) {
subrouter = express.Router({ mergeParams: true });
impl(subrouter);
/**CAW: DEBUG ***************************************/
subrouter.stack.forEach(function (handler) {
if (!handler.route) return;
var methods = "*";
if (handler.route.methods) {
methods = lpad(Object.keys(handler.route.methods).map(function (mm) { return mm.toUpperCase(); }).join(','), 8);
}
console.log('[router] %s %s (%s)', methods, lpad(mountpath, 40), filename);
});
/****************************************************/
router.use(mountpath, subrouter);
}
});
}
/** Left pad a string to a given length.
* @param str String to left pad with spaces.
* @param sz Size of the resulting string.
* @returns {String} [str] padded to [sz] places.
*/
var pad = ' ';
function lpad(str, sz) {
return str + (str.length < sz ? pad.substr(0, sz - str.length) : '');
};
/**
* Returns true if `require` is able to load the provided file
* or false if not.
* http://nodejs.org/api/modules.html#modules_file_modules
* @param file the file for which to determine module-ness.
* @returns {boolean}
*/
function isFileModule(file) {
var ext = path.extname(file);
var extensions = Object.keys(require.extensions);
return !!~extensions.indexOf(ext);
}
/**
* Gets a module ID from a path, helps with Windows and paths to modules.
*/
function pathToModuleId(pathname) {
var ext = path.extname(pathname);
var moduleId = ext ? pathname.slice(0, -ext.length) : pathname;
// CAW: for Windows
if (/\\/.test(moduleId)) {
moduleId = moduleId.replace(/\\/g, '/');
}
return './' + moduleId;
}
/**
* Sample app showing the behavior from this version of enrouten's directory
*/
var express = require('express');
var enrouten = require('./enrouten-directory');
var app = express();
app.use(function (req, res, next) {
console.log('[%s] %s', req.method, req.url);
next();
});
app.use('/static', express.static(__dirname + '/public'));
enrouten(app, './controllers');
app.use(function (req, res, next) {
return res.status(404).end('HTTP/1.1 404 Not Found');
});
app.use(function (err, req, res, next) {
return res.status(500).send('HTTP/1.1 500 Internal Server Error\n\n' + err);
});
app.listen(3000, function () {
console.log('[app] listening on port 3000 for requests');
});
'use strict';
var fs = require('fs');
var path = require('path');
/* EXAMPLE:
* var routes = routeify.mapDirectory('./controllers');
* Object.keys(routes).forEach(function (fn) {
* console.log('%s => %s', fn, routes[fn]);
* });
*
* Looks like:
*
* controllers/index.js => /
* controllers/companies/{company}.js => /companies/{company}
* controllers/products/{product}/update.js => /products/{product}/update
* controllers/products/{product}.js => /products/{product}
* controllers/suppliers/{supplier}/index.js => /suppliers/{supplier}
* controllers/users/index.js => /users
* controllers/users/{user}.js => /users/{user}
*
*
*
* `routeify.mapDirectory()` supports an `options` argument too:
*
* [options.excludeDotFiles] {Boolean} Excludes dotfiles as well (default: false)
* [options.fileChooser] {Function} Function of the form `function (filename)`
* which must return a {Boolean} value indicating
* whether the file should be mapped to a route.
*
* Example of `options.fileChooser` performing the work done in express-enrouten's
* `createFileHandler()` function:
*
* var options = {
* excludeDotFiles: true,
* fileChooser: function (filename) {
* var impl;
* if (isFileModule(filename)) {
* impl = require(filename);
* if (impl instanceof Function && impl.length === 1) {
* return true;
* }
* }
* return false;
* }
* };
*
* var routes = routeify.mapDirectory('./controllers', options);
*
*/
/**
* Maps handlers in directories to routes based on the filename
* and path to the file.
*
* @param basedir {String} Base directory to search for files.
* @param options {Object} Options used during traversal.
* @param options.fileChooser {Function} Function taking one argument (current filename) and returning
* a {boolean} value indicating whether the current file should
* be mapped to a route.
* @returns Returns a hash of local filenames to routes appropriate for mounting.
*/
module.exports = function mapDirectory(basedir, options) {
var routes = {}; // accumulated recursion
options || (options = {});
options.fileChooser = options.fileChooser && (options.fileChooser instanceof Function) ? options.fileChooser : null;
traverse(basedir, '', '', routes, options)
return routes;
}
/**
* Visits `current` w.r.t. `ancestors`, adding a route to the `routes`
* hash if a filename, or recursively visiting the children of `current`
* if it is a directory.
*/
function traverse(basedir, ancestors, current, routes, options) {
var abs, stat;
abs = path.join(basedir, ancestors, current);
stat = fs.statSync(abs);
// no dotted directories
if (stat.isDirectory() && current.charAt(0) !== '.') {
ancestors = ancestors ? path.join(ancestors, current) : current;
var items = fs.readdirSync(abs);
// sort items alphabetically and with 'index.js' first
items.sort(directorySorter);
items.forEach(function (child) {
traverse(basedir, ancestors, child, routes, options);
});
}
if (stat.isFile()) {
var mountpath, filename, route;
if (!options.fileChooser || options.fileChooser(abs)) {
mountpath = ancestors ? ancestors.split(path.sep) : [];
filename = path.basename(current, path.extname(current));
if (filename !== 'index') {
mountpath.push(filename);
}
route = createRouteFromPath(mountpath);
routes[route] = abs;
}
}
}
/**
* Converts `/a/b/{c}/d/{e}` to `/a/b/:c/d/:e`
*/
function createRouteFromPath(pathname) {
var reRouteSpec = /\{([^\}]+)\}/g;
return ('/' + pathname.join('/')).replace(reRouteSpec, ':$1');
}
/**
* Sorts `index.js` before all other items.
*/
function directorySorter(a, b) {
if (a === b) {
return 0;
}
else if (a === 'index.js') {
return -2;
}
else if (b === 'index.js') {
return 2;
}
else {
return (a < b) ? -1 : 1;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment