Skip to content

Instantly share code, notes, and snippets.

@sixlettervariables
Last active August 29, 2015 14:05
Show Gist options
  • Save sixlettervariables/0c8c9589c7c0e20c7335 to your computer and use it in GitHub Desktop.
Save sixlettervariables/0c8c9589c7c0e20c7335 to your computer and use it in GitHub Desktop.
Provides Filename to Route hash from a swaggerized directory structure
'use strict';
var fs = require('fs');
var path = require('path');
/* EXAMPLE:
* var routes = routeifyDirectory('./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}
*
*
* `routeifyDirectory()` 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 = routeifyDirectory('./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.excludeDotFiles {Boolean} Value indicating whether dotfiles should be excluded
* from the mapping. Default: false.
* @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.
*/
function routeifyDirectory(basedir, options) {
var routes = {}; // accumulated recursion
options || (options = {});
options.excludeDotFiles = options.excludeDotFiles || false;
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) {
// skip addition of dot files if configured
if (!options.excludeDotFiles || child.charAt(0) !== '.') {
traverse(basedir, ancestors, child, routes, options);
}
});
}
if (stat.isFile()) {
var mountpath, filename;
// CAW: optionally exclude files based on a test
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);
}
routes[abs] = createRouteFromPath(mountpath);
}
}
}
/**
* 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;
if (a === 'index.js') return -2;
if (b === 'index.js') return 2;
return (a < b) ? -1 : 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment