]> git.cworth.org Git - obsolete/notmuch-web/blobdiff - node_modules/express/node_modules/connect/lib/middleware/router.js
Install the "express" node module via npm
[obsolete/notmuch-web] / node_modules / express / node_modules / connect / lib / middleware / router.js
diff --git a/node_modules/express/node_modules/connect/lib/middleware/router.js b/node_modules/express/node_modules/connect/lib/middleware/router.js
new file mode 100644 (file)
index 0000000..a07452e
--- /dev/null
@@ -0,0 +1,379 @@
+
+/*!
+ * Connect - router
+ * Copyright(c) 2010 Sencha Inc.
+ * Copyright(c) 2011 TJ Holowaychuk
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var utils = require('../utils')
+  , parse = require('url').parse;
+
+/**
+ * Expose router.
+ */
+
+exports = module.exports = router;
+
+/**
+ * Supported HTTP / WebDAV methods.
+ */
+
+var _methods = exports.methods = [
+    'get'
+  , 'post'
+  , 'put'
+  , 'delete'
+  , 'connect'
+  , 'options'
+  , 'trace'
+  , 'copy'
+  , 'lock'
+  , 'mkcol'
+  , 'move'
+  , 'propfind'
+  , 'proppatch'
+  , 'unlock'
+  , 'report'
+  , 'mkactivity'
+  , 'checkout'
+  , 'merge'
+];
+
+/**
+ * Provides Sinatra and Express-like routing capabilities.
+ *
+ * Examples:
+ *
+ *     connect.router(function(app){
+ *       app.get('/user/:id', function(req, res, next){
+ *         // populates req.params.id
+ *       });
+ *       app.put('/user/:id', function(req, res, next){
+ *         // populates req.params.id
+ *       });
+ *     })
+ *
+ * @param {Function} fn
+ * @return {Function}
+ * @api public
+ */
+
+function router(fn){
+  var self = this
+    , methods = {}
+    , routes = {}
+    , params = {};
+
+  if (!fn) throw new Error('router provider requires a callback function');
+
+  // Generate method functions
+  _methods.forEach(function(method){
+    methods[method] = generateMethodFunction(method.toUpperCase());
+  });
+
+  // Alias del -> delete
+  methods.del = methods.delete;
+
+  // Apply callback to all methods
+  methods.all = function(){
+    var args = arguments;
+    _methods.forEach(function(name){
+      methods[name].apply(this, args);
+    });
+    return self;
+  };
+
+  // Register param callback
+  methods.param = function(name, fn){
+    params[name] = fn;
+  };
+      
+  fn.call(this, methods);
+
+  function generateMethodFunction(name) {
+    var localRoutes = routes[name] = routes[name] || [];
+    return function(path, fn){
+      var keys = []
+        , middleware = [];
+
+      // slice middleware
+      if (arguments.length > 2) {
+        middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
+        fn = middleware.pop();
+        middleware = utils.flatten(middleware);
+      }
+
+      fn.middleware = middleware;
+
+      if (!path) throw new Error(name + ' route requires a path');
+      if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
+      var regexp = path instanceof RegExp
+        ? path
+        : normalizePath(path, keys);
+      localRoutes.push({
+          fn: fn
+        , path: regexp
+        , keys: keys
+        , orig: path
+        , method: name
+      });
+      return self;
+    };
+  }
+
+  function router(req, res, next){
+    var route
+      , self = this;
+
+    (function pass(i){
+      if (route = match(req, routes, i)) {
+        var i = 0
+          , keys = route.keys;
+
+        req.params = route.params;
+
+        // Param preconditions
+        (function param(err) {
+          try {
+            var key = keys[i++]
+              , val = req.params[key]
+              , fn = params[key];
+
+            if ('route' == err) {
+              pass(req._route_index + 1);
+            // Error
+            } else if (err) {
+              next(err);
+            // Param has callback
+            } else if (fn) {
+              // Return style
+              if (1 == fn.length) {
+                req.params[key] = fn(val);
+                param();
+              // Middleware style
+              } else {
+                fn(req, res, param, val);
+              }
+            // Finished processing params
+            } else if (!key) {
+              // route middleware
+              i = 0;
+              (function nextMiddleware(err){
+                var fn = route.middleware[i++];
+                if ('route' == err) {
+                  pass(req._route_index + 1);
+                } else if (err) {
+                  next(err);
+                } else if (fn) {
+                  fn(req, res, nextMiddleware);
+                } else {
+                  route.call(self, req, res, function(err){
+                    if (err) {
+                      next(err);
+                    } else {
+                      pass(req._route_index + 1);
+                    }
+                  });
+                }
+              })();
+            // More params
+            } else {
+              param();
+            }
+          } catch (err) {
+            next(err);
+          }
+        })();
+      } else if ('OPTIONS' == req.method) {
+        options(req, res, routes);
+      } else {
+        next();
+      }
+    })();
+  };
+
+  router.remove = function(path, method){
+    var fns = router.lookup(path, method);
+    fns.forEach(function(fn){
+      routes[fn.method].splice(fn.index, 1);
+    });
+  };
+
+  router.lookup = function(path, method, ret){
+    ret = ret || [];
+
+    // method specific lookup
+    if (method) {
+      method = method.toUpperCase();
+      if (routes[method]) {
+        routes[method].forEach(function(route, i){
+          if (path == route.orig) {
+            var fn = route.fn;
+            fn.regexp = route.path;
+            fn.keys = route.keys;
+            fn.path = route.orig;
+            fn.method = route.method;
+            fn.index = i;
+            ret.push(fn);
+          }
+        });
+      }
+    // global lookup
+    } else {
+      _methods.forEach(function(method){
+        router.lookup(path, method, ret);
+      });
+    }
+
+    return ret;
+  };
+
+  router.match = function(url, method, ret){
+    var ret = ret || []
+      , i = 0
+      , fn
+      , req;
+
+    // method specific matches
+    if (method) {
+      method = method.toUpperCase();
+      req = { url: url, method: method };
+      while (fn = match(req, routes, i)) {
+        i = req._route_index + 1;
+        ret.push(fn);
+      } 
+    // global matches
+    } else {
+      _methods.forEach(function(method){
+        router.match(url, method, ret);
+      });
+    }
+
+    return ret;
+  };
+
+  return router;
+}
+
+/**
+ * Respond to OPTIONS.
+ *
+ * @param {ServerRequest} req
+ * @param {ServerResponse} req
+ * @param {Array} routes
+ * @api private
+ */
+
+function options(req, res, routes) {
+  var pathname = parse(req.url).pathname
+    , body = optionsFor(pathname, routes).join(',');
+  res.writeHead(200, {
+      'Content-Length': body.length
+    , 'Allow': body
+  });
+  res.end(body);
+}
+
+/**
+ * Return OPTIONS array for the given `path`, matching `routes`.
+ *
+ * @param {String} path
+ * @param {Array} routes
+ * @return {Array}
+ * @api private
+ */
+
+function optionsFor(path, routes) {
+  return _methods.filter(function(method){
+    var arr = routes[method.toUpperCase()];
+    for (var i = 0, len = arr.length; i < len; ++i) {
+      if (arr[i].path.test(path)) return true;
+    }
+  }).map(function(method){
+    return method.toUpperCase();
+  });
+}
+
+/**
+ * Normalize the given path string,
+ * returning a regular expression.
+ *
+ * An empty array should be passed,
+ * which will contain the placeholder
+ * key names. For example "/user/:id" will
+ * then contain ["id"].
+ *
+ * @param  {String} path
+ * @param  {Array} keys
+ * @return {RegExp}
+ * @api private
+ */
+
+function normalizePath(path, keys) {
+  path = path
+    .concat('/?')
+    .replace(/\/\(/g, '(?:/')
+    .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
+      keys.push(key);
+      slash = slash || '';
+      return ''
+        + (optional ? '' : slash)
+        + '(?:'
+        + (optional ? slash : '')
+        + (format || '') + (capture || '([^/]+?)') + ')'
+        + (optional || '');
+    })
+    .replace(/([\/.])/g, '\\$1')
+    .replace(/\*/g, '(.+)');
+  return new RegExp('^' + path + '$', 'i');
+}
+
+/**
+ * Attempt to match the given request to
+ * one of the routes. When successful
+ * a route function is returned.
+ *
+ * @param  {ServerRequest} req
+ * @param  {Object} routes
+ * @return {Function}
+ * @api private
+ */
+
+function match(req, routes, i) {
+  var captures
+    , method = req.method
+    , i = i || 0;
+  if ('HEAD' == method) method = 'GET';
+  if (routes = routes[method]) {
+    var url = parse(req.url)
+      , pathname = url.pathname;
+    for (var len = routes.length; i < len; ++i) {
+      var route = routes[i]
+        , fn = route.fn
+        , path = route.path
+        , keys = fn.keys = route.keys;
+      if (captures = path.exec(pathname)) {
+        fn.method = method;
+        fn.params = [];
+        for (var j = 1, len = captures.length; j < len; ++j) {
+          var key = keys[j-1],
+            val = typeof captures[j] === 'string'
+              ? decodeURIComponent(captures[j])
+              : captures[j];
+          if (key) {
+            fn.params[key] = val;
+          } else {
+            fn.params.push(val);
+          }
+        }
+        req._route_index = i;
+        return fn;
+      }
+    }
+  }
+}