]> git.cworth.org Git - obsolete/notmuch-web/blob - node_modules/express/lib/router/index.js
Install the "express" node module via npm
[obsolete/notmuch-web] / node_modules / express / lib / router / index.js
1
2 /*!
3  * Express - router
4  * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
5  * MIT Licensed
6  */
7
8 /**
9  * Module dependencies.
10  */
11
12 var utils = require('../utils')
13   , parse = require('url').parse
14   , _methods = require('./methods')
15   , Route = require('./route');
16
17 /**
18  * Expose router.
19  */
20
21 exports = module.exports = router;
22
23 /**
24  * Expose methods.
25  */
26
27 exports.methods = _methods;
28
29 /**
30  * Provides Sinatra-like routing capabilities.
31  *
32  * @param {Function} fn
33  * @return {Function}
34  * @api private
35  */
36
37 function router(fn, app){
38   var self = this
39     , methods = {}
40     , routes = {}
41     , params = {};
42
43   if (!fn) throw new Error('router provider requires a callback function');
44
45   // Generate method functions
46   _methods.forEach(function(method){
47     methods[method] = generateMethodFunction(method.toUpperCase());
48   });
49
50   // Alias del -> delete
51   methods.del = methods.delete;
52
53   // Apply callback to all methods
54   methods.all = function(){
55     var args = arguments;
56     _methods.forEach(function(name){
57       methods[name].apply(this, args);
58     });
59     return self;
60   };
61
62   // Register param callback
63   methods.param = function(name, fn){
64     params[name] = fn;
65   };
66       
67   fn.call(this, methods);
68
69   function generateMethodFunction(name) {
70     var localRoutes = routes[name] = routes[name] || [];
71     return function(path, fn){
72       var keys = []
73         , middleware = [];
74
75       // slice middleware
76       if (arguments.length > 2) {
77         middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
78         fn = middleware.pop();
79         middleware = utils.flatten(middleware);
80       }
81
82       if (!path) throw new Error(name + ' route requires a path');
83       if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
84
85       var options = { sensitive: app.enabled('case sensitive routes') };
86       var route = new Route(name, path, fn, options);
87       route.middleware = middleware;
88       localRoutes.push(route);
89       return self;
90     };
91   }
92
93   function router(req, res, next){
94     var route
95       , self = this;
96
97     (function pass(i){
98       if (route = match(req, routes, i)) {
99         var i = 0
100           , keys = route.keys;
101
102         req.params = route.params;
103
104         // Param preconditions
105         (function param(err) {
106           try {
107             var key = keys[i++]
108               , val = req.params[key]
109               , fn = params[key];
110
111             if ('route' == err) {
112               pass(req._route_index + 1);
113             // Error
114             } else if (err) {
115               next(err);
116             // Param has callback
117             } else if (fn) {
118               // Return style
119               if (1 == fn.length) {
120                 req.params[key] = fn(val);
121                 param();
122               // Middleware style
123               } else {
124                 fn(req, res, param, val);
125               }
126             // Finished processing params
127             } else if (!key) {
128               // route middleware
129               i = 0;
130               (function nextMiddleware(err){
131                 var fn = route.middleware[i++];
132                 if ('route' == err) {
133                   pass(req._route_index + 1);
134                 } else if (err) {
135                   next(err);
136                 } else if (fn) {
137                   fn(req, res, nextMiddleware);
138                 } else {
139                   route.callback.call(self, req, res, function(err){
140                     if (err) {
141                       next(err);
142                     } else {
143                       pass(req._route_index + 1);
144                     }
145                   });
146                 }
147               })();
148             // More params
149             } else {
150               param();
151             }
152           } catch (err) {
153             next(err);
154           }
155         })();
156       } else if ('OPTIONS' == req.method) {
157         options(req, res, routes);
158       } else {
159         next();
160       }
161     })();
162   };
163
164   router.remove = function(path, method, ret){
165     var ret = ret || []
166       , route;
167
168     // method specific remove
169     if (method) {
170       method = method.toUpperCase();
171       if (routes[method]) {
172         for (var i = 0; i < routes[method].length; ++i) {
173           route = routes[method][i];
174           if (path == route.path) {
175             route.index = i;
176             routes[method].splice(i, 1);
177             ret.push(route);
178             --i;
179           }
180         }
181       }
182     // global remove
183     } else {
184       _methods.forEach(function(method){
185         router.remove(path, method, ret);
186       });
187     }
188
189     return ret;
190   };
191
192   router.lookup = function(path, method, ret){
193     ret = ret || [];
194
195     // method specific lookup
196     if (method) {
197       method = method.toUpperCase();
198       if (routes[method]) {
199         routes[method].forEach(function(route, i){
200           if (path == route.path) {
201             route.index = i;
202             ret.push(route);
203           }
204         });
205       }
206     // global lookup
207     } else {
208       _methods.forEach(function(method){
209         router.lookup(path, method, ret);
210       });
211     }
212
213     return ret;
214   };
215
216   router.match = function(url, method, ret){
217     var ret = ret || []
218       , i = 0
219       , route
220       , req;
221
222     // method specific matches
223     if (method) {
224       method = method.toUpperCase();
225       req = { url: url, method: method };
226       while (route = match(req, routes, i)) {
227         i = req._route_index + 1;
228         route.index = i;
229         ret.push(route);
230       } 
231     // global matches
232     } else {
233       _methods.forEach(function(method){
234         router.match(url, method, ret);
235       });
236     }
237
238     return ret;
239   };
240
241   return router;
242 }
243
244 /**
245  * Respond to OPTIONS.
246  *
247  * @param {ServerRequest} req
248  * @param {ServerResponse} req
249  * @param {Array} routes
250  * @api private
251  */
252
253 function options(req, res, routes) {
254   var pathname = parse(req.url).pathname
255     , body = optionsFor(pathname, routes).join(',');
256   res.send(body, { Allow: body });
257 }
258
259 /**
260  * Return OPTIONS array for the given `path`, matching `routes`.
261  *
262  * @param {String} path
263  * @param {Array} routes
264  * @return {Array}
265  * @api private
266  */
267
268 function optionsFor(path, routes) {
269   return _methods.filter(function(method){
270     var arr = routes[method.toUpperCase()];
271     for (var i = 0, len = arr.length; i < len; ++i) {
272       if (arr[i].regexp.test(path)) return true;
273     }
274   }).map(function(method){
275     return method.toUpperCase();
276   });
277 }
278
279 /**
280  * Attempt to match the given request to
281  * one of the routes. When successful
282  * a route function is returned.
283  *
284  * @param  {ServerRequest} req
285  * @param  {Object} routes
286  * @return {Function}
287  * @api private
288  */
289
290 function match(req, routes, i) {
291   var captures
292     , method = req.method
293     , i = i || 0;
294
295   // pass HEAD to GET routes
296   if ('HEAD' == method) method = 'GET';
297
298   // routes for this method
299   if (routes = routes[method]) {
300     var url = parse(req.url)
301       , pathname = url.pathname;
302
303     // matching routes
304     for (var len = routes.length; i < len; ++i) {
305       var route = routes[i]
306         , fn = route.callback
307         , path = route.regexp
308         , keys = route.keys;
309
310       // match
311       if (captures = path.exec(pathname)) {
312         route.params = [];
313         for (var j = 1, l = captures.length; j < l; ++j) {
314           var key = keys[j-1],
315             val = 'string' == typeof captures[j]
316               ? decodeURIComponent(captures[j])
317               : captures[j];
318           if (key) {
319             route.params[key] = val;
320           } else {
321             route.params.push(val);
322           }
323         }
324         req._route_index = i;
325         return route;
326       }
327     }
328   }
329 }