Created
August 20, 2014 19:45
-
-
Save sixlettervariables/94ae9a7e8450df8b9359 to your computer and use it in GitHub Desktop.
Enrouten with swaggerize-express support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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'); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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