loader.js
* @module greppy/http/mvc/loader
* @author Hermann Mayer <hermann.mayer92@gmail.com>
var basename = require('path').basename;
var dirname = require('path').dirname;
var normalize = require('path').normalize;
var ControllerLoader = function(worker)
* Gets the relative path of a controller.
* @param {String} path - The full path to the controller file
* @param {String} module - The name of the controller module
ControllerLoader.prototype.getRelativePath = function(path, module)
var modulePath = process.cwd() + '/modules/' + module + '/controllers';
if (-1 !== path.indexOf(normalize(__dirname + '/../../../'))) {
path = path.replace(path, '');
return path.replace(modulePath, '');
* Gets the canonical path to store the controller.
* @param {String} path - The full path to the controller file
* @param {String} controller - Name of the controller
* @param {String} module - The name of the controller module
ControllerLoader.prototype.getCanonicalPath = function(path, controller, module)
path = this.getRelativePath(path, module)
* Gets the base path for a given controller.
* @param {Object} controller - The controller object
* @param {String} path - The full path to the controller file
* @param {String} module - The name of the controller module
* @return {String} The complete base path of the controller
ControllerLoader.prototype.getBasePath = function(controller, path, module)
if (controller.options && controller.options.path) {
return controller.options.path;
var firstPath = this.getRelativePath(path, module);
var lastPath = ('index' === controller.name)
: '/' + controller.name.replace(/\.js$/g, '');
* Prepare actions for a given controller object.
* @param {Object} controller - Controller to prepare
* @param {Function} callback - Function to call on finish
ControllerLoader.prototype.prepareControllerActions = function(controller, callback)
// Just setup a clean output object
// Allow action less controllers
return callback(null, preparedActions);
async.each(Object.keys(controller.actions || []), function(name, callback) {
// Setup an internal alias of the action object
var action = controller.actions[name];
// If no explicit method(-array) was specified
action.methods = ['GET', 'POST', 'PUT', 'DELETE'];
var actionPath = (action.path)
: ('index' == name) ? '' : '/' + name;
// Build really clean route paths
var path = ((controller.basePath + actionPath)
.replace(/\/$/gi, '')) || '/';
// Check if the route is configured
// to be protected by an auth handler
// Setup any authentication options
if (controller.options && controller.options.auth) {
var handler = controller.options.auth.handler;
// We found a simple handler - as a direct function
if ('function' === typeof handler) {
// Our found handler seems to be an object, in
// this case we should check for a middleware
// function. If this middleware is present lets
if (handler && 'function' === typeof handler.middleware) {
handler.middleware.apply(handler, arguments);
// Disable authentication if the client has explicit
// specified to use the handler for no route (null)
if (auth && null === controller.options.auth.routes) {
// Authentication routes are specified by the client
util.isArray(controller.options.auth.routes) &&
0 !== controller.options.auth.routes.length) {
controller.options.auth.routes.forEach(function(authRoute) {
if (!authRoute instanceof RegExp) {
if (null === path.match(authRoute)) {
// Add the current action (in combination with all
// configured HTTP methods) to the output set
action.methods.forEach(function(method) {
controllerPath : controller.canonicalPath.replace(controller.module + '.', ''),
return callback && callback(err);
// Sort the actions - static routes first
preparedActions.sort(function(a, b) {
a = a.path.split(':').length - 1;
b = b.path.split(':').length - 1;
callback && callback(null, preparedActions);
* @param {String} path - Path to the controller to load
* @param {String} name - Name the controller
* @param {String} module - Name of the module to add the controller
* @param {Function} callback - Function to call on finish
ControllerLoader.prototype.loadController = function(path, name, module, callback)
var controller = require(path);
// Run the configure method of the current controller
function(controller, callback) {
// Allow configure-method less controllers
if ('function' !== typeof controller.configure) {
return callback(null, controller);
function(controller, callback) {
// Add the given name to the controller
// Set default value on the controller instance
controller.basePath = self.getBasePath(
controller.canonicalPath = self.getCanonicalPath(
// Prepare all actions of the controller
function(controller, callback) {
self.prepareControllerActions(controller, function(err, actions) {
callback(err, controller, actions);
// Register routes to the worker context
function(controller, actions, callback) {
self.worker.context.routes = self.worker.context.routes.concat(
], function(err, controller) {
// Store the loaded controller object
self.controllers[controller.canonicalPath] = controller;
* Load routes for given modules.
* @param {Array} modules - Modules to lookup
* @param {Function} callback - Function to call on finish
ControllerLoader.prototype.load = function(modules, callback)
var pathHelper = new (require('../../helper/path'))();
async.map(modules, function(module, callback) {
path: process.cwd() + '/modules/' +
// Check for valid modules - paths
async.filter(map, function(module, callback) {
fs.exists(module.path, callback);
// Search for javascript files
// Walk through all modules in parallel
async.map(map, function(module, callback) {
// Filter all non-js files in parallel
callback(path.match(/\.js$/i));
// Load controllers of the modules
// Walk through all modules in parallel
async.map(map, function(module, callback) {
logger.info('Loading ' + (module.name).blue + ' module');
// Walk through all possible controllers of the module
async.map(module.paths, function(path, callback) {
basename(path.replace(/\.js$/g, '')),
}, function(err, controllers) {
module.controllers = controllers;
* Apply all routes to the given application.
* @param {Object} app - App to prepare
ControllerLoader.prototype.configure = function(app)
this.worker.context.routes.forEach(function(route) {
// Prepare the action to be wrapped to enable
// this referencing to the controller instance
// and the default ability to use self
var preparedAction = function() {
route.callback.apply(route.thisArg, arguments);
// Also prepare bundled static helpers to support
Object.keys(route.thisArg.helpers || []).forEach(function(helper) {
var preparedHelper = route.thisArg.helpers[helper].bind(route.thisArg);
route.thisArg.helpers[helper] = function() {
preparedHelper.apply(route.thisArg, arguments);
return app[route.method.toLowerCase()](
route.path, route.auth, preparedAction
app[route.method.toLowerCase()](route.path, preparedAction);