Comparar commits
444 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 6a6cce03b7 | |||
| 970ff87ef6 | |||
| 8b6c4d322f | |||
| 4467a00acd | |||
| 63efc61517 | |||
| 7aa18345c8 | |||
| 34149187a8 | |||
| 2d132cd0d5 | |||
| 8d741361e0 | |||
| 2bcea3a370 | |||
| ead3cace02 | |||
| 7afa5c7b43 | |||
| 115765e1ee | |||
| d712fae2d7 | |||
| 0dba9bd87f | |||
| 2fbc495088 | |||
| 37d490cbe4 | |||
| 451679c582 | |||
| 7270a13ef7 | |||
| 16f3f90ed7 | |||
| 9ff2f81a10 | |||
| 1bc9a1af6a | |||
| 760d9e3341 | |||
| 5b28abc5ed | |||
| defb1596bb | |||
| 9c117d5875 | |||
| 1b09fce42a | |||
| 5e328830e7 | |||
| 606da1c45b | |||
| 6232c1a8f3 | |||
| 15a24e68d2 | |||
| c12ace81db | |||
| 531990b516 | |||
| b90a3dbffe | |||
| 0352b97798 | |||
| e45abe60bf | |||
| 8077481707 | |||
| 4651dd33cd | |||
| c93cfa0871 | |||
| d57cb7d411 | |||
| 81088766ee | |||
| ae59a50c28 | |||
| 4a05056393 | |||
| 71e97c815a | |||
| 8b7787aa60 | |||
| e0f94b052b | |||
| eb2c9ffd32 | |||
| 5af315c165 | |||
| e9fdfc339b | |||
| 132730acea | |||
| 405097d323 | |||
| c9d79f26c5 | |||
| 7ef13eaf0c | |||
| 2756aba21f | |||
| c01a7f57df | |||
| 33a4dd3841 | |||
| 3c150db4a2 | |||
| e31f5d2325 | |||
| f8a61c667e | |||
| e2fe60399f | |||
| 59c4e35691 | |||
| 2dc5afd018 | |||
| b5df39bc46 | |||
| f87b709923 | |||
| 671aa1036b | |||
| 4580340db3 | |||
| 31b53eae39 | |||
| 089423958d | |||
| 0d02ea43e1 | |||
| 93239ae3fb | |||
| 5d21e9364d | |||
| b9306c4cca | |||
| c07bd31f61 | |||
| fee4830669 | |||
| 9016b6778e | |||
| e05501a1ae | |||
| 23d09fcc9e | |||
| 5d11fccbf2 | |||
| 3851957dd0 | |||
| 135afb0883 | |||
| 27b27af7cd | |||
| 20fe31b803 | |||
| ebca9aab8d | |||
| 49821e0416 | |||
| b5d6f1ada5 | |||
| b40325cd7b | |||
| 3b27ec66e1 | |||
| 52b8b36d54 | |||
| 7e3106dd1b | |||
| 399e0d94fc | |||
| eef1ff9e87 | |||
| 9202c7504b | |||
| 3fd7e3b93e | |||
| b6996df86a | |||
| 4b3efd7bfa | |||
| 12c2682c34 | |||
| 2caf67b813 | |||
| fbbe13661a | |||
| 0fb102edae | |||
| b4dd90d074 | |||
| 2e3f806d07 | |||
| f76092e83e | |||
| 1e44081583 | |||
| 26b3a4259d | |||
| 8a074a7e6d | |||
| e99434582f | |||
| fec57df994 | |||
| 033cb2d20a | |||
| a5fa58e3e6 | |||
| fdc9d2714b | |||
| 824ca9b513 | |||
| a74a740964 | |||
| 2041d52495 | |||
| fb5d528eff | |||
| a2cedbd9da | |||
| 8574eddfa0 | |||
| 2ccf876929 | |||
| 28df150784 | |||
| 0950083da2 | |||
| 88c5f910da | |||
| a9d3e4798e | |||
| d85d834b23 | |||
| 5729da5a9f | |||
| 1255331160 | |||
| 0fef51b5f6 | |||
| 7cf18c0468 | |||
| b994509b9d | |||
| 6571e19e8f | |||
| 7441164db9 | |||
| 09bb10cd3f | |||
| bd6f24f4b1 | |||
| 0a174ad219 | |||
| 33d004fb9a | |||
| 652bf34c77 | |||
| 3f11bad68e | |||
| 5245fbfc6d | |||
| 68f8ee8a99 | |||
| 7b83a52dce | |||
| 5f57c82478 | |||
| 3128cbef73 | |||
| 694b2bf055 | |||
| e03a023c5f | |||
| 3ff5d3bd09 | |||
| 04841196e3 | |||
| 18dd495d99 | |||
| f714945bbb | |||
| a318ef2232 | |||
| 8ed24de9d3 | |||
| e06e50936d | |||
| d710e9cb47 | |||
| 9af3699850 | |||
| 3cc7b4d8c2 | |||
| 8b58732a1f | |||
| a2a0935343 | |||
| 2668156e53 | |||
| d67615d239 | |||
| afd70b64ed | |||
| 78a6e43667 | |||
| a65af97e43 | |||
| 637dfabe69 | |||
| ff8b3c10f3 | |||
| f442555d8f | |||
| bdf9f882ad | |||
| 1e5c5bfe00 | |||
| 0e78fdfcb4 | |||
| 4f532f86dc | |||
| 92844825cb | |||
| 6b13fc99b0 | |||
| 0f7aa26757 | |||
| a06f963263 | |||
| d31de1e654 | |||
| 46a0301022 | |||
| 73d26036ef | |||
| b9637c9d7d | |||
| 08497683bf | |||
| 44a50f6e58 | |||
| 4c2b4e5c66 | |||
| d33c38f671 | |||
| fbfba21854 | |||
| b2093d6f10 | |||
| 736a0190c1 | |||
| a74c259c38 | |||
| d3dedd6312 | |||
| 920eab0ef9 | |||
| d576085e8d | |||
| 62b9a9e287 | |||
| 56ffe1d62a | |||
| 14acbcb5f1 | |||
| 9a0011bf49 | |||
| 7b9f18b097 | |||
| f8e4333157 | |||
| b5c933aa94 | |||
| c441af3f2c | |||
| e885421a67 | |||
| ce4fe24a93 | |||
| 1c0e2ceba5 | |||
| a435e8ec47 | |||
| b958393135 | |||
| a6dd697a68 | |||
| a4833e7b35 | |||
| 54fa643c10 | |||
| 6047cd4542 | |||
| 8c5f7df280 | |||
| ff1250c33c | |||
| 4297c10fe9 | |||
| ab6ad94ec3 | |||
| 759183461f | |||
| f850fa6bc9 | |||
| 5a242d35b7 | |||
| abf5d66e01 | |||
| e92360e0d4 | |||
| b5c0fdc013 | |||
| 906ef02c5e | |||
| 7550decbc0 | |||
| acd88c3c8a | |||
| a3854a2de1 | |||
| 880aca5d83 | |||
| 2d1c98a5a7 | |||
| fefa06ba21 | |||
| 18faa91c94 | |||
| 9f23c7b31a | |||
| eee6926bab | |||
| 6806f952d6 | |||
| faaef54b42 | |||
| c29852fae7 | |||
| 7d100dae97 | |||
| 206e800963 | |||
| 555a334315 | |||
| d2c5def108 | |||
| 98323c530e | |||
| b36510dbb9 | |||
| 50e533c32b | |||
| 33277c3d37 | |||
| 28f2ad0109 | |||
| 5d10ee4e61 | |||
| b4190ada0c | |||
| 60d314552d | |||
| c971d54543 | |||
| 346f019fa8 | |||
| d61c2480b6 | |||
| e30b5b86da | |||
| bd74fe24fd | |||
| b30eaa8ee3 | |||
| 75c530516a | |||
| 3b49821e82 | |||
| febf443960 | |||
| f5da81e782 | |||
| 9cb23ac584 | |||
| 6ff3100c1f | |||
| e3ea3723bf | |||
| 33443d9b41 | |||
| e0ef61659f | |||
| 655ad77bce | |||
| c09e546240 | |||
| a08b14a89e | |||
| 12e90b0eef | |||
| 7606f1bcbd | |||
| cabb43b187 | |||
| b85cda3f6e | |||
| b92f1b7497 | |||
| 273a51a335 | |||
| 212dc88b89 | |||
| ce82a91d14 | |||
| 52a08401c7 | |||
| 706c0cb033 | |||
| 1567a613a6 | |||
| d767d80d20 | |||
| 1103a9d510 | |||
| c34af4b97a | |||
| 8dcee4d338 | |||
| fc1e69ea73 | |||
| db460117c1 | |||
| 1c514df0fd | |||
| 780ec5cbec | |||
| 3721873b62 | |||
| 3bdc77ca91 | |||
| b8f54d64a4 | |||
| 90d48b320a | |||
| a797342800 | |||
| 31e608e0ae | |||
| ebcfb0e577 | |||
| 161c9f9142 | |||
| 2a099da726 | |||
| 5118397004 | |||
| 546c340656 | |||
| 3034cc7c52 | |||
| d89381ff78 | |||
| 86cac58100 | |||
| 8fc062007e | |||
| 9fd92e3306 | |||
| a4cbbe5dd4 | |||
| da7ea9655f | |||
| 00a8c642dd | |||
| e1152f6e56 | |||
| 7cf9c6842a | |||
| d375535cd9 | |||
| c5ea868f47 | |||
| 884d333805 | |||
| 3b9921a15b | |||
| 17b753d95e | |||
| 6a8781b5e2 | |||
| 0dfaf01749 | |||
| 088aa83e22 | |||
| 934adb1e9f | |||
| 50e0593de6 | |||
| f2637c6421 | |||
| a52dacea68 | |||
| f56a33d22d | |||
| 2aa858b0fb | |||
| c3b8ba4b9a | |||
| ac96b8c0e1 | |||
| a329a9f4d8 | |||
| 63cc7eb44d | |||
| 6505f32221 | |||
| 22088260f4 | |||
| d893009a8d | |||
| b62e1741be | |||
| 903c2aa642 | |||
| d0a8bb550e | |||
| f96f1423e1 | |||
| 7bf17f2f61 | |||
| 670b6cfc15 | |||
| b6d2c8479c | |||
| 5aaa114271 | |||
| e99c2791bb | |||
| 656d7754cd | |||
| 4aaf10fbfc | |||
| 85e77b77aa | |||
| f23ef09247 | |||
| 440d956438 | |||
| acd2852cf3 | |||
| 4246f43bdf | |||
| 7d33769cd2 | |||
| 31fdba80d4 | |||
| 77f8e460d0 | |||
| 51e51db9f7 | |||
| 73c506f19c | |||
| 79143f3334 | |||
| 128ba9040e | |||
| 300cfe74ad | |||
| f008af05bd | |||
| 3aa870d6bd | |||
| 25e1a8c001 | |||
| 0ba3b114b0 | |||
| c429e88e8e | |||
| c7a2fe8440 | |||
| de237e760b | |||
| 927f5c9883 | |||
| 88f461baf2 | |||
| 3251ae26a0 | |||
| e84c81633e | |||
| a2ec966ac7 | |||
| 69660fbfda | |||
| dcedca1a80 | |||
| 6455e954fc | |||
| 646904688f | |||
| 62779fc972 | |||
| fdee4cde26 | |||
| 0c18ac5adc | |||
| 6da4a942ca | |||
| 565f68d2d7 | |||
| a7cee4c889 | |||
| 4f315f9b11 | |||
| 51febfec2d | |||
| df8bd96b2e | |||
| 9da9beb342 | |||
| f93af823df | |||
| 5e74723a92 | |||
| ec77e1acea | |||
| 1afad64972 | |||
| a3365dda07 | |||
| 4c246a4cd1 | |||
| 934ffd0731 | |||
| 790e2c233d | |||
| 9e9967381c | |||
| 891ed08827 | |||
| 55d13a6f08 | |||
| 5569ea4397 | |||
| 693e37459a | |||
| 90de1fa55d | |||
| 6d6e1557ce | |||
| 2d84f16dc0 | |||
| 2f98ef9f6d | |||
| e0c07d2385 | |||
| 0b2413d8c0 | |||
| e92b01f813 | |||
| 27ff13459f | |||
| 3e80915454 | |||
| 4063d2e2c4 | |||
| ce9416857b | |||
| 575d5e8e57 | |||
| 14ceb8c046 | |||
| c6c29ca505 | |||
| c551226504 | |||
| fcbc09543c | |||
| 6d83bb0026 | |||
| 0784a513c9 | |||
| ebcdec0860 | |||
| 6805e4f28f | |||
| 962f5fa412 | |||
| 1385988ab8 | |||
| 6cc80f8c54 | |||
| d6c843962d | |||
| 247e6ad9ab | |||
| 364c131a4e | |||
| b047325033 | |||
| 0894cfe058 | |||
| f4a9d7e70b | |||
| 593634fb5d | |||
| 2932bf5006 | |||
| 4b1c39fe41 | |||
| ee91e3a139 | |||
| e287b854a2 | |||
| 5458e5dd00 | |||
| ed30f37ca9 | |||
| 9e495f6bb8 | |||
| 21566c49f0 | |||
| 0a225c13a4 | |||
| 680c07a030 | |||
| 8ee66dca22 | |||
| 60393f07ef | |||
| 34418b03fb | |||
| ce2f161f08 | |||
| d5ca1ea152 | |||
| c3fec8225a | |||
| bfff00826e | |||
| 61cb50b2bd | |||
| c8d3bfabc7 | |||
| 1ce212d33d | |||
| 3682f4f06a | |||
| 0f74408b0e | |||
| f7d7c4a8e0 | |||
| 0cb8d12796 | |||
| 9586ecfd58 | |||
| 86217867f9 | |||
| 496de51a11 | |||
| 38591d06a0 | |||
| 91805f7da4 | |||
| 7bd8340a8b | |||
| 4817007097 | |||
| d5003feb39 | |||
| f03e460ca5 | |||
| 347c8847b0 | |||
| 9f48b32329 |
@@ -1 +1,7 @@
|
||||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
benchmarks/graphs
|
||||
+12
-9
@@ -1,12 +1,15 @@
|
||||
[submodule "spec/support/libxmljs"]
|
||||
path = spec/support/libxmljs
|
||||
url = git://github.com/sprsquish/libxmljs.git
|
||||
[submodule "lib/support/haml"]
|
||||
path = lib/support/haml
|
||||
url = git://github.com/creationix/haml-js.git
|
||||
[submodule "lib/support/js-oo"]
|
||||
path = lib/support/js-oo
|
||||
url = git://github.com/visionmedia/js-oo.git
|
||||
[submodule "lib/support/ext"]
|
||||
path = lib/support/ext
|
||||
url = git://github.com/visionmedia/ext.js.git
|
||||
[submodule "lib/support/sass"]
|
||||
path = lib/support/sass
|
||||
url = git://github.com/visionmedia/sass.js.git
|
||||
[submodule "lib/support/class"]
|
||||
path = lib/support/class
|
||||
url = git://github.com/visionmedia/class.js.git
|
||||
[submodule "lib/support/haml"]
|
||||
path = lib/support/haml
|
||||
url = git://github.com/visionmedia/haml.js.git
|
||||
[submodule "lib/support/multipart"]
|
||||
path = lib/support/multipart
|
||||
url = git://github.com/isaacs/multipart-js.git
|
||||
|
||||
+155
@@ -1,4 +1,159 @@
|
||||
|
||||
0.10.1 / 2010-05-03
|
||||
==================
|
||||
|
||||
* Fixed binary uploads.
|
||||
|
||||
0.10.0 / 2010-04-30
|
||||
==================
|
||||
|
||||
* Added charset support via Request#charset (automatically assigned to 'UTF-8' when respond()'s
|
||||
encoding is set to 'utf8' or 'utf-8'.
|
||||
* Added "encoding" option to Request#render(). Closes #299
|
||||
* Added "dump exceptions" setting, which is enabled by default.
|
||||
* Added simple ejs template engine support
|
||||
* Added error reponse support for text/plain, application/json. Closes #297
|
||||
* Added callback function param to Request#error()
|
||||
* Added Request#sendHead()
|
||||
* Added Request#stream()
|
||||
* Added support for Request#respond(304, null) for empty response bodies
|
||||
* Added ETag support to Request#sendfile()
|
||||
* Added options to Request#sendfile(), passed to fs.createReadStream()
|
||||
* Added filename arg to Request#download()
|
||||
* Performance enhanced due to pre-reversing plugins so that plugins.reverse() is not called on each request
|
||||
* Performance enhanced by preventing several calls to toLowerCase() in Router#match()
|
||||
* Changed; Request#sendfile() now streams
|
||||
* Changed; Renamed Request#halt() to Request#respond(). Closes #289
|
||||
* Changed; Using sys.inspect() instead of JSON.encode() for error output
|
||||
* Changed; run() returns the http.Server instance. Closes #298
|
||||
* Changed; Defaulting Server#host to null (INADDR_ANY)
|
||||
* Changed; Logger "common" format scale of 0.4f
|
||||
* Removed Logger "request" format
|
||||
* Fixed; Catching ENOENT in view caching, preventing error when "views/partials" is not found
|
||||
* Fixed several issues with http client
|
||||
* Fixed Logger Content-Length output
|
||||
* Fixed bug preventing Opera from retaining the generated session id. Closes #292
|
||||
|
||||
0.9.0 / 2010-04-14
|
||||
==================
|
||||
|
||||
* Added DSL level error() route support
|
||||
* Added DSL level notFound() route support
|
||||
* Added Request#error()
|
||||
* Added Request#notFound()
|
||||
* Added Request#render() callback function. Closes #258
|
||||
* Added "max upload size" setting
|
||||
* Added "magic" variables to collection partials (\_\_index\_\_, \_\_length\_\_, \_\_isFirst\_\_, \_\_isLast\_\_). Closes #254
|
||||
* Added [haml.js](http://github.com/visionmedia/haml.js) submodule; removed haml-js
|
||||
* Added callback function support to Request#halt() as 3rd/4th arg
|
||||
* Added preprocessing of route param wildcards using param(). Closes #251
|
||||
* Added view partial support (with collections etc)
|
||||
* Fixed bug preventing falsey params (such as ?page=0). Closes #286
|
||||
* Fixed setting of multiple cookies. Closes #199
|
||||
* Changed; view naming convention is now NAME.TYPE.ENGINE (for example page.html.haml)
|
||||
* Changed; session cookie is now httpOnly
|
||||
* Changed; Request is no longer global
|
||||
* Changed; Event is no longer global
|
||||
* Changed; "sys" module is no longer global
|
||||
* Changed; moved Request#download to Static plugin where it belongs
|
||||
* Changed; Request instance created before body parsing. Closes #262
|
||||
* Changed; Pre-caching views in memory when "cache view contents" is enabled. Closes #253
|
||||
* Changed; Pre-caching view partials in memory when "cache view partials" is enabled
|
||||
* Updated support to node --version 0.1.90
|
||||
* Updated dependencies
|
||||
* Removed set("session cookie") in favour of use(Session, { cookie: { ... }})
|
||||
* Removed utils.mixin(); use Object#mergeDeep()
|
||||
|
||||
0.8.0 / 2010-03-19
|
||||
==================
|
||||
|
||||
* Added coffeescript example app. Closes #242
|
||||
* Changed; cache api now async friendly. Closes #240
|
||||
* Removed deprecated 'express/static' support. Use 'express/plugins/static'
|
||||
|
||||
0.7.6 / 2010-03-19
|
||||
==================
|
||||
|
||||
* Added Request#isXHR. Closes #229
|
||||
* Added `make install` (for the executable)
|
||||
* Added `express` executable for setting up simple app templates
|
||||
* Added "GET /public/*" to Static plugin, defaulting to <root>/public
|
||||
* Added Static plugin
|
||||
* Fixed; Request#render() only calls cache.get() once
|
||||
* Fixed; Namespacing View caches with "view:"
|
||||
* Fixed; Namespacing Static caches with "static:"
|
||||
* Fixed; Both example apps now use the Static plugin
|
||||
* Fixed set("views"). Closes #239
|
||||
* Fixed missing space for combined log format
|
||||
* Deprecated Request#sendfile() and 'express/static'
|
||||
* Removed Server#running
|
||||
|
||||
0.7.5 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Added Request#flash() support without args, now returns all flashes
|
||||
* Updated ext submodule
|
||||
|
||||
0.7.4 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Fixed session reaper
|
||||
* Changed; class.js replacing js-oo Class implementation (quite a bit faster, no browser cruft)
|
||||
|
||||
0.7.3 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Added package.json
|
||||
* Fixed requiring of haml / sass due to kiwi removal
|
||||
|
||||
0.7.2 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Fixed GIT submodules (HAH!)
|
||||
|
||||
0.7.1 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Changed; Express now using submodules again until a PM is adopted
|
||||
* Changed; chat example using millisecond conversions from ext
|
||||
|
||||
0.7.0 / 2010-03-15
|
||||
==================
|
||||
|
||||
* Added Request#pass() support (finds the next matching route, or the given path)
|
||||
* Added Logger plugin (default "common" format replaces CommonLogger)
|
||||
* Removed Profiler plugin
|
||||
* Removed CommonLogger plugin
|
||||
|
||||
0.6.0 / 2010-03-11
|
||||
==================
|
||||
|
||||
* Added seed.yml for kiwi package management support
|
||||
* Added HTTP client query string support when method is GET. Closes #205
|
||||
|
||||
* Added support for arbitrary view engines.
|
||||
For example "foo.engine.html" will now require('engine'),
|
||||
the exports from this module are cached after the first require().
|
||||
|
||||
* Added async plugin support
|
||||
|
||||
* Removed usage of RESTful route funcs as http client
|
||||
get() etc, use http.get() and friends
|
||||
|
||||
* Removed custom exceptions
|
||||
|
||||
0.5.0 / 2010-03-10
|
||||
==================
|
||||
|
||||
* Added ext dependency (library of js extensions)
|
||||
* Removed extname() / basename() utils. Use path module
|
||||
* Removed toArray() util. Use arguments.values
|
||||
* Removed escapeRegexp() util. Use RegExp.escape()
|
||||
* Removed process.mixin() dependency. Use utils.mixin()
|
||||
* Removed Collection
|
||||
* Removed ElementCollection
|
||||
* Shameless self promotion of ebook "Advanced JavaScript" (http://dev-mag.com) ;)
|
||||
|
||||
0.4.0 / 2010-02-11
|
||||
==================
|
||||
|
||||
|
||||
+31
-18
@@ -1,33 +1,46 @@
|
||||
|
||||
|
||||
AB = ab
|
||||
ABFLAGS = -n 3000 -c 50
|
||||
NODE = node
|
||||
COFFEE = coffee
|
||||
PREFIX = /usr/local
|
||||
|
||||
all: test
|
||||
|
||||
install: bin/express
|
||||
ln -fs $< $(PREFIX)/bin/express
|
||||
|
||||
init:
|
||||
@git submodule init && git submodule update
|
||||
uninstall:
|
||||
rm -f $(PREFIX)/bin/express
|
||||
|
||||
test: init spec/support/libxmljs/libxmljs.node
|
||||
test:
|
||||
@$(NODE) spec/node.js all
|
||||
|
||||
test-independant: init
|
||||
@$(NODE) spec/node.js independant
|
||||
|
||||
test-dependant: init spec/support/libxmljs/libxmljs.node
|
||||
@$(NODE) spec/node.js dependant
|
||||
|
||||
app: app-chat
|
||||
|
||||
prof:
|
||||
@$(NODE) --prof --prof_auto examples/chat/app.js
|
||||
|
||||
app-chat:
|
||||
@$(NODE) examples/chat/app.js
|
||||
|
||||
|
||||
app-hello-world:
|
||||
@$(NODE) examples/hello-world/app.js
|
||||
|
||||
app-upload:
|
||||
@$(NODE) examples/upload/app.js
|
||||
|
||||
benchmark:
|
||||
@$(NODE) benchmarks/collection.js
|
||||
@$(NODE) benchmarks/views.js
|
||||
app-coffee-upload: compile-coffee
|
||||
@$(NODE) examples/coffee-upload/app.js
|
||||
|
||||
spec/support/libxmljs/libxmljs.node:
|
||||
@scons -C spec/support/libxmljs libxmljs.node
|
||||
|
||||
.PHONY: init test benchmark app
|
||||
compile-coffee:
|
||||
@$(COFFEE) examples/coffee-upload/app.coffee
|
||||
|
||||
benchmark: benchmarks/run
|
||||
@./benchmarks/run
|
||||
@./benchmarks/graph
|
||||
|
||||
graphs:
|
||||
@./benchmarks/graph
|
||||
|
||||
.PHONY: install test app benchmark graphs
|
||||
+38
-52
@@ -12,90 +12,72 @@
|
||||
* Sexy DSL with robust sinatra-like routing
|
||||
* High performance
|
||||
* Session support
|
||||
* Cache API
|
||||
* RESTful HTTP client
|
||||
* Mime helpers
|
||||
* ETag support
|
||||
* Redirection helpers
|
||||
* Nested parameter parsing
|
||||
* Full test coverage
|
||||
* Extremely readable specs
|
||||
* Multipart file upload support
|
||||
* Test helpers (mock requests etc)
|
||||
* Environment based configuration
|
||||
* Light-weight JavaScript class implementation via js-oo
|
||||
* Collections and chainable iterators
|
||||
* ElementCollections / markup parsing via libxmljs and css selector traversal support via css2xpath
|
||||
* Light-weight JavaScript class implementation via [class.js](http://github.com/visionmedia/class.js/)
|
||||
* Persistent flash messages
|
||||
* Route passing
|
||||
* View support (ejs, haml, sass, etc)
|
||||
* View partials
|
||||
* Full test coverage
|
||||
* Logger plugin with several formats
|
||||
* Upload size restrictions
|
||||
* Extremely readable specs
|
||||
* Cookie support
|
||||
|
||||
## Installation
|
||||
|
||||
Currently Express must be cloned (or downloaded), you can use the following command to
|
||||
get rolling and initialize the submodule dependencies:
|
||||
|
||||
$ git clone git://github.com/visionmedia/express.git && cd express && git submodule update --init && make app
|
||||
|
||||
Or with the [gh](http://github.com/visionmedia/gh) utility:
|
||||
|
||||
$ gh clone visionmedia express && cd express && git submodule update --init && make app
|
||||
|
||||
## Performance
|
||||
|
||||
Extensive benchmarking will wait until a development version
|
||||
has been released.
|
||||
Install the [Kiwi package manager for nodejs](http://github.com/visionmedia/kiwi)
|
||||
and run:
|
||||
|
||||
Currently Express can chew through a request with a two Haml views (*page and layout*)
|
||||
requested **2000** times with concurrency of **80** in **2.4** seconds and **814**
|
||||
requests per second. With no caching involved.
|
||||
$ kiwi -v install express
|
||||
|
||||
or
|
||||
|
||||
Install via git clone:
|
||||
|
||||
An identical Sinatra application was served with the **Thin** HTTP server
|
||||
and scored **8.3** seconds and **238** requests per second. In this situation
|
||||
Express is currently **3.5** times faster than Sinatra.
|
||||
$ git clone git://github.com/visionmedia/express.git && cd express && git submodule update --init
|
||||
|
||||
## Examples
|
||||
|
||||
Below is a minimal app example when express is already within your load path.
|
||||
Below is a tiny Express application. View the [Wiki](http://wiki.github.com/visionmedia/express/) for detailed information.
|
||||
|
||||
require.paths.unshift('express/lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/hello', function(){
|
||||
this.contentType('html')
|
||||
return '<h1>World<h1>'
|
||||
get('/user', function(){
|
||||
this.redirect('/user/' + this.currentUser.id)
|
||||
})
|
||||
|
||||
get('/user/:id?', function(id) {
|
||||
get('/user/:id', function(id){
|
||||
this.render('user.haml.html', {
|
||||
locals: {
|
||||
name: id ? 'User ' + id : 'You'
|
||||
user: this.currentUser,
|
||||
usersOnline: Session.store.length()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
run()
|
||||
|
||||
|
||||
## Running Tests
|
||||
|
||||
Express uses the [JSpec](http://jspec.info) BDD JavaScript testing
|
||||
framework to write and run elegant spec suites. JSpec is frozen
|
||||
to spec/lib and does not require separate installation.
|
||||
|
||||
To run all specifications run the following command. This will ensure
|
||||
git submodules are initialized and updated, as well as building test
|
||||
related dependencies such as libxmljs.
|
||||
to spec/lib and **does not** require separate installation.
|
||||
|
||||
$ make test
|
||||
|
||||
To run independent specs (which do not require building of external apis etc) use:
|
||||
|
||||
$ make test-independant
|
||||
|
||||
To run dependent specs (which require building of external apis etc) use:
|
||||
|
||||
$ make test-dependant
|
||||
|
||||
Run individual suites:
|
||||
|
||||
$ node spec/node.js core
|
||||
@@ -103,14 +85,18 @@ Run individual suites:
|
||||
$ node spec/node.js routing
|
||||
...
|
||||
|
||||
Express is currently being developed with node --version:
|
||||
v0.1.27
|
||||
The latest release of Express is compatible with node --version:
|
||||
v0.1.93
|
||||
|
||||
With _EDGE_ Express we do our best to keep up to date with node's _EDGE_
|
||||
|
||||
## More Information
|
||||
|
||||
* [JavaScript Extensions & Utilities](http://github.com/visionmedia/ext.js)
|
||||
* [JavaScript Sass](http://github.com/visionmedia/sass.js)
|
||||
* [Scons Build System](http://www.scons.org/) (some development dependencies rely on this, ex libxmljs)
|
||||
* Featured in [Advanced JavaScript e-book](http://www.dev-mag.com/2010/02/18/advanced-javascript/) for only $4
|
||||
* [0.9.0 release details](http://tjholowaychuk.com/post/522036176/express-0-9-0-released)
|
||||
* [Express vs Sinatra Benchmarks](http://tjholowaychuk.com/post/543953703/express-vs-sinatra-benchmarks)
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
|
||||
;(function(){
|
||||
var currentSuite
|
||||
|
||||
/**
|
||||
* Contents of _fn_. Strips function literal and signature.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function contentsOf(fn) {
|
||||
return fn.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad _str_ to _len_.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {integer} len
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function pad(str, len) {
|
||||
return str + (new Array(len - str.length)).join(' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* Time the execution of _fn_
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {float}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function time(fn) {
|
||||
var start = Number(new Date)
|
||||
fn()
|
||||
return (Number(new Date) - start) / 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* Benchmark _fn_ with the given _label_.
|
||||
*
|
||||
* @param {string} label
|
||||
* @param {function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function benchmark(label, fn) {
|
||||
var duration = time(function(){
|
||||
for (var i = 0; i < currentSuite.times; ++i)
|
||||
fn()
|
||||
}).toFixed(3)
|
||||
print(pad(' ' + label, 50 - duration.toString().length) + duration + ' |')
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a benchmark suite with the given _label_, which
|
||||
* will run each benchmark n _times_. If _times_ is omitted
|
||||
* then it defaults to 1.
|
||||
*
|
||||
* @param {string} label
|
||||
* @param {integer, function} times
|
||||
* @param {function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
suite = function(label, times, fn) {
|
||||
currentSuite = this
|
||||
if (typeof times == 'function')
|
||||
this.times = 1, fn = times
|
||||
else
|
||||
this.times = times
|
||||
print('\n ' + pad(label, 42 - this.times.toString().length) + this.times + ' time(s)')
|
||||
print(' -------------------------------------------------')
|
||||
eval(contentsOf(fn))
|
||||
print('')
|
||||
}
|
||||
|
||||
})()
|
||||
@@ -1,111 +0,0 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require.paths.unshift('benchmarks')
|
||||
process.mixin(GLOBAL, require('sys'))
|
||||
process.mixin(GLOBAL, require('benchmark'))
|
||||
require('express')
|
||||
|
||||
print = puts
|
||||
|
||||
range = function(a, b) {
|
||||
var array = []
|
||||
while (a++ < b)
|
||||
array.push(a-1)
|
||||
return array
|
||||
}
|
||||
|
||||
suite('Collection with [0..10,000]', 100, function(){
|
||||
array = range(0, 10000)
|
||||
|
||||
benchmark('for', function(){
|
||||
for (var i = 0, len = array.length; i < len; ++i) ;
|
||||
})
|
||||
|
||||
benchmark('for uncached', function(){
|
||||
for (var i = 0; i < array.length; ++i) ;
|
||||
})
|
||||
|
||||
benchmark('forEach()', function(){
|
||||
array.forEach(function(){})
|
||||
})
|
||||
|
||||
benchmark('#each()', function(){
|
||||
$(array).each(function(){})
|
||||
})
|
||||
|
||||
benchmark('#map()', function(){
|
||||
$(array).map(function(n){ return n += 1 })
|
||||
})
|
||||
|
||||
benchmark('#map() with shorthand', function(){
|
||||
$(array).map('a += 1')
|
||||
})
|
||||
|
||||
benchmark('#find()', function(){
|
||||
$(array).find(function(n){ return n > 5000 })
|
||||
})
|
||||
|
||||
benchmark('#select()', function(){
|
||||
$(array).select(function(n){ return n % 2 })
|
||||
})
|
||||
|
||||
benchmark('#first()', function(){
|
||||
$(array).first(5000)
|
||||
})
|
||||
|
||||
benchmark('#slice()', function(){
|
||||
$(array).slice(100, 5000)
|
||||
})
|
||||
|
||||
benchmark('#drop()', function(){
|
||||
$(array).drop(5000)
|
||||
})
|
||||
|
||||
benchmark('#length()', function(){
|
||||
$(array).length()
|
||||
})
|
||||
|
||||
benchmark('#keys()', function(){
|
||||
$(array).keys()
|
||||
})
|
||||
|
||||
benchmark('#toArray()', function(){
|
||||
$(array).toArray()
|
||||
})
|
||||
|
||||
benchmark('#min()', function(){
|
||||
$(array).min()
|
||||
})
|
||||
|
||||
benchmark('#max()', function(){
|
||||
$(array).max()
|
||||
})
|
||||
|
||||
benchmark('#sum()', function(){
|
||||
$(array).sum()
|
||||
})
|
||||
|
||||
benchmark('#avg()', function(){
|
||||
$(array).avg()
|
||||
})
|
||||
|
||||
benchmark('#clone()', function(){
|
||||
$(array).clone()
|
||||
})
|
||||
|
||||
benchmark('#merge()', function(){
|
||||
$(array).merge({ foo: 'bar' })
|
||||
})
|
||||
|
||||
benchmark('#sample()', function(){
|
||||
$(array).sample()
|
||||
})
|
||||
|
||||
benchmark('#chunk()', function(){
|
||||
$(array).chunk(5)
|
||||
})
|
||||
|
||||
benchmark('#at()', function(){
|
||||
$(array).at(5000)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
//enable('cache view contents')
|
||||
set('root', __dirname)
|
||||
set('views', __dirname + '/../shared')
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.render('page.html.haml')
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
//enable('cache view contents')
|
||||
set('root', __dirname)
|
||||
set('views', __dirname + '/../shared')
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.render('style.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
|
||||
get('/', function(){
|
||||
return 'Hello World'
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.sendfile('benchmarks/shared/jquery.js', { bufferSize: 8 * 1024 })
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.sendfile('benchmarks/shared/huge.js', { bufferSize: 8 * 1024 })
|
||||
})
|
||||
|
||||
run()
|
||||
Arquivo executável
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
COL=${COL-9}
|
||||
|
||||
#
|
||||
# Log <msg ...>
|
||||
#
|
||||
# <msg ...>
|
||||
#
|
||||
|
||||
log(){
|
||||
echo "... $@"
|
||||
}
|
||||
|
||||
#
|
||||
# Output gnuplot script for line graph.
|
||||
#
|
||||
# <title> <node> <express> <sinatra>
|
||||
#
|
||||
|
||||
function line() {
|
||||
cat <<-EOF
|
||||
set terminal png
|
||||
set output "benchmarks/graphs/$1.png"
|
||||
set title "$1"
|
||||
set size 1,0.7
|
||||
set grid y
|
||||
set key left top
|
||||
set xlabel "request"
|
||||
set ylabel "response time (ms)"
|
||||
plot "benchmarks/$2" using $COL smooth sbezier with lines title "node", \\
|
||||
"benchmarks/$3" using $COL smooth sbezier with lines title "express", \\
|
||||
"benchmarks/$4" using $COL smooth sbezier with lines title "sinatra thin"
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# Output gnuplot script for bar graph.
|
||||
#
|
||||
# <title> <node> <express> <sinatra>
|
||||
#
|
||||
|
||||
function bar() {
|
||||
cat <<-EOF
|
||||
set terminal png
|
||||
set output "benchmarks/graphs/$1.rps.png"
|
||||
set title "$1"
|
||||
set size 0.7,0.5
|
||||
set grid y
|
||||
set key left top
|
||||
set ylabel "requests per second"
|
||||
plot "benchmarks/$1.rps.dat" using 2: xtic(1) with histogram title ""
|
||||
EOF
|
||||
}
|
||||
|
||||
mkdir -p benchmarks/graphs
|
||||
|
||||
for type in simple haml sass static static.large; do
|
||||
plot=benchmarks/graphs/$type.p
|
||||
log generating benchmarks/graphs/$type.png
|
||||
line $type \
|
||||
node/$type.js.dat \
|
||||
express/$type.js.dat \
|
||||
thin/$type.ru.dat \
|
||||
> $plot
|
||||
gnuplot $plot
|
||||
|
||||
log generating benchmarks/graphs/$type.rps.png
|
||||
plot=benchmarks/graphs/$type.rps.p
|
||||
dat=benchmarks/$type.rps.dat
|
||||
:> $dat
|
||||
for server in node express thin; do
|
||||
case $server in
|
||||
node|express) ext=js ;;
|
||||
thin) ext=ru ;;
|
||||
esac
|
||||
rps=$(cat benchmarks/$server/$type.$ext.out | grep "Requests per second:" | awk '{ print $4 }')
|
||||
echo $server $rps >> $dat
|
||||
done
|
||||
bar $type > $plot
|
||||
gnuplot $plot
|
||||
done
|
||||
|
||||
rm benchmarks/graphs/*.p
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
var fs = require('fs'),
|
||||
http = require('http')
|
||||
|
||||
http.createServer(function(req, res) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': 11 })
|
||||
res.end('Hello World', 'ascii')
|
||||
}).listen(3000, 'localhost')
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
var fs = require('fs'),
|
||||
http = require('http')
|
||||
|
||||
http.createServer(function(req, res) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain', 'Transfer-Encoding': 'chunked' })
|
||||
fs.createReadStream('benchmarks/shared/jquery.js')
|
||||
.addListener('data', function(data){
|
||||
res.write(data, 'binary')
|
||||
})
|
||||
.addListener('end', function(){
|
||||
res.end()
|
||||
})
|
||||
}).listen(3000, 'localhost')
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
var fs = require('fs'),
|
||||
http = require('http')
|
||||
|
||||
http.createServer(function(req, res) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain', 'Transfer-Encoding': 'chunked' })
|
||||
fs.createReadStream('benchmarks/shared/huge.js')
|
||||
.addListener('data', function(data){
|
||||
res.write(data, 'binary')
|
||||
})
|
||||
.addListener('end', function(){
|
||||
res.end()
|
||||
})
|
||||
}).listen(3000, 'localhost')
|
||||
Arquivo executável
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SLEEP=${SLEEP-2}
|
||||
ABFLAGS=${ABFLAGS-"-n 2000 -c 50"}
|
||||
ADDR=${ADDR-http://127.0.0.1:3000/}
|
||||
AB=${AB-ab}
|
||||
|
||||
#
|
||||
# Log <msg ...>
|
||||
#
|
||||
# <msg ...>
|
||||
#
|
||||
|
||||
log(){
|
||||
echo "... $@"
|
||||
}
|
||||
|
||||
#
|
||||
# Benchmark <type> and <file>
|
||||
#
|
||||
# - starts the server
|
||||
# - allows $SLEEP seconds for startup
|
||||
# - runs $AB
|
||||
# - kills the server process
|
||||
#
|
||||
# <type> <file>
|
||||
#
|
||||
|
||||
bm(){
|
||||
local type=$1
|
||||
local file=$2
|
||||
log benchmarking $type $file
|
||||
case $type in
|
||||
node|express)
|
||||
node benchmarks/$type/$file &
|
||||
;;
|
||||
thin)
|
||||
thin -R benchmarks/thin/$file -p 3000 start &
|
||||
;;
|
||||
esac
|
||||
pid=$!
|
||||
sleep $SLEEP
|
||||
$AB $ABFLAGS -g benchmarks/$type/$file.dat $ADDR > benchmarks/$type/$file.out
|
||||
log $(cat benchmarks/$type/$file.out | grep Requests)
|
||||
kill -KILL $pid
|
||||
}
|
||||
|
||||
log ab $ABFLAGS $ADDR
|
||||
bm node simple.js
|
||||
bm node static.js
|
||||
bm node static.large.js
|
||||
bm express simple.js
|
||||
bm express static.js
|
||||
bm express static.large.js
|
||||
bm express haml.js
|
||||
bm express sass.js
|
||||
bm thin simple.ru
|
||||
bm thin static.ru
|
||||
bm thin static.large.ru
|
||||
bm thin haml.ru
|
||||
bm thin sass.ru
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+19
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,6 @@
|
||||
!!!
|
||||
%html
|
||||
%head
|
||||
%title Wahoo
|
||||
%body
|
||||
#primary= yield
|
||||
@@ -0,0 +1,6 @@
|
||||
!!!
|
||||
%html
|
||||
%head
|
||||
%title Wahoo
|
||||
%body
|
||||
#primary!= body
|
||||
@@ -0,0 +1,10 @@
|
||||
%h1 Some title
|
||||
%ul
|
||||
%li a
|
||||
%li b
|
||||
%li c
|
||||
%li
|
||||
%ol
|
||||
%li d
|
||||
%li e
|
||||
%li f
|
||||
@@ -0,0 +1,10 @@
|
||||
%h1 Some title
|
||||
%ul
|
||||
%li a
|
||||
%li b
|
||||
%li c
|
||||
%li
|
||||
%ol
|
||||
%li d
|
||||
%li e
|
||||
%li f
|
||||
@@ -0,0 +1,73 @@
|
||||
body
|
||||
:font-family "Helvetica Neue", "Lucida Grande", "Arial"
|
||||
:font-size 13px
|
||||
:text-align center
|
||||
:color #555
|
||||
|
||||
h1, h2
|
||||
:margin 0
|
||||
:font-size 22px
|
||||
:color #343434
|
||||
h1
|
||||
:text-shadow 1px 2px 2px #ddd
|
||||
:font-size 60px
|
||||
|
||||
img.bubble
|
||||
:position absolute
|
||||
:top -25px
|
||||
:left 120px
|
||||
|
||||
#wrapper
|
||||
:position relative
|
||||
:margin 100px auto
|
||||
:width 500px
|
||||
:text-align left
|
||||
|
||||
ul
|
||||
:margin 0
|
||||
:padding 0
|
||||
:max-height 300px
|
||||
:overflow-x hidden
|
||||
li
|
||||
:margin 5px 0
|
||||
:padding 3px 8px
|
||||
:list-style none
|
||||
:border 1px solid #eee
|
||||
li:hover
|
||||
:cursor pointer
|
||||
:color #2E2E2E
|
||||
|
||||
input[type=text]
|
||||
:padding 5px
|
||||
:border 1px solid #ddd
|
||||
:outline none
|
||||
input[type=text]:focus
|
||||
:border-color #00C3FF
|
||||
|
||||
input[type=submit]
|
||||
:padding 6px 10px
|
||||
:border solid 1px #999
|
||||
:background -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ddd))
|
||||
:color #333
|
||||
:text-decoration none
|
||||
:cursor pointer
|
||||
:display inline-block
|
||||
:text-align center
|
||||
:text-shadow 0px 1px 1px #fff
|
||||
:line-height 1
|
||||
input[type=submit]:hover
|
||||
:background -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#E6E4E4))
|
||||
input[type=submit]:active
|
||||
:background -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#c7c7c7))
|
||||
input[name=name]
|
||||
:width 80px
|
||||
|
||||
a
|
||||
:color #1ABFF1
|
||||
a:hover
|
||||
:padding 0 5px
|
||||
a:hover:before
|
||||
:content 'visit: '
|
||||
|
||||
#online
|
||||
:font-size 12px
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
require 'haml'
|
||||
|
||||
configure do
|
||||
set 'views', File.dirname(__FILE__) + '/../shared'
|
||||
end
|
||||
|
||||
get '/' do
|
||||
haml :page, :ugly => true
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
require 'sass'
|
||||
|
||||
configure do
|
||||
set 'views', File.dirname(__FILE__) + '/../shared'
|
||||
end
|
||||
|
||||
get '/' do
|
||||
sass :'style.css'
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
|
||||
get '/' do
|
||||
'Hello World'
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
|
||||
get '/' do
|
||||
send_file 'benchmarks/shared/huge.js'
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
|
||||
get '/' do
|
||||
send_file 'benchmarks/shared/jquery.js'
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -1,56 +0,0 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require.paths.unshift('benchmarks')
|
||||
process.mixin(GLOBAL, require('sys'))
|
||||
process.mixin(GLOBAL, require('benchmark'))
|
||||
require('express')
|
||||
|
||||
print = puts
|
||||
|
||||
engine = {
|
||||
ejs: require('ejs'),
|
||||
haml: require('haml'),
|
||||
sass: require('sass')
|
||||
}
|
||||
|
||||
options = { locals: { article: { title: 'Foo', body: 'bar' }}}
|
||||
|
||||
ejs = ' \n\
|
||||
<div id="primary"> \n\
|
||||
<div class="block first"> \n\
|
||||
<h1><%= article.title %></h1> \n\
|
||||
<p><%= article.body %></p> \n\
|
||||
</div> \n\
|
||||
</div> \n\
|
||||
'
|
||||
|
||||
haml = ' \n\
|
||||
#primary \n\
|
||||
.block.first \n\
|
||||
%h1= article.title \n\
|
||||
%p= article.body \n\
|
||||
'
|
||||
|
||||
sass = ' \n\
|
||||
red: #ff0000 \n\
|
||||
body \n\
|
||||
ul \n\
|
||||
li \n\
|
||||
a \n\
|
||||
:color !red \n\
|
||||
:list-style none \n\
|
||||
'
|
||||
|
||||
suite('Template Engines', 1000, function(){
|
||||
benchmark('ejs', function(){
|
||||
engine.ejs.render(ejs, options)
|
||||
})
|
||||
|
||||
benchmark('haml', function(){
|
||||
engine.haml.render(haml, options)
|
||||
})
|
||||
|
||||
benchmark('sass', function(){
|
||||
engine.sass.render(sass)
|
||||
})
|
||||
})
|
||||
Arquivo executável
+142
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# Output usage information and exit.
|
||||
#
|
||||
|
||||
function usage() {
|
||||
echo 'usage: express [options] [dir]'
|
||||
exit 1
|
||||
}
|
||||
|
||||
#
|
||||
# Create template in the givin [dir] or cwd.
|
||||
#
|
||||
# [dir]
|
||||
#
|
||||
|
||||
function create_template() {
|
||||
local dir=${1-.}
|
||||
mkdir -p $dir
|
||||
[[ $(ls $dir) ]] && confirm "$dir is not empty, continue?"
|
||||
mkdir -p $dir/public/{javascripts,stylesheets,images}
|
||||
mkdir -p $dir/views/partials
|
||||
app_template > $dir/app.js
|
||||
layout_template > $dir/views/layout.html.haml
|
||||
front_template > $dir/views/front.html.haml
|
||||
sass_template > $dir/views/style.css.sass
|
||||
echo template created in $dir
|
||||
}
|
||||
|
||||
#
|
||||
# views/style.css.sass
|
||||
#
|
||||
|
||||
function sass_template() {
|
||||
cat <<-EOF
|
||||
blue: #03ADF0
|
||||
light: #4d4d4d
|
||||
lighter: #eee
|
||||
|
||||
body
|
||||
:margin 80px
|
||||
:font 14px/1.5 "Helvetica Nueue", "Lucida Grande", "Arial", sans-serif
|
||||
|
||||
code
|
||||
:padding 3px 10px
|
||||
:border 1px solid !lighter
|
||||
:color !light
|
||||
|
||||
a
|
||||
:color !blue
|
||||
:text-decoration none
|
||||
&:hover
|
||||
:text-decoration underline
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# views/front.html.haml
|
||||
#
|
||||
|
||||
function front_template() {
|
||||
cat <<-EOF
|
||||
%h1 Express
|
||||
%p
|
||||
Generated by the
|
||||
<code>express</code>
|
||||
executable.
|
||||
%p
|
||||
Visit
|
||||
%a{ href: 'http://expressjs.com' } ExpressJS.com
|
||||
for more information.
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# views/layout.html.haml
|
||||
#
|
||||
|
||||
function layout_template() {
|
||||
cat <<-EOF
|
||||
!!! strict
|
||||
%html
|
||||
%head
|
||||
%title Express
|
||||
%link{ rel: 'stylesheet', href: 'style.css' }
|
||||
%body!= body
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# app.js
|
||||
#
|
||||
|
||||
function app_template() {
|
||||
cat <<-EOF
|
||||
// If you are using the kiwi package manager
|
||||
var kiwi = require('kiwi'),
|
||||
express = kiwi.require('express')
|
||||
|
||||
// Otherwise you will need to expose the path to express
|
||||
// require.paths.unshift('path/to/express/lib')
|
||||
// require('express')
|
||||
|
||||
configure(function(){
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.render('front.html.haml')
|
||||
})
|
||||
|
||||
get('/*.css', function(path){
|
||||
this.render(path + '.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
run()
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# Confirm <msg> or exit.
|
||||
#
|
||||
# <msg>
|
||||
#
|
||||
|
||||
function confirm() {
|
||||
echo -n "$1 "
|
||||
read answer
|
||||
case $answer in
|
||||
n|N|no) exit 1 ;;
|
||||
y|Y|yes) ;;
|
||||
*) confirm "yes or no?" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Process arguments
|
||||
|
||||
case $1 in
|
||||
-h|--help|help) usage ;;
|
||||
*) create_template $1 ;;
|
||||
esac
|
||||
+24
-27
@@ -3,35 +3,36 @@ require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
var messages = [],
|
||||
utils = require('express/utils'),
|
||||
http = require('express/http')
|
||||
|
||||
configure(function(){
|
||||
var fiveMinutes = 300000,
|
||||
oneMinute = 60000
|
||||
use(Logger)
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(CommonLogger)
|
||||
use(Cookie)
|
||||
use(Cache, { lifetime: fiveMinutes, reapInterval: oneMinute })
|
||||
use(Session, { lifetime: fiveMinutes, reapInterval: oneMinute })
|
||||
use(Cache, { lifetime: (5).minutes, reapInterval: (1).minute })
|
||||
use(Session, { lifetime: (15).minutes, reapInterval: (1).minute })
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
require('express/http')
|
||||
|
||||
var messages = [],
|
||||
utils = require('express/utils')
|
||||
|
||||
get('/', function(){
|
||||
this.redirect('/chat')
|
||||
get('/', function(){
|
||||
this.pass('/chat')
|
||||
})
|
||||
|
||||
get('/chat', function(){
|
||||
this.render('chat.haml.html', {
|
||||
locals: {
|
||||
title: 'Chat',
|
||||
messages: messages,
|
||||
name: this.session.name,
|
||||
usersOnline: Session.store.length()
|
||||
}
|
||||
var self = this
|
||||
Session.store.length(function(err, len){
|
||||
self.render('chat.html.haml', {
|
||||
locals: {
|
||||
title: 'Chat',
|
||||
messages: messages,
|
||||
name: self.session.name,
|
||||
usersOnline: len
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,7 +42,7 @@ post('/chat', function(){
|
||||
.push(utils.escape(this.param('name')) + ': ' + utils.escape(this.param('message'))
|
||||
.replace(/(http:\/\/[^\s]+)/g, '<a href="$1" target="express-chat">$1</a>')
|
||||
.replace(/:\)/g, '<img src="http://icons3.iconfinder.netdna-cdn.com/data/icons/ledicons/emoticon_smile.png">'))
|
||||
this.halt(200)
|
||||
this.respond(200)
|
||||
})
|
||||
|
||||
get('/chat/messages', function(){
|
||||
@@ -51,17 +52,13 @@ get('/chat/messages', function(){
|
||||
if (messages.length > previousLength)
|
||||
self.contentType('json'),
|
||||
previousLength = messages.length,
|
||||
self.halt(200, JSON.encode(messages)),
|
||||
self.respond(200, JSON.encode(messages)),
|
||||
clearInterval(timer)
|
||||
}, 100)
|
||||
})
|
||||
|
||||
get('/public/*', function(file){
|
||||
this.sendfile(__dirname + '/public/' + file)
|
||||
})
|
||||
|
||||
get('/*.css', function(file){
|
||||
this.render(file + '.sass.css', { layout: false })
|
||||
this.render(file + '.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
get('/error/view', function(){
|
||||
@@ -77,7 +74,7 @@ get('/simple', function(){
|
||||
})
|
||||
|
||||
get('/favicon.ico', function(){
|
||||
this.halt()
|
||||
this.notFound()
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -1,8 +1,7 @@
|
||||
%h1 Chat
|
||||
%img.bubble{ src: '/public/images/bubble.png' }
|
||||
%ul#messages
|
||||
:each msg in messages
|
||||
%li= msg
|
||||
!= this.partial('message.html.haml', { collection: messages })
|
||||
%form{ method: 'post' }
|
||||
%input{ type: 'hidden', name: '_method', value: 'put' }
|
||||
%input{ type: 'text', name: 'name', value: name || 'guest' }
|
||||
@@ -5,7 +5,8 @@
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper= body
|
||||
#online
|
||||
#wrapper
|
||||
!= body
|
||||
#online
|
||||
Online:
|
||||
%strong= usersOnline
|
||||
@@ -0,0 +1 @@
|
||||
%li= message
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
require.paths.unshift 'lib'
|
||||
require 'express'
|
||||
require 'express/plugins'
|
||||
|
||||
sys: require 'sys'
|
||||
|
||||
configure ->
|
||||
use MethodOverride
|
||||
use ContentLength
|
||||
use Cookie
|
||||
use Session
|
||||
use Flash
|
||||
use Logger
|
||||
use Static
|
||||
set 'root', __dirname
|
||||
set 'views', __dirname + '/../upload/views'
|
||||
|
||||
get '/', ->
|
||||
@redirect('/upload')
|
||||
|
||||
get '/upload', ->
|
||||
@render 'upload.html.haml', {
|
||||
locals: {
|
||||
flashes: @flash 'info'
|
||||
}
|
||||
}
|
||||
|
||||
post '/upload', ->
|
||||
@param('images').each (image) =>
|
||||
sys.puts image.filename + ' -> ' + image.tempfile
|
||||
@flash 'info', 'Uploaded ' + image.filename
|
||||
@redirect '/upload'
|
||||
|
||||
get '/*.css', (file) ->
|
||||
@render file + '.css.sass', { layout: no }
|
||||
|
||||
run()
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,18 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
|
||||
configure(function(){
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.render('front.html.ejs', {
|
||||
locals: {
|
||||
title: 'Hello World',
|
||||
items: ['one', 'two', 'three']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,8 @@
|
||||
<h1><%= title %></h1>
|
||||
<% if (items && items.length) { %>
|
||||
<ul>
|
||||
<% for (var i = 0; i < items.length; ++i) { %>
|
||||
<li><%= items[i] %></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% } %>
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<%= body %>
|
||||
</body>
|
||||
</html>
|
||||
+12
-13
@@ -3,14 +3,18 @@ require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
var sys = require('sys')
|
||||
|
||||
configure(function(){
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(CommonLogger)
|
||||
use(Cookie)
|
||||
use(Session)
|
||||
use(Flash)
|
||||
use(Logger)
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
set('max upload size', (5).megabytes)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
@@ -18,28 +22,23 @@ get('/', function(){
|
||||
})
|
||||
|
||||
get('/upload', function(){
|
||||
this.render('upload.haml.html', {
|
||||
this.render('upload.html.haml', {
|
||||
locals: {
|
||||
flashes: this.flash('info')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
post('/upload', function(){
|
||||
var self = this
|
||||
$(this.param('images')).each(function(image){
|
||||
puts(image.filename + ' -> ' + image.tempfile)
|
||||
self.flash('info', 'Uploaded ' + image.filename)
|
||||
})
|
||||
post('/upload', function(){
|
||||
this.param('images').each(function(image){
|
||||
sys.puts(image.filename + ' -> ' + image.tempfile)
|
||||
this.flash('info', 'Uploaded ' + image.filename)
|
||||
}, this)
|
||||
this.redirect('/upload')
|
||||
})
|
||||
|
||||
get('/public/*', function(file){
|
||||
this.sendfile(__dirname + '/public/' + file)
|
||||
})
|
||||
|
||||
get('/*.css', function(file){
|
||||
this.render(file + '.sass.css', { layout: false })
|
||||
this.render(file + '.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -2,13 +2,12 @@
|
||||
%head
|
||||
%title Upload
|
||||
%script{ src: '/public/javascripts/jquery.js' }
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper
|
||||
%h1 Upload
|
||||
:if flashes
|
||||
- if (flashes)
|
||||
%ul.messages.info
|
||||
:each msg in flashes
|
||||
- each msg in flashes
|
||||
%li= msg
|
||||
.body= body
|
||||
.body!= body
|
||||
@@ -1,6 +1,6 @@
|
||||
:if typeof images !== 'undefined'
|
||||
- if (typeof images !== 'undefined')
|
||||
.images
|
||||
:each img in images
|
||||
- each img in images
|
||||
%img{ src: img }
|
||||
|
||||
%h2 Singles
|
||||
@@ -8,11 +8,11 @@
|
||||
%input{ type: 'file', name: 'images[0]' }
|
||||
%input{ type: 'file', name: 'images[1]' }
|
||||
%input{ type: 'file', name: 'images[2]' }
|
||||
.panel
|
||||
%div.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
|
||||
%h2 Multiple
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[]', multiple: 'multiple' }
|
||||
.panel
|
||||
%div.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
+4
-2
@@ -1,7 +1,9 @@
|
||||
|
||||
require.paths.unshift(__dirname + '/support/js-oo/lib')
|
||||
require.paths.unshift(__dirname + '/support/ext/lib')
|
||||
require.paths.unshift(__dirname + '/support/ejs/lib')
|
||||
require.paths.unshift(__dirname + '/support/haml/lib')
|
||||
require.paths.unshift(__dirname + '/support/sass/lib')
|
||||
require('oo')
|
||||
require.paths.unshift(__dirname + '/support/multipart/lib')
|
||||
require('ext')
|
||||
Class = require('support/class/lib/class').Class
|
||||
require('express/core')
|
||||
@@ -1,543 +0,0 @@
|
||||
|
||||
// Express - Collection - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Throw $break in order to stop iteration.
|
||||
*/
|
||||
|
||||
$break = '__break__'
|
||||
|
||||
var property = /^\w+$/,
|
||||
method = /^\w+\(/
|
||||
|
||||
/**
|
||||
* Normalize callback _fn_. When a string is
|
||||
* passed convert the shorthand expr to a function.
|
||||
*
|
||||
* - Functions are passed through un-touched
|
||||
* - Strings with length of < 4 are considered operators between a and b
|
||||
* - Single words are considered properties on a
|
||||
* - Single functions are considered method calls on a
|
||||
* - Larger strings are considered return expressions
|
||||
*
|
||||
* @param {string, function} fn
|
||||
* @return {function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function callback(fn) {
|
||||
if (fn === undefined) return
|
||||
if (fn instanceof Function) return fn
|
||||
if (fn.length < 4) return Function('a, b, c', 'return a ' + fn + ' b')
|
||||
if (property.test(fn) || method.test(fn)) fn = 'a.' + fn
|
||||
return Function('a, b, c', 'return ' + fn)
|
||||
}
|
||||
|
||||
// --- Collection
|
||||
|
||||
Collection = Class({
|
||||
|
||||
/**
|
||||
* Initialize collection with an array-like object.
|
||||
*
|
||||
* @param {object} arr
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(arr) {
|
||||
this.arr = arr || []
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the value of _index_ or null.
|
||||
*
|
||||
* @param {int} index
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
at: function(index) {
|
||||
if ('length' in this.arr)
|
||||
return this.arr[index]
|
||||
var result, i = 0
|
||||
this.each(function(val){
|
||||
if (i++ == index) {
|
||||
result = val
|
||||
throw $break
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate collection using callback _fn_,
|
||||
* passing both the value and index.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
each: function(fn) {
|
||||
try {
|
||||
if (this.arr.forEach)
|
||||
this.arr.forEach(fn)
|
||||
else
|
||||
for (var key in this.arr)
|
||||
if (this.arr.hasOwnProperty(key))
|
||||
fn(this.arr[key], key)
|
||||
}
|
||||
catch (e) {
|
||||
if (e != $break) throw e
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Reverse a collection.
|
||||
*
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
reverse: function() {
|
||||
if (this.arr.reverse)
|
||||
return $(this.arr.reverse())
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Join a collection with the given _str_ or ''.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
join: function(str) {
|
||||
return this.toArray().join(str || '')
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort collection with optional _fn_.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @warn converts to array
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sort: function(fn) {
|
||||
fn = callback(fn)
|
||||
return $(this.toArray().sort(fn))
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate with _memo_ using callback _fn_.
|
||||
* The _memo_ object is passed as the first
|
||||
* argument, and the return value of _fn_ becomes
|
||||
* the value of _memo_ providing a functional
|
||||
* approach to reducing a collection.
|
||||
*
|
||||
* @param {mixed} memo
|
||||
* @param {function} fn
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
reduce: function(memo, fn) {
|
||||
fn = callback(fn)
|
||||
if (this.arr.reduce)
|
||||
return this.arr.reduce(fn, memo)
|
||||
this.each(function(val, key){
|
||||
memo = fn(memo, val, key)
|
||||
})
|
||||
return memo
|
||||
},
|
||||
|
||||
/**
|
||||
* Map using callback _fn_, returning a
|
||||
* new collection of return values.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
map: function(fn) {
|
||||
fn = callback(fn)
|
||||
if (this.arr.map)
|
||||
return $(this.arr.map(fn))
|
||||
return $(this.reduce([], function(array, val, key){
|
||||
array.push(fn(val, key))
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Return collection of values when _fn_ evaluates
|
||||
* to true.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
select: function(fn) {
|
||||
fn = callback(fn)
|
||||
return $(this.reduce([], function(array, val, key){
|
||||
if (fn(val, key))
|
||||
array.push(val)
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Return collection of values when _fn_ evaluates
|
||||
* to false.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
reject: function(fn) {
|
||||
fn = callback(fn)
|
||||
return $(this.reduce([], function(array, val, key){
|
||||
if (!fn(val, key))
|
||||
array.push(val)
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if all arguments passed are found within
|
||||
* the collection using the === operator.
|
||||
*
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
includes: function() {
|
||||
var self = this
|
||||
return $(arguments).all(function(arg){
|
||||
return self.any(function(val){
|
||||
return val === arg
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the first _n_ value(s), defaults to 1.
|
||||
*
|
||||
* @param {int} n
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
first: function(n) {
|
||||
return n ?
|
||||
this.slice(0, n) :
|
||||
this.at(0)
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the last _n_ value(s), defaults to -1.
|
||||
*
|
||||
* @param {int} n
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
last: function(n) {
|
||||
var len = this.length()
|
||||
return n ?
|
||||
this.slice(len - n, len) :
|
||||
this.at(--len)
|
||||
},
|
||||
|
||||
/**
|
||||
* Drop the first _n_ values, returning the others.
|
||||
*
|
||||
* @param {int} n
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
drop: function(n) {
|
||||
return this.slice(n, this.length())
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a slice of values from _start_ to _end_.
|
||||
*
|
||||
* @param {int} start
|
||||
* @param {int} end
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
slice: function(start, end) {
|
||||
if (this.arr.slice)
|
||||
return $(this.arr.slice(start, end))
|
||||
var i = 0
|
||||
return $(this.reduce([], function(array, val){
|
||||
if (i++ >= start)
|
||||
if (i <= end)
|
||||
array.push(val)
|
||||
else
|
||||
throw $break
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate until _fn_ evaluates to true, then
|
||||
* return the matching value.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
find: function(fn) {
|
||||
var result, fn = callback(fn)
|
||||
this.each(function(val, key){
|
||||
if (fn(val, key)) {
|
||||
result = val
|
||||
throw $break
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true if _fn_ ever evaluates to true, otherwise
|
||||
* returns false.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
any: function(fn) {
|
||||
fn = callback(fn)
|
||||
if (this.arr.some)
|
||||
return this.arr.some(fn)
|
||||
return !! this.find(fn)
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if _fn_ always evaluates to true, otherwise
|
||||
* returns false.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
all: function(fn) {
|
||||
fn = callback(fn)
|
||||
if (this.arr.every)
|
||||
return this.arr.every(fn)
|
||||
return this.reduce(true, function(state, val, key){
|
||||
if (!state) throw $break
|
||||
return !! fn(val, key)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Select values matching _pattern_
|
||||
*
|
||||
* @param {regexp} pattern
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
grep: function(pattern) {
|
||||
return this.select(function(val){
|
||||
return pattern.exec(val)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return keys as a collection.
|
||||
*
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
keys: function() {
|
||||
return $(Object.keys(this.arr))
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a collection to a true array.
|
||||
* Works recursively
|
||||
*
|
||||
* @return {array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toArray: function() {
|
||||
return this.reduce([], function(array, val){
|
||||
val instanceof Collection ?
|
||||
array.push(val.toArray()) :
|
||||
array.push(val)
|
||||
return array
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the lowest number in the collection.
|
||||
*
|
||||
* @return {number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
min: function() {
|
||||
return this.reduce(null, function(min, val){
|
||||
return min === null ? val :
|
||||
val < min ? val :
|
||||
min
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the largest number in the collection.
|
||||
*
|
||||
* @return {number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
max: function() {
|
||||
return this.reduce(null, function(max, val){
|
||||
return max === null ? val :
|
||||
val > max ? val :
|
||||
max
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Group collection into several collections grouped by _size_.
|
||||
*
|
||||
* @param {int} size
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
chunk: function(size) {
|
||||
var len = this.arr.length,
|
||||
chunks = [], chunk = [], i = 0
|
||||
this.each(function(val, key){
|
||||
chunk.push(val)
|
||||
if (i++ > 0 && (i % size == 0 || i == len))
|
||||
chunks.push($(chunk)),
|
||||
chunk = []
|
||||
})
|
||||
return $(chunks)
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the length of this collection.
|
||||
*
|
||||
* @return {int}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
length: function() {
|
||||
if ('length' in this.arr)
|
||||
return this.arr.length
|
||||
return this.reduce(0, function(len){
|
||||
return ++len
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the sum of numeric values in this collection.
|
||||
*
|
||||
* @return {number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sum: function() {
|
||||
return this.reduce(0, function(sum, n){
|
||||
return sum + n
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the average of numeric values in this collection.
|
||||
*
|
||||
* @return {number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
avg: function() {
|
||||
return this.sum() / this.length()
|
||||
},
|
||||
|
||||
/**
|
||||
* Merge _other_ with this collection.
|
||||
*
|
||||
* @param {mixed} other
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
merge: function(other) {
|
||||
if (this.arr instanceof Array)
|
||||
return $($(other).reduce(this.arr, function(array, val){
|
||||
array.push(val)
|
||||
return array
|
||||
}))
|
||||
else
|
||||
return $(process.mixin(this.arr, $(other).arr))
|
||||
},
|
||||
|
||||
/**
|
||||
* Clone the collection.
|
||||
*
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clone: function() {
|
||||
return this.merge({})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the value of a random index.
|
||||
*
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sample: function() {
|
||||
return this.at(Math.floor(Math.random() * this.length()))
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert collection to a string.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toString: function() {
|
||||
return '[Collection ' + this.arr + ']'
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Create a new collection from an array-like object.
|
||||
*
|
||||
* @param {object} arr
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var $ = exports.$ = function(arr) {
|
||||
if (arr instanceof Collection) return arr
|
||||
return new Collection(arr)
|
||||
}
|
||||
+129
-125
@@ -1,30 +1,28 @@
|
||||
|
||||
// Express - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
process.mixin(require('sys'))
|
||||
process.mixin(require('express/exceptions'))
|
||||
process.mixin(require('express/collection'))
|
||||
process.mixin(require('express/event'))
|
||||
process.mixin(require('express/request'))
|
||||
process.mixin(require('express/plugin'))
|
||||
process.mixin(require('express/dsl'))
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var multipart = require('multipart'),
|
||||
events = require('events'),
|
||||
File = require('file').File,
|
||||
utils = require('express/utils')
|
||||
|
||||
var Request = require('express/request').Request,
|
||||
normalizePath = require('express/request').normalizePath,
|
||||
multipart = require('old'),
|
||||
utils = require('express/utils'),
|
||||
http = require('http'),
|
||||
sys = require('sys'),
|
||||
fs = require('fs')
|
||||
|
||||
Object.merge(global, require('express/plugin'))
|
||||
Object.merge(global, require('express/dsl'))
|
||||
|
||||
// --- Route
|
||||
|
||||
Route = Class({
|
||||
Route = new Class({
|
||||
|
||||
/**
|
||||
* Initialize a route with the given _method_,
|
||||
* _path_, and callback _fn_.
|
||||
* _path_, and _callback_.
|
||||
*
|
||||
* The given _path_ becomes #originalPath,
|
||||
* #path is then a normalized version converted
|
||||
@@ -32,16 +30,16 @@ Route = Class({
|
||||
*
|
||||
* @param {string} method
|
||||
* @param {string} path
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(method, path, fn, options){
|
||||
constructor: function(method, path, callback, options){
|
||||
this.method = method
|
||||
this.originalPath = path
|
||||
this.path = this.normalize(path)
|
||||
this.fn = fn
|
||||
this.callback = callback
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -71,7 +69,7 @@ Route = Class({
|
||||
var self = this
|
||||
this.keys = []
|
||||
if (path instanceof RegExp) return path
|
||||
return new RegExp('^' + utils.escapeRegexp(normalizePath(path), '.')
|
||||
return new RegExp('^' + RegExp.escape(normalizePath(path), '.')
|
||||
.replace(/\*/g, '(.+)')
|
||||
.replace(/(\/|\\\.):(\w+)\?/g, function(_, c, key){
|
||||
self.keys.push(key)
|
||||
@@ -86,7 +84,7 @@ Route = Class({
|
||||
|
||||
// --- Router
|
||||
|
||||
Router = Class({
|
||||
Router = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with _request_ and parse url.
|
||||
@@ -95,8 +93,9 @@ Router = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(request) {
|
||||
constructor: function(request) {
|
||||
this.request = request
|
||||
this.method = request.method.lowercase
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -106,14 +105,21 @@ Router = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
route: function(){
|
||||
var route = this.matchingRoute()
|
||||
if (route)
|
||||
return route.fn.apply(this.request, this.request.captures.slice(1))
|
||||
else if (this.request.accepts('html') && set('helpful 404'))
|
||||
this.request.halt(404, require('express/pages/not-found').render(this.request))
|
||||
route: function() {
|
||||
var body,
|
||||
route = this.matchingRoute()
|
||||
if (route) {
|
||||
body = route.callback.apply(this.request, this.request.captures.slice(1));
|
||||
if (this.request.passed) {
|
||||
if (typeof this.request.passed === 'string')
|
||||
this.request.url.pathname = this.request.passed
|
||||
this.request.passed = false
|
||||
return this.route()
|
||||
}
|
||||
return body
|
||||
}
|
||||
else
|
||||
this.request.halt()
|
||||
this.request.notFound()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -123,11 +129,13 @@ Router = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
matchingRoute: function(){
|
||||
var self = this
|
||||
return $(Express.routes).find(function(route){
|
||||
return self.match(route)
|
||||
})
|
||||
matchingRoute: function() {
|
||||
this.lastMatchingRoute = this.lastMatchingRoute || 0
|
||||
var routes = Express.routes, route
|
||||
while (route = routes[this.lastMatchingRoute++])
|
||||
if (this.match(route))
|
||||
break
|
||||
return route
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -140,7 +148,7 @@ Router = Class({
|
||||
*/
|
||||
|
||||
match: function(route) {
|
||||
if (this.request.method.toLowerCase() == route.method)
|
||||
if (this.method === route.method)
|
||||
if (this.request.captures = this.request.url.pathname.match(route.path)) {
|
||||
this.mapParams(route)
|
||||
return true
|
||||
@@ -149,23 +157,26 @@ Router = Class({
|
||||
|
||||
/**
|
||||
* Map #request.captures to #request.params.path based on the
|
||||
* given _route_ keys.
|
||||
* given _route_ params.
|
||||
*
|
||||
* @param {Route} route
|
||||
* @api private
|
||||
*/
|
||||
|
||||
mapParams: function(route) {
|
||||
var self = this
|
||||
$(route.keys).each(function(key, i){
|
||||
self.request.params.path[key] = self.request.captures[++i]
|
||||
})
|
||||
route.keys.each(function(key, i){
|
||||
var val = this.request.captures[++i]
|
||||
if (key in Express.params)
|
||||
if ((val = Express.params[key].call(this.request, val)) === false)
|
||||
this.request.passed = true
|
||||
this.request.params.path[key] = this.request.captures[i] = val
|
||||
}, this)
|
||||
}
|
||||
})
|
||||
|
||||
// --- Server
|
||||
|
||||
Server = Class({
|
||||
Server = new Class({
|
||||
|
||||
/**
|
||||
* Default port number.
|
||||
@@ -178,124 +189,114 @@ Server = Class({
|
||||
* all network addresses.
|
||||
*/
|
||||
|
||||
host: 'localhost',
|
||||
host: null,
|
||||
|
||||
/**
|
||||
* Maximum number of queued connections.
|
||||
*/
|
||||
|
||||
backlog: 128,
|
||||
|
||||
/**
|
||||
* Run Express.
|
||||
*
|
||||
* - Buffers request bodies
|
||||
* - Calls #route() once the request is complete
|
||||
* Run Express with optional _port_ defaulting to 3000,
|
||||
* and host defaulting to null (INADDR_ANY).
|
||||
*
|
||||
* @param {int} port
|
||||
* @param {string} host
|
||||
* @param {int} backlog
|
||||
* @return {Server}
|
||||
* @see run()
|
||||
* @api private
|
||||
*/
|
||||
|
||||
run: function(port, host, backlog){
|
||||
run: function(port, host){
|
||||
var self = this
|
||||
this.running = true
|
||||
if (host !== undefined) this.host = host
|
||||
if (port !== undefined) this.port = port
|
||||
if (backlog !== undefined) this.backlog = backlog
|
||||
require('http')
|
||||
.createServer(function(request, response){
|
||||
var server = http
|
||||
.createServer(function(req, response){
|
||||
var request, pendingFiles = 0
|
||||
req.setBodyEncoding('binary')
|
||||
request = new Request(req, response)
|
||||
request.body = ''
|
||||
request.setBodyEncoding('binary')
|
||||
if (request.headers['content-type'] &&
|
||||
request.headers['content-type'].indexOf('multipart/form-data') !== -1) {
|
||||
var stream = new multipart.Stream(request),
|
||||
promise = new events.Promise,
|
||||
pendingFiles = 0
|
||||
request.params = { post: {}}
|
||||
promise.timeout(set('upload timeout') || 5000)
|
||||
stream.addListener('part', function(part){
|
||||
if (part.filename) {
|
||||
var file = new File(part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(), 'w', { encoding: 'binary' })
|
||||
++pendingFiles
|
||||
part.pos = 0
|
||||
part.addListener('body', function(chunk){
|
||||
file.write(chunk, part.pos, 'binary').addErrback(function(){
|
||||
promise.emitError.apply(promise, arguments)
|
||||
})
|
||||
part.pos += chunk.length
|
||||
})
|
||||
.addListener('complete', function(){
|
||||
file.close().addCallback(function(){
|
||||
if (!--pendingFiles)
|
||||
promise.emitSuccess()
|
||||
}).addErrback(function(){
|
||||
promise.emitError.apply(promise, arguments)
|
||||
})
|
||||
utils.mergeParam(part.name, part, request.params.post)
|
||||
})
|
||||
}
|
||||
else
|
||||
part.buf = '',
|
||||
part
|
||||
.addListener('body', function(chunk){ part.buf += chunk })
|
||||
.addListener('complete', function(){
|
||||
if (part.buf.length)
|
||||
utils.mergeParam(part.name, part.buf, request.params.post)
|
||||
})
|
||||
}).addListener('complete', function(){
|
||||
if (!pendingFiles) promise.emitSuccess()
|
||||
promise.addCallback(function(){ self.route(request, response) })
|
||||
})
|
||||
function callback(err) {
|
||||
if (err)
|
||||
request.error(err)
|
||||
else if (!pendingFiles)
|
||||
self.route(request)
|
||||
}
|
||||
if (request.header('Content-Type') &&
|
||||
request.header('Content-Type').includes('multipart/form-data')) {
|
||||
var stream,
|
||||
contentLength = parseInt(request.header('Content-Length')),
|
||||
maxBodyLength = set('max upload size')
|
||||
if (maxBodyLength && contentLength > maxBodyLength)
|
||||
return callback(new Error('upload size limit exceeded'))
|
||||
stream = multipart.parse(req)
|
||||
stream
|
||||
.addListener('partBegin', function(part) {
|
||||
if (part.filename)
|
||||
++pendingFiles,
|
||||
part.tempfile = '/tmp/express-' + Date.now() + utils.uid(),
|
||||
part.fileStream = fs.createWriteStream(part.tempfile),
|
||||
part.fileStream.addListener('error', callback)
|
||||
else
|
||||
part.buf = ''
|
||||
})
|
||||
.addListener('body', function(chunk) {
|
||||
if (stream.part.fileStream)
|
||||
stream.part.fileStream.write(chunk, 'binary')
|
||||
else
|
||||
stream.part.buf += chunk
|
||||
})
|
||||
.addListener('partEnd', function(part) {
|
||||
if (!part.name) return
|
||||
if (part.fileStream)
|
||||
part.fileStream.end(function(){
|
||||
--pendingFiles
|
||||
callback()
|
||||
}),
|
||||
utils.mergeParam(part.name, { filename: part.filename, tempfile: part.tempfile }, request.params.post)
|
||||
else
|
||||
utils.mergeParam(part.name, part.buf, request.params.post)
|
||||
})
|
||||
.addListener('error', callback)
|
||||
.addListener('complete', callback)
|
||||
}
|
||||
else
|
||||
request
|
||||
.addListener('body', function(chunk){ request.body += chunk })
|
||||
.addListener('complete', function(){ self.route(request, response) })
|
||||
req
|
||||
.addListener('data', function(chunk){ request.body += chunk })
|
||||
.addListener('end', callback)
|
||||
})
|
||||
.listen(this.port, this.host, this.backlog)
|
||||
puts('Express started at http://' + this.host + ':' + this.port + '/ in ' + Express.environment + ' mode')
|
||||
server.listen(this.port, this.host)
|
||||
sys.puts('Express started at http://' + (this.host || '*') + ':' + this.port + '/ in ' + Express.environment + ' mode')
|
||||
return server
|
||||
},
|
||||
|
||||
/**
|
||||
* Route the given _request_ and _response_.
|
||||
* Route the given _request_.
|
||||
*
|
||||
* @param {object} request
|
||||
* @param {object} response
|
||||
* @param {Request} request
|
||||
* @api private
|
||||
*/
|
||||
|
||||
route: function(request, response){
|
||||
request = new Request(request, response)
|
||||
request.trigger('request')
|
||||
if (request.response.finished) return
|
||||
try {
|
||||
if (typeof (body = (new Router(request)).route()) == 'string')
|
||||
request.halt(200, body)
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof ExpressError)
|
||||
throw e
|
||||
if (request.accepts('html') && set('show exceptions'))
|
||||
request.halt(500, require('express/pages/show-exceptions').render(request, e))
|
||||
else
|
||||
request.halt(500)
|
||||
if (set('throw exceptions'))
|
||||
throw e
|
||||
}
|
||||
route: function(request){
|
||||
var self = this
|
||||
request.trigger('request', function(err) {
|
||||
try {
|
||||
if (err) throw err
|
||||
if (request.response.finished) return
|
||||
if (typeof (body = (new Router(request)).route()) === 'string')
|
||||
request.respond(200, body)
|
||||
} catch (err) {
|
||||
request.error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// --- Express
|
||||
|
||||
Express = {
|
||||
version: '0.4.0',
|
||||
version: '0.10.1',
|
||||
config: [],
|
||||
routes: [],
|
||||
plugins: [],
|
||||
settings: {},
|
||||
params: {},
|
||||
server: new Server
|
||||
}
|
||||
|
||||
@@ -311,13 +312,16 @@ configure(function(){
|
||||
configure('development', function(){
|
||||
enable('helpful 404')
|
||||
enable('show exceptions')
|
||||
enable('dump exceptions')
|
||||
})
|
||||
|
||||
configure('test', function(){
|
||||
enable('throw exceptions')
|
||||
disable('dump exceptions')
|
||||
})
|
||||
|
||||
configure('production', function(){
|
||||
enable('cache view contents')
|
||||
enable('cache view partials')
|
||||
enable('cache static files')
|
||||
})
|
||||
|
||||
+65
-37
@@ -1,12 +1,6 @@
|
||||
|
||||
// Express - DSL - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('express/http')
|
||||
|
||||
/**
|
||||
* Return a routing function for _method_.
|
||||
*
|
||||
@@ -16,15 +10,10 @@ var http = require('express/http')
|
||||
*/
|
||||
|
||||
function route(method) {
|
||||
return function(path, options, fn){
|
||||
return function(path, options, callback){
|
||||
if (options instanceof Function)
|
||||
fn = options, options = {}
|
||||
if (path.indexOf('http://') === 0)
|
||||
return http[method].apply(this, arguments)
|
||||
else if (!Express.server.running)
|
||||
Express.routes.push(new Route(method, path, fn, options))
|
||||
else
|
||||
throw new Error('cannot create route ' + method.toUpperCase() + " `" + path + "' at runtime")
|
||||
callback = options, options = {}
|
||||
Express.routes.push(new Route(method, path, callback, options))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +28,11 @@ function route(method) {
|
||||
*/
|
||||
|
||||
exports.set = function(option, val) {
|
||||
return val === undefined ?
|
||||
Express.settings[option] instanceof Function ?
|
||||
Express.settings[option]() :
|
||||
Express.settings[option] :
|
||||
Express.settings[option] = val
|
||||
return val === undefined
|
||||
? Express.settings[option] instanceof Function
|
||||
? Express.settings[option]()
|
||||
: Express.settings[option]
|
||||
: Express.settings[option] = val
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,16 +68,16 @@ exports.disable = function(option) {
|
||||
*/
|
||||
|
||||
exports.run = function() {
|
||||
configure(Express.environment = process.ENV.EXPRESS_ENV || 'development')
|
||||
$(Express.plugins).each(function(plugin){
|
||||
configure(Express.environment = process.env.EXPRESS_ENV || 'development')
|
||||
Express.plugins.each(function(plugin){
|
||||
if ('init' in plugin.klass)
|
||||
plugin.klass.init(plugin.options)
|
||||
})
|
||||
Express.server.run.apply(Express.server, arguments)
|
||||
return Express.server.run.apply(Express.server, arguments)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure _environment_ with _fn_.
|
||||
* Configure _env_ with _callback_.
|
||||
*
|
||||
* Global configuration, disregards which
|
||||
* environment is active:
|
||||
@@ -107,22 +96,61 @@ exports.run = function() {
|
||||
*
|
||||
* configure('development')
|
||||
*
|
||||
* @param {string, function} environment
|
||||
* @param {function} fn
|
||||
* @param {string, function} env
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.configure = function(environment, fn) {
|
||||
if (environment instanceof Function)
|
||||
fn = environment, environment = 'all'
|
||||
if (fn instanceof Function)
|
||||
return Express.config.push([environment, fn])
|
||||
if (typeof environment != 'string')
|
||||
throw new Error('environment required')
|
||||
for (var i = 0, len = Express.config.length; i < len; ++i)
|
||||
if (Express.config[i][0] == environment ||
|
||||
Express.config[i][0] == 'all')
|
||||
Express.config[i][1].call(Express)
|
||||
exports.configure = function(env, callback) {
|
||||
if (env instanceof Function)
|
||||
callback = env, env = 'all'
|
||||
if (callback instanceof Function)
|
||||
return Express.config.push([env, callback])
|
||||
if (typeof env !== 'string')
|
||||
throw new TypeError('environment required')
|
||||
Express.config.each(function(conf){
|
||||
if (conf[0] === env ||
|
||||
conf[0] === 'all')
|
||||
conf[1].call(Express)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-process param _key_ with _callback_.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.param = function(key, callback) {
|
||||
if (typeof key !== 'string')
|
||||
throw new TypeError('param key must be a string')
|
||||
if (typeof callback !== 'function')
|
||||
throw new TypeError('param must pass a function to process "' + key + '"')
|
||||
Express.params[key] = callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a "Not Found" route with the given _callback_.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.notFound = function(callback) {
|
||||
Express.notFound = callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an "error" route with the given _callback_.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.error = function(callback) {
|
||||
Express.error = callback
|
||||
}
|
||||
|
||||
// --- Routing API
|
||||
@@ -130,4 +158,4 @@ exports.configure = function(environment, fn) {
|
||||
exports.get = exports.view = route('get')
|
||||
exports.post = exports.create = route('post')
|
||||
exports.del = exports.destroy = route('delete')
|
||||
exports.put = exports.update = route('put')
|
||||
exports.put = exports.update = route('put')
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
|
||||
// Express - ElementCollection - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var libxml = require('libxmljs')
|
||||
|
||||
// --- css2xpath
|
||||
|
||||
/** version 0.1 2009-04-30
|
||||
* @author Andrea Giammarchi
|
||||
* @license Mit Style License
|
||||
* @project http://code.google.com/p/css2xpath/
|
||||
*/
|
||||
var css2xpath=(function(){var b=[/\[([^\]~\$\*\^\|\!]+)(=[^\]]+)?\]/g,"[@$1$2]",/\s*,\s*/g,"|",/\s*(\+|~|>)\s*/g,"$1",/([a-zA-Z0-9\_\-\*])~([a-zA-Z0-9\_\-\*])/g,"$1/following-sibling::$2",/([a-zA-Z0-9\_\-\*])\+([a-zA-Z0-9\_\-\*])/g,"$1/following-sibling::*[1]/self::$2",/([a-zA-Z0-9\_\-\*])>([a-zA-Z0-9\_\-\*])/g,"$1/$2",/\[([^=]+)=([^'|"][^\]]*)\]/g,"[$1='$2']",/(^|[^a-zA-Z0-9\_\-\*])(#|\.)([a-zA-Z0-9\_\-]+)/g,"$1*$2$3",/([\>\+\|\~\,\s])([a-zA-Z\*]+)/g,"$1//$2",/\s+\/\//g,"//",/([a-zA-Z0-9\_\-\*]+):first-child/g,"*[1]/self::$1",/([a-zA-Z0-9\_\-\*]+):last-child/g,"$1[not(following-sibling::*)]",/([a-zA-Z0-9\_\-\*]+):only-child/g,"*[last()=1]/self::$1",/([a-zA-Z0-9\_\-\*]+):empty/g,"$1[not(*) and not(normalize-space())]",/([a-zA-Z0-9\_\-\*]+):not\(([^\)]*)\)/g,function(f,e,d){return e.concat("[not(",a(d).replace(/^[^\[]+\[([^\]]*)\].*$/g,"$1"),")]")},/([a-zA-Z0-9\_\-\*]+):nth-child\(([^\)]*)\)/g,function(f,e,d){switch(d){case"n":return e;case"even":return"*[position() mod 2=0 and position()>=0]/self::"+e;case"odd":return e+"[(count(preceding-sibling::*) + 1) mod 2=1]";default:d=(d||"0").replace(/^([0-9]*)n.*?([0-9]*)$/,"$1+$2").split("+");d[1]=d[1]||"0";return"*[(position()-".concat(d[1],") mod ",d[0],"=0 and position()>=",d[1],"]/self::",e)}},/:contains\(([^\)]*)\)/g,function(e,d){return"[contains(string(.),'"+d+"')]"},/\[([a-zA-Z0-9\_\-]+)\|=([^\]]+)\]/g,"[@$1=$2 or starts-with(@$1,concat($2,'-'))]",/\[([a-zA-Z0-9\_\-]+)\*=([^\]]+)\]/g,"[contains(@$1,$2)]",/\[([a-zA-Z0-9\_\-]+)~=([^\]]+)\]/g,"[contains(concat(' ',normalize-space(@$1),' '),concat(' ',$2,' '))]",/\[([a-zA-Z0-9\_\-]+)\^=([^\]]+)\]/g,"[starts-with(@$1,$2)]",/\[([a-zA-Z0-9\_\-]+)\$=([^\]]+)\]/g,function(f,e,d){return"[substring(@".concat(e,",string-length(@",e,")-",d.length-3,")=",d,"]")},/\[([a-zA-Z0-9\_\-]+)\!=([^\]]+)\]/g,"[not(@$1) or @$1!=$2]",/#([a-zA-Z0-9\_\-]+)/g,"[@id='$1']",/\.([a-zA-Z0-9\_\-]+)/g,"[contains(concat(' ',normalize-space(@class),' '),' $1 ')]",/\]\[([^\]]+)/g," and ($1)"],c=b.length;return function a(e){var d=0;while(d<c){e=e.replace(b[d++],b[d++])}return"//"+e}})();
|
||||
|
||||
// --- ElementCollection
|
||||
|
||||
ElementCollection = Collection.extend({
|
||||
|
||||
/**
|
||||
* Initialize with string of _markup_.
|
||||
*
|
||||
* @param {string} markup
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(markup) {
|
||||
if (typeof markup != 'string')
|
||||
return this.__super__(markup)
|
||||
if (!(/<html>/.test(markup)))
|
||||
markup = '<html><body>' + markup + '</body></html>'
|
||||
this.document = libxml.parseHtmlString(markup)
|
||||
this.arr = [this.document.root()]
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the first element's name.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
name: function() {
|
||||
return this.at(0).name()
|
||||
},
|
||||
|
||||
/**
|
||||
* Search child elements with the given _xpath_.
|
||||
*
|
||||
* @param {string} xpath
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
xpath: function(xpath) {
|
||||
// TODO: refactor with flatten()
|
||||
return $(this.reduce([], function(array, e){
|
||||
$(e.find(xpath)).each(function(child){
|
||||
array.push(child)
|
||||
})
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Search child elements with the given css _selector_
|
||||
*
|
||||
* @param {string} selector
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
search: function(selector) {
|
||||
return this.xpath(css2xpath(selector))
|
||||
},
|
||||
|
||||
/**
|
||||
* Return collection of children.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
children: function() {
|
||||
// TODO: refactor with flatten()
|
||||
return $(this.reduce([], function(array, e){
|
||||
$(e.children()).each(function(child){
|
||||
array.push(child)
|
||||
})
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Return collection of parents.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
parents: function() {
|
||||
return this.map(function(e){
|
||||
return e.parent()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the first element's parent.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
parent: function() {
|
||||
return $([this.at(0).parent()])
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the prev element.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
prev: function() {
|
||||
return $([this.at(0).prevSibling()])
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the next element.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
next: function() {
|
||||
return $([this.at(0).nextSibling()])
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the first element's text content.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
text: function() {
|
||||
return this.at(0).text()
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert collection to a string.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toString: function() {
|
||||
if (this.at(0) && this.at(0).doc)
|
||||
return '[Collection <elements>]'
|
||||
return this.__super__()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Add markup support to $().
|
||||
*
|
||||
* @param {object} arr
|
||||
* @return {Collection, ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var $ = exports.$ = function(arr) {
|
||||
if (arr instanceof Collection) return arr
|
||||
return new ElementCollection(arr)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
// Express - Event - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
exports.Event = Class({
|
||||
exports.Event = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with event _name_ and optional _data_.
|
||||
@@ -11,9 +11,9 @@ exports.Event = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(name, data) {
|
||||
constructor: function(name, data) {
|
||||
this.name = name
|
||||
process.mixin(this, data)
|
||||
Object.merge(this, data)
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
|
||||
// Express - Exceptions - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
// --- ExpressError
|
||||
|
||||
ExpressError = Class({
|
||||
name: 'ExpressError',
|
||||
init: function(message) {
|
||||
this.message = message
|
||||
},
|
||||
toString: function() {
|
||||
return this.name + ': ' + this.message
|
||||
}
|
||||
})
|
||||
+75
-39
@@ -1,79 +1,115 @@
|
||||
|
||||
// Express - HTTP - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http'),
|
||||
events = require('events'),
|
||||
parse = require('url').parse,
|
||||
queryString = require('querystring')
|
||||
|
||||
/**
|
||||
* Request using the given _method_, _url_
|
||||
* followed by optional _headers_, and _data_.
|
||||
* Mega super awesome private request utility.
|
||||
*
|
||||
* @param {string} method
|
||||
* @param {string} url
|
||||
* @param {hash} headers
|
||||
* @param {hash} data
|
||||
* @param {Promise} promise
|
||||
* @return {Promise}
|
||||
* @param {hash} headers
|
||||
* @param {function} callback
|
||||
* @param {number} redirects
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function request(method, url, headers, data, promise) {
|
||||
function request(method, url, data, headers, callback, redirects, first) {
|
||||
var buf = '',
|
||||
promise = promise || new events.Promise,
|
||||
redirects = typeof redirects !== 'number' ? 3 : redirects,
|
||||
url = parse(url),
|
||||
path = url.pathname || '/',
|
||||
search = url.search || '',
|
||||
hash = url.hash || '',
|
||||
port = url.port || 80,
|
||||
headers = process.mixin(headers, { host: url.hostname }),
|
||||
headers = Object.merge(headers || {}, { Host: url.hostname }),
|
||||
client = http.createClient(port, url.hostname)
|
||||
client.addListener('error', function(e){
|
||||
promise.emitError(new Error("client failed to " + method + " `" + url.href + "'"))
|
||||
})
|
||||
if (data) {
|
||||
data = queryString.stringify(data)
|
||||
headers['content-length'] = data.length
|
||||
headers['content-type'] = 'application/x-www-form-urlencoded'
|
||||
}
|
||||
var request = client.request(method, path + search + hash, headers)
|
||||
if (data) request.sendBody(data)
|
||||
request.finish(function(response){
|
||||
if (response.statusCode < 200 || response.statusCode >= 400)
|
||||
promise.emitError(new Error('request failed with status ' + response.statusCode + ' "' + http.STATUS_CODES[response.statusCode] + '"'))
|
||||
else if (response.statusCode >= 300 && response.statusCode < 400)
|
||||
request(method, response.headers.location, headers, data, promise)
|
||||
if (typeof data === 'object' && 'redirects' in data)
|
||||
redirects = data.redirects,
|
||||
delete data.redirects
|
||||
if (first && data) {
|
||||
if (typeof data !== 'string')
|
||||
data = queryString.stringify(data)
|
||||
if (method === 'GET')
|
||||
search += (search ? '&' : '?') + data
|
||||
else {
|
||||
response.setBodyEncoding('utf8')
|
||||
response
|
||||
.addListener('body', function(chunk){ buf += chunk })
|
||||
.addListener('complete', function(){ promise.emitSuccess(buf, response) })
|
||||
if (!headers['Content-Length'])
|
||||
headers['Content-Length'] = data.length
|
||||
if (!headers['Content-Type'])
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}
|
||||
var req = client.request(method, path + search + hash, headers)
|
||||
if (data && method !== 'GET') req.write(data)
|
||||
req.addListener('response', function(res){
|
||||
if (res.statusCode < 200 || res.statusCode >= 400)
|
||||
callback(new Error('request failed with status ' + res.statusCode + ' "' + http.STATUS_CODES[res.statusCode] + '"'), '', res)
|
||||
else if (res.statusCode >= 300 && res.statusCode < 400)
|
||||
if (redirects--)
|
||||
request(method, res.headers.location, data, headers, callback, redirects, false)
|
||||
else
|
||||
callback(new Error('maximum number of redirects reached'), '', res)
|
||||
else {
|
||||
res.setBodyEncoding('utf8')
|
||||
res
|
||||
.addListener('data', function(chunk){ buf += chunk })
|
||||
.addListener('end', function(){ callback(null, buf, res) })
|
||||
}
|
||||
})
|
||||
return promise
|
||||
req.end()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTTP Client function for the given _method_,
|
||||
* which optionally may _allowData_ to be passed.
|
||||
* Return HTTP Client function for the given _method_.
|
||||
*
|
||||
* @param {string} method
|
||||
* @param {bool} allowData
|
||||
* @return {function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function client(method, allowData) {
|
||||
return function(url, headers, data) {
|
||||
if (allowData) data = data || {}
|
||||
return request(method.toUpperCase(), url, headers, data)
|
||||
function client(method) {
|
||||
return function() {
|
||||
var redirects,
|
||||
args = Array.prototype.slice.call(arguments),
|
||||
url = args.shift(),
|
||||
callback = args.pop(),
|
||||
data = args.shift(),
|
||||
headers = args.shift()
|
||||
if (typeof callback !== 'function')
|
||||
throw new TypeError('http client requires a callback function')
|
||||
return request(method.toUpperCase(), url, data, headers, callback, redirects, true)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Public API
|
||||
|
||||
/**
|
||||
* Examples:
|
||||
*
|
||||
* var http = require('express/http')
|
||||
*
|
||||
* http.get('http://google.com/search', { q: 'foobar' }, function(err, body, response){
|
||||
* if (!err) sys.puts(body)
|
||||
* })
|
||||
*
|
||||
* http.get('http://google.com/search?lang=en', { q: 'foobar' }, function(){
|
||||
* // ...
|
||||
* })
|
||||
*
|
||||
* http.post('http://localhost:8000', '<user>tj</user>', { 'Content-Type': 'text/xml' }, function(err, body){
|
||||
* // ...
|
||||
* })
|
||||
*
|
||||
*/
|
||||
|
||||
exports.get = exports.view = client('get')
|
||||
exports.post = exports.create = client('post', true)
|
||||
exports.put = exports.update = client('put', true)
|
||||
exports.del = exports.destroy = client('delete', true)
|
||||
exports.post = exports.create = client('post')
|
||||
exports.put = exports.update = client('put')
|
||||
exports.del = exports.destroy = client('delete')
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils')
|
||||
var extname = require('path').extname
|
||||
|
||||
/**
|
||||
* Mime type lookup table.
|
||||
@@ -364,7 +364,7 @@ exports.types = {
|
||||
|
||||
exports.type = function(path) {
|
||||
return exports.types[path] ||
|
||||
exports.types[utils.extname(path)] ||
|
||||
exports.types[extname(path).substr(1)] ||
|
||||
set('default mime type') ||
|
||||
'application/octet-stream'
|
||||
}
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
|
||||
// Express - Pages - Show Exceptions - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var style = require('express/pages/style').style
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var sys = require('sys'),
|
||||
style = require('express/pages/style').style
|
||||
|
||||
|
||||
/**
|
||||
* Return list items for exception _e_'s stack.
|
||||
*
|
||||
* @param {object} e
|
||||
* @param {Error} e
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function stack(e) {
|
||||
if (e.stack)
|
||||
return $(e.stack.split('\n').slice(1)).map(function(val, i){
|
||||
return e.stack.split('\n').slice(1).map(function(val, i){
|
||||
if (!i)
|
||||
return '<li>' + val.replace(/^(.*?):/, '<span class="path">$1</span>:') + '</li>'
|
||||
return '<li>' + val
|
||||
@@ -26,16 +32,50 @@ function stack(e) {
|
||||
/**
|
||||
* Return table rows for _hash_.
|
||||
*
|
||||
* @param {hash} hash
|
||||
* @param {object} hash
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function hash(hash) {
|
||||
if (!$(hash).length()) return '<tr><td class="empty" colspan="2">Empty</td></tr>'
|
||||
return $(hash).map(function(val, key){
|
||||
return '<tr><td>' + key + ':</td><td>' + JSON.encode(val) + '</td></tr>'
|
||||
}).join('\n')
|
||||
var keys = Object.keys(hash),
|
||||
buf = []
|
||||
if (!keys.length) return '<tr><td class="empty" colspan="2">Empty</td></tr>'
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
buf.push('<tr><td>' + keys[i] + ':</td><td>' + sys.inspect(hash[keys[i]]) + '</td></tr>')
|
||||
return buf.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Return plain-text list items for exception _e_'s stack.
|
||||
*
|
||||
* @param {Error} e
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function stackText(e) {
|
||||
if (e.stack)
|
||||
return e.stack.split('\n').slice(1).map(function(val, i){
|
||||
return '\n ' + val.strip
|
||||
}).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an ascii table for _hash_.
|
||||
*
|
||||
* @param {object} hash
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function hashText(hash) {
|
||||
var keys = Object.keys(hash),
|
||||
buf = ''
|
||||
if (!keys.length) return '\n Empty'
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
buf += '\n ' + keys[i] + ': ' + sys.inspect(hash[keys[i]])
|
||||
return buf
|
||||
}
|
||||
|
||||
exports.render = function(request, e) {
|
||||
@@ -62,17 +102,36 @@ exports.render = function(request, e) {
|
||||
</table> \n\
|
||||
<h3>Params</h3> \n\
|
||||
<table id="route-params"> \n\
|
||||
' + hash(request.params) + ' \n\
|
||||
' + hash(request.params.path) + ' \n\
|
||||
</table> \n\
|
||||
<h3>GET</h3> \n\
|
||||
<table id="get-params"> \n\
|
||||
' + hash(request.url.params) + ' \n\
|
||||
' + hash(request.params.get) + ' \n\
|
||||
</table> \n\
|
||||
<h3>POST</h3> \n\
|
||||
<table id="post-params"> \n\
|
||||
' + hash(request.url.post) + ' \n\
|
||||
' + hash(request.params.post) + ' \n\
|
||||
</table> \n\
|
||||
</div> \n\
|
||||
</body> \n\
|
||||
</html>'
|
||||
}
|
||||
}
|
||||
|
||||
exports.renderText = function(request, e) {
|
||||
request.contentType('text')
|
||||
return '\
|
||||
\n Express \
|
||||
\n\n 500 ' + e + ' \
|
||||
\n' + stackText(e) + ' \
|
||||
\n\n Request\n ' + hashText(request.headers) + '\
|
||||
\n\n Response\n ' + hashText(request.response.headers) + '\
|
||||
\n\n Params\n ' + hashText(request.params.path) + '\
|
||||
\n\n GET\n ' + hashText(request.params.get) + '\
|
||||
\n\n POST\n ' + hashText(request.params.post) + '\n'
|
||||
}
|
||||
|
||||
exports.renderJSON = function(request, e) {
|
||||
request.contentType('json')
|
||||
delete e.stack
|
||||
return JSON.encode({ error: e })
|
||||
}
|
||||
|
||||
+4
-16
@@ -14,7 +14,7 @@
|
||||
exports.use = function(plugin, options) {
|
||||
if (Express.environment === 'test' && 'init' in plugin)
|
||||
plugin.init(options)
|
||||
$(Express.plugins).each(function(other, i){
|
||||
Express.plugins.each(function(other, i){
|
||||
if (other.klass === plugin)
|
||||
delete Express.plugins[i]
|
||||
})
|
||||
@@ -26,19 +26,7 @@ exports.use = function(plugin, options) {
|
||||
|
||||
// --- Plugin
|
||||
|
||||
exports.Plugin = Class({
|
||||
|
||||
/**
|
||||
* Initialize with _options_.
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
if (options)
|
||||
process.mixin(this, options)
|
||||
},
|
||||
exports.Plugin = new Class({
|
||||
|
||||
/**
|
||||
* Trigger handler for the given _event_.
|
||||
@@ -47,9 +35,9 @@ exports.Plugin = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
trigger: function(event) {
|
||||
trigger: function(event, callback) {
|
||||
if ('on' in this)
|
||||
if (event.name in this.on)
|
||||
this.on[event.name].call(this, event)
|
||||
return this.on[event.name].call(this, event, callback)
|
||||
}
|
||||
})
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
// Express - Plugins - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
process.mixin(require('express/plugins/hooks'))
|
||||
process.mixin(require('express/plugins/flash'))
|
||||
process.mixin(require('express/plugins/cache'))
|
||||
process.mixin(require('express/plugins/cookie'))
|
||||
process.mixin(require('express/plugins/session'))
|
||||
process.mixin(require('express/plugins/profiler'))
|
||||
process.mixin(require('express/plugins/common-logger'))
|
||||
process.mixin(require('express/plugins/content-length'))
|
||||
process.mixin(require('express/plugins/method-override'))
|
||||
Object.merge(global, require('express/plugins/hooks'))
|
||||
Object.merge(global, require('express/plugins/static'))
|
||||
Object.merge(global, require('express/plugins/flash'))
|
||||
Object.merge(global, require('express/plugins/cache'))
|
||||
Object.merge(global, require('express/plugins/cookie'))
|
||||
Object.merge(global, require('express/plugins/session'))
|
||||
Object.merge(global, require('express/plugins/logger'))
|
||||
Object.merge(global, require('express/plugins/content-length'))
|
||||
Object.merge(global, require('express/plugins/method-override'))
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
|
||||
// Express - BodyDecoder - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var queryString = require('querystring')
|
||||
|
||||
// --- BodyDecoder
|
||||
|
||||
exports.BodyDecoder = Plugin.extend({
|
||||
on: {
|
||||
|
||||
@@ -14,8 +20,8 @@ exports.BodyDecoder = Plugin.extend({
|
||||
|
||||
request: function(event) {
|
||||
var request = event.request
|
||||
if (request.header('content-type') &&
|
||||
request.header('content-type').indexOf('application/x-www-form-urlencoded') > -1)
|
||||
if (request.header('Content-Type') &&
|
||||
request.header('Content-Type').includes('application/x-www-form-urlencoded'))
|
||||
request.params.post = queryString.parseQuery(request.body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
|
||||
// Express - Cache - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('express/request').Request
|
||||
|
||||
// --- Cache
|
||||
|
||||
var Cache = Class({
|
||||
var Cache = new Class({
|
||||
|
||||
/**
|
||||
* Initialize cache with _key_ and _val_.
|
||||
*/
|
||||
|
||||
init: function(key, val) {
|
||||
constructor: function(key, val) {
|
||||
this.key = key
|
||||
this.val = val
|
||||
this.created = Number(new Date)
|
||||
this.created = Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store
|
||||
|
||||
exports.Store = Class({
|
||||
exports.Store = new Class({
|
||||
|
||||
/**
|
||||
* Ensure that the given _key_ is a string.
|
||||
@@ -26,11 +32,13 @@ exports.Store = Class({
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set: function(key, val) {
|
||||
if (typeof key !== 'string') throw new Error(this.name + ' store #set() key must be a string')
|
||||
set: function(key, val, callback) {
|
||||
if (typeof key !== 'string')
|
||||
throw new Error(this.name + ' store #set() key must be a string')
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -38,11 +46,13 @@ exports.Store = Class({
|
||||
* Override in subclass to provide data-store specific functionality.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get: function(key) {
|
||||
if (typeof key !== 'string') throw new Error(this.name + 'store #get() key must be a string')
|
||||
get: function(key, callback) {
|
||||
if (typeof key !== 'string')
|
||||
throw new Error(this.name + 'store #get() key must be a string')
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -71,22 +81,24 @@ exports.Store.Memory = exports.Store.extend({
|
||||
* Initialize data.
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
constructor: function() {
|
||||
this.data = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the given _key_ to _val_, returning _val_.
|
||||
* Set the given _key_ to _val_.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @param {function} callback
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set: function(key, val) {
|
||||
this.__super__(key, val)
|
||||
return this.data[key] = new Cache(key, val), val
|
||||
set: function(key, val, callback) {
|
||||
exports.Store.prototype.set.apply(this, arguments)
|
||||
this.data[key] = new Cache(key, val)
|
||||
if (callback) callback(val)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -94,31 +106,33 @@ exports.Store.Memory = exports.Store.extend({
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* cache.get('page:front')
|
||||
* cache.get('page:front', function(val){})
|
||||
* // => '<html>...</html>'
|
||||
*
|
||||
* cache.get('page:*')
|
||||
* cache.get('page:*', function(vals){})
|
||||
* // => { 'page:front': '<html>...</html>',
|
||||
* 'page:users': '<html>...</html>',
|
||||
* ... }
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} callback
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get: function(key) {
|
||||
this.__super__(key)
|
||||
get: function(key, callback) {
|
||||
exports.Store.prototype.get.apply(this, arguments)
|
||||
if (key.indexOf('*') === -1)
|
||||
return this.data[key] instanceof Cache ?
|
||||
this.data[key].val :
|
||||
null
|
||||
var regexp = this.normalize(key)
|
||||
return $(this.data).reduce({}, function(vals, cache){
|
||||
if (regexp.test(cache.key))
|
||||
vals[cache.key] = cache.val
|
||||
return vals
|
||||
})
|
||||
return callback(this.data[key] instanceof Cache
|
||||
? this.data[key].val
|
||||
: null)
|
||||
var regexp = this.normalize(key),
|
||||
keys = Object.keys(this.data),
|
||||
matches = {}
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
if (regexp.test(keys[i]))
|
||||
matches[keys[i]] = this.data[keys[i]].val
|
||||
callback(matches)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -126,37 +140,42 @@ exports.Store.Memory = exports.Store.extend({
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* cache.clear('page:front')
|
||||
* cache.clear('page:*')
|
||||
* cache.clear('page:front', function(){})
|
||||
* cache.clear('page:*', function(){})
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clear: function(key) {
|
||||
clear: function(key, callback) {
|
||||
if (key.indexOf('*') === -1)
|
||||
return delete this.data[key]
|
||||
var regexp = this.normalize(key)
|
||||
for (var key in this.data)
|
||||
if (this.data.hasOwnProperty(key))
|
||||
if (regexp.test(key))
|
||||
delete this.data[key]
|
||||
delete this.data[key]
|
||||
else {
|
||||
var regexp = this.normalize(key),
|
||||
keys = Object.keys(this.data)
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
if (regexp.test(keys[i]))
|
||||
delete this.data[keys[i]]
|
||||
}
|
||||
callback()
|
||||
},
|
||||
|
||||
/**
|
||||
* Reap caches older than _ms_.
|
||||
* Reap caches older than _ms_ or caches which
|
||||
* have been "cleared" (null).
|
||||
*
|
||||
* @param {int} ms
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms) {
|
||||
var self = this,
|
||||
threshold = Number(new Date(Number(new Date) - ms))
|
||||
$(this.data).each(function(cache){
|
||||
if (cache.created < threshold)
|
||||
self.clear(cache.key)
|
||||
})
|
||||
var threshold = +new Date(Date.now() - ms),
|
||||
keys = Object.keys(this.data)
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
if (this.data[keys[i]].created < threshold ||
|
||||
this.data[keys[i]].val === null)
|
||||
this.clear(keys[i], function(){})
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -194,7 +213,7 @@ exports.Cache = Plugin.extend({
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
process.mixin(this, options)
|
||||
Object.merge(this, options)
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
Request.include({ cache: this.store })
|
||||
this.startReaper()
|
||||
@@ -207,12 +226,9 @@ exports.Cache = Plugin.extend({
|
||||
*/
|
||||
|
||||
startReaper: function() {
|
||||
var self = this,
|
||||
oneDay = 86400000,
|
||||
oneHour = 3600000
|
||||
setInterval(function(){
|
||||
self.store.reap(self.lifetime || oneDay)
|
||||
}, self.reapInterval || self.reapEvery || oneHour)
|
||||
setInterval(function(self){
|
||||
self.store.reap(self.lifetime || (1).day)
|
||||
}, this.reapInterval || this.reapEvery || (1).hour, this)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
// Express - CommonLogger - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Months.
|
||||
*/
|
||||
|
||||
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
|
||||
/**
|
||||
* Format _date_.
|
||||
*
|
||||
* @param {Date} date
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function format(date) {
|
||||
var d = date.getDate(),
|
||||
m = months[date.getMonth()],
|
||||
y = date.getFullYear(),
|
||||
h = date.getHours(),
|
||||
mi = date.getMinutes(),
|
||||
s = date.getSeconds()
|
||||
return (d < 10 ? '0' : '') + d + '/' + m + '/' + y + ' ' +
|
||||
(h < 10 ? '0' : '') + h + ':' + (mi < 10 ? '0' : '') +
|
||||
mi + ':' + (s < 10 ? '0' : '') + s
|
||||
}
|
||||
|
||||
// --- CommonLogger
|
||||
|
||||
exports.CommonLogger = Plugin.extend({
|
||||
on: {
|
||||
|
||||
/**
|
||||
* Output log data.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
puts([event.request.headers.host,
|
||||
'-',
|
||||
'-',
|
||||
'[' + format(new Date) + ']',
|
||||
'"' + event.request.method.toUpperCase() + ' ' + (event.request.url.pathname || '/') +
|
||||
' HTTP/' + event.request.httpVersion + '"',
|
||||
event.request.response.status,
|
||||
event.request.response.headers['content-length'] || 0].join(' '))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -10,9 +10,9 @@ exports.ContentLength = Plugin.extend({
|
||||
|
||||
response: function(event) {
|
||||
var response = event.request.response
|
||||
response.headers['content-length'] =
|
||||
response.headers['content-length'] ||
|
||||
response.body.length
|
||||
if (!response.chunkedEncoding)
|
||||
if (!response.headers['Content-Length'] && response.body)
|
||||
response.headers['Content-Length'] = response.body.length
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,12 @@
|
||||
|
||||
// Express - Cookie - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('express/request').Request
|
||||
|
||||
/**
|
||||
* Parse an HTTP _cookie_ string into a hash.
|
||||
*
|
||||
@@ -10,11 +16,11 @@
|
||||
*/
|
||||
|
||||
exports.parseCookie = function(cookie) {
|
||||
return $(cookie.replace(/^ *| *$/g, '').split(/ *; */)).reduce({}, function(hash, pair){
|
||||
return cookie.replace(/^ *| *$/g, '').split(/ *; */).reduce(function(hash, pair){
|
||||
var parts = pair.split(/ *= */)
|
||||
hash[parts[0]] = parts[1]
|
||||
return hash
|
||||
})
|
||||
}, {})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,14 +35,18 @@ exports.parseCookie = function(cookie) {
|
||||
|
||||
exports.compileCookie = function(name, val, options) {
|
||||
if (!options) return name + '=' + val
|
||||
return name + '=' + val + '; ' + $(options).map(function(val, key){
|
||||
var val,
|
||||
buf = [name + '=' + val],
|
||||
keys = Object.keys(options)
|
||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
||||
val = options[keys[i]]
|
||||
if (val instanceof Date)
|
||||
val = val.toString()
|
||||
.replace(/^(\w+)/, '$1,')
|
||||
.replace(/(\w+) (\d+) (\d+)/, '$2-$1-$3')
|
||||
.replace(/GMT.*$/, 'GMT')
|
||||
return val === true ? key : key + '=' + val
|
||||
}).toArray().join('; ')
|
||||
val = val.toGMTString()
|
||||
buf.push(val === true
|
||||
? keys[i]
|
||||
: keys[i] + '=' + val)
|
||||
}
|
||||
return buf.join('; ')
|
||||
}
|
||||
|
||||
// --- Cookie
|
||||
@@ -93,9 +103,9 @@ exports.Cookie = Plugin.extend({
|
||||
|
||||
request: function(event) {
|
||||
event.request.response.cookies = []
|
||||
event.request.cookies = event.request.headers.cookie ?
|
||||
exports.parseCookie(event.request.headers.cookie) :
|
||||
{}
|
||||
event.request.cookies = event.request.headers.cookie
|
||||
? exports.parseCookie(event.request.headers.cookie)
|
||||
: {}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -103,8 +113,9 @@ exports.Cookie = Plugin.extend({
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
if (event.response.cookies.length)
|
||||
event.request.header('set-cookie', event.response.cookies.join(', '))
|
||||
if (event.response.cookies &&
|
||||
event.response.cookies.length)
|
||||
event.request.header('Set-Cookie', event.response.cookies.join('\nSet-Cookie: '))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,14 @@
|
||||
|
||||
// Express - Flash - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('express/request').Request
|
||||
|
||||
// --- Flash
|
||||
|
||||
exports.Flash = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
@@ -18,6 +26,9 @@ exports.Flash = Plugin.extend({
|
||||
* it will persist in the session until outputted.
|
||||
* The _val_ pushed is returned.
|
||||
*
|
||||
* When no arguments are given, all flashes are
|
||||
* returned keyed by their type.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* this.flash('info', 'email sent')
|
||||
@@ -30,12 +41,17 @@ exports.Flash = Plugin.extend({
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @return {string}
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
flash: function(key, val) {
|
||||
if (!this.session.flash) this.session.flash = {}
|
||||
if (!key) {
|
||||
var flashes = this.session.flash
|
||||
this.session.flash = {}
|
||||
return flashes
|
||||
}
|
||||
if (!(key in this.session.flash)) this.session.flash[key] = []
|
||||
if (val)
|
||||
return this.session.flash[key].push(val), val
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
|
||||
// Express - Hooks - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Callbacks.
|
||||
*/
|
||||
|
||||
exports.callbacks = { before: [], after: [] }
|
||||
|
||||
/**
|
||||
* Add a _fn_ to be excuted before a request.
|
||||
* Add a _callback_ to be excuted before a request.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.before = function(fn) {
|
||||
exports.callbacks.before.push(fn)
|
||||
exports.before = function(callback) {
|
||||
exports.callbacks.before.push(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a _fn_ to be excuted after a request.
|
||||
* Add a _callback_ to be excuted after a request.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.after = function(fn) {
|
||||
exports.callbacks.after.push(fn)
|
||||
exports.after = function(callback) {
|
||||
exports.callbacks.after.push(callback)
|
||||
}
|
||||
|
||||
// --- Hooks
|
||||
@@ -35,8 +39,10 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
process.mixin(GLOBAL, { before: exports.before,
|
||||
after: exports.after })
|
||||
Object.merge(global, {
|
||||
before: exports.before,
|
||||
after: exports.after
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -49,8 +55,8 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
$(exports.callbacks.before).each(function(fn){
|
||||
fn.call(event.request, event.request)
|
||||
exports.callbacks.before.each(function(callback){
|
||||
callback.call(event.request, event.request)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -59,8 +65,8 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
$(exports.callbacks.after).each(function(fn){
|
||||
fn.call(event.request, event.request)
|
||||
exports.callbacks.after.each(function(callback){
|
||||
callback.call(event.request, event.request)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
// Express - Logger - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var sys = require('sys'),
|
||||
printf = require('ext').printf
|
||||
|
||||
/**
|
||||
* Log formats
|
||||
*/
|
||||
|
||||
var formats = {
|
||||
common: function(event, start) {
|
||||
printf('%s - - [%s] "%s %s HTTP/%d.%d" %s %d %0.4f',
|
||||
event.request.socket.remoteAddress,
|
||||
(new Date).format('%d/%b/%Y %H:%M:%S'),
|
||||
event.request.method.uppercase,
|
||||
event.request.url.pathname || '/',
|
||||
event.request.httpVersionMajor,
|
||||
event.request.httpVersionMinor,
|
||||
event.request.response.status,
|
||||
event.request.response.headers['Content-Length'] || 0,
|
||||
(Date.now() - start) / 1000)
|
||||
},
|
||||
combined: function(event, start) {
|
||||
formats.common(event, start)
|
||||
printf(' "%s" "%s"',
|
||||
event.request.headers['referrer'] || event.request.headers['referer'] || '-',
|
||||
event.request.headers['user-agent'])
|
||||
},
|
||||
plot: function(event, start) {
|
||||
sys.print(Date.now() - start)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Logger
|
||||
|
||||
exports.Logger = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize logger options.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - format
|
||||
* 'common' outputs log in CommonLog format (DEFAULT)
|
||||
* 'combined' outputs log in Apache Combined format
|
||||
* 'request' outputs the HTTP request for debugging
|
||||
* 'plot' outputs request duration in milliseconds only
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
Object.merge(this, options)
|
||||
}
|
||||
},
|
||||
|
||||
on: {
|
||||
|
||||
/**
|
||||
* Start timer.
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
this.start = Date.now()
|
||||
},
|
||||
|
||||
/**
|
||||
* Output log data.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
formats[exports.Logger.format || 'common'](event, this.start || Date.now())
|
||||
sys.print('\n')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
// Express - Profiler - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var n = 0
|
||||
|
||||
exports.Profiler = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize profiler options.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - format 'plot' outputs request duration in milliseconds only
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
process.mixin(this, options)
|
||||
}
|
||||
},
|
||||
|
||||
// --- Events
|
||||
|
||||
on: {
|
||||
|
||||
/**
|
||||
* Start timer.
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
this.start = Number(new Date)
|
||||
},
|
||||
|
||||
/**
|
||||
* Output duration.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
if (exports.Profiler.format === 'plot')
|
||||
puts(Number(new Date) - this.start)
|
||||
else
|
||||
puts(event.request.method + ' ' +
|
||||
event.request.url.pathname + ': ' +
|
||||
(Number(new Date) - this.start) + ' ms' +
|
||||
' #' + ++n)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,14 @@
|
||||
|
||||
// Express - Redirect - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('express/request').Request
|
||||
|
||||
// --- Redirect
|
||||
|
||||
exports.Redirect = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
@@ -32,9 +40,9 @@ exports.Redirect = Plugin.extend({
|
||||
*/
|
||||
|
||||
redirect: function(url, code) {
|
||||
if (url == 'back' || url == 'home') url = this[url]
|
||||
this.header('location', url)
|
||||
this.halt(code || 303)
|
||||
if (url === 'back' || url === 'home') url = this[url]
|
||||
this.header('Location', url)
|
||||
this.respond(code || 303)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -51,7 +59,7 @@ exports.Redirect = Plugin.extend({
|
||||
|
||||
request: function(event) {
|
||||
event.request.home = set('home') || set('basepath') || '/'
|
||||
event.request.back = event.request.header('referrer') || event.request.header('referer')
|
||||
event.request.back = event.request.header('Referrer') || event.request.header('Referer')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -5,17 +5,18 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils')
|
||||
var Request = require('express/request').Request,
|
||||
utils = require('express/utils')
|
||||
|
||||
// --- Session
|
||||
|
||||
var Session = Class({
|
||||
exports.Base = new Class({
|
||||
|
||||
/**
|
||||
* Initialize session _sid_.
|
||||
*/
|
||||
|
||||
init: function(sid) {
|
||||
constructor: function(sid) {
|
||||
this.id = sid
|
||||
this.touch()
|
||||
},
|
||||
@@ -27,13 +28,13 @@ var Session = Class({
|
||||
*/
|
||||
|
||||
touch: function() {
|
||||
this.lastAccess = Number(new Date)
|
||||
this.lastAccess = Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store
|
||||
|
||||
exports.Store = Class({
|
||||
exports.Store = new Class({
|
||||
|
||||
/**
|
||||
* Convert to '[NAME Store]'.
|
||||
@@ -61,64 +62,73 @@ exports.Store.Memory = exports.Store.extend({
|
||||
* Initialize in-memory session store.
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
constructor: function() {
|
||||
this.store = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch session with the given _sid_ or
|
||||
* a new Session is returned.
|
||||
* a new Session is created.
|
||||
*
|
||||
* @param {int} sid
|
||||
* @return {Session}
|
||||
* @param {number} sid
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
fetch: function(sid) {
|
||||
return this.store[sid] || new Session(sid)
|
||||
fetch: function(sid, callback) {
|
||||
if (sid && this.store[sid])
|
||||
callback(null, this.store[sid])
|
||||
else
|
||||
this.generate(callback)
|
||||
},
|
||||
|
||||
/**
|
||||
* Commit _session_ data.
|
||||
*
|
||||
* @param {Session} session
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
commit: function(session) {
|
||||
return this.store[session.id] = session
|
||||
commit: function(session, callback) {
|
||||
this.store[session.id] = session
|
||||
if (callback) callback(null, session)
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all sessions.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clear: function() {
|
||||
clear: function(callback) {
|
||||
this.store = {}
|
||||
if (callback) callback()
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy session using the given _sid_.
|
||||
*
|
||||
* @param {int} sid
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
destroy: function(sid) {
|
||||
destroy: function(sid, callback) {
|
||||
delete this.store[sid]
|
||||
if (callback) callback(sid)
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the number of sessions currently stored.
|
||||
* Pass the number of sessions currently stored.
|
||||
*
|
||||
* @return {int}
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
length: function() {
|
||||
return $(this.store).length()
|
||||
length: function(callback) {
|
||||
callback(null, Object.keys(this.store).length)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -129,12 +139,22 @@ exports.Store.Memory = exports.Store.extend({
|
||||
*/
|
||||
|
||||
reap: function(ms) {
|
||||
var self = this,
|
||||
threshold = Number(new Date(Number(new Date) - ms))
|
||||
$(this.store).each(function(session, sid){
|
||||
if (session.lastAccess < threshold)
|
||||
self.destroy(sid)
|
||||
})
|
||||
var threshold = +new Date(Date.now() - ms),
|
||||
sids = Object.keys(this.store)
|
||||
for (var i = 0, len = sids.length; i < len; ++i)
|
||||
if (this.store[sids[i]].lastAccess < threshold)
|
||||
this.destroy(sids[i])
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and passes a shiny new session.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
generate: function(callback) {
|
||||
callback(null, new exports.Base(utils.uid()))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -151,13 +171,16 @@ exports.Session = Plugin.extend({
|
||||
* - dataStore constructor name of session data store, defaults to Store.Memory
|
||||
* - lifetime lifetime of session in milliseconds, defaults to one day
|
||||
* - reapInterval, reapEvery interval in milliseconds in which to reap old sessions, defaults to one hour
|
||||
* - cookie session specific cookie options passed to Request#cookie()
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
process.mixin(this, options)
|
||||
this.cookie = {}
|
||||
Object.merge(this, options)
|
||||
this.cookie.httpOnly = true
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
this.startReaper()
|
||||
},
|
||||
@@ -169,12 +192,9 @@ exports.Session = Plugin.extend({
|
||||
*/
|
||||
|
||||
startReaper: function() {
|
||||
var self = this,
|
||||
oneDay = 86400000,
|
||||
oneHour = 3600000
|
||||
setInterval(function(){
|
||||
self.store.reap(self.lifetime || oneDay)
|
||||
}, self.reapInterval || self.reapEvery || oneHour)
|
||||
setInterval(function(self) {
|
||||
self.store.reap(self.lifetime || (1).day)
|
||||
}, this.reapInterval || this.reapEvery || (1).hour, this)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -186,20 +206,28 @@ exports.Session = Plugin.extend({
|
||||
* Create session id when not found; delegate to store.
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
var sid
|
||||
if (!(sid = event.request.cookie('sid')))
|
||||
event.request.cookie('sid', sid = utils.uid(), set('session cookie'))
|
||||
event.request.session = exports.Session.store.fetch(sid)
|
||||
event.request.session.touch()
|
||||
request: function(event, callback) {
|
||||
var sid = event.request.cookie('sid')
|
||||
if (!sid && event.request.url.pathname === '/favicon.ico') return
|
||||
exports.Session.store.fetch(sid, function(err, session) {
|
||||
if (err) return callback(err)
|
||||
if (session.id != sid)
|
||||
event.request.cookie('sid', session.id, exports.Session.cookie)
|
||||
event.request.session = session
|
||||
event.request.session.touch()
|
||||
callback()
|
||||
})
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* Delegate to store, allowing it to save sessions changes.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
exports.Session.store.commit(event.request.session)
|
||||
response: function(event, callback) {
|
||||
if (event.request.session)
|
||||
return exports.Session.store.commit(event.request.session, callback),
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
// Express - Static - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('express/request').Request,
|
||||
path = require('path'),
|
||||
fs = require('fs')
|
||||
|
||||
// --- Static
|
||||
|
||||
exports.Static = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize routes and request extensions.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - path path from which to serve static files. Defaults to <root>/public
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
options = options || {}
|
||||
options.path = options.path || set('root') + '/public'
|
||||
|
||||
// Routes
|
||||
|
||||
get('/public/*', function(file){
|
||||
this.sendfile(options.path + '/' + file)
|
||||
})
|
||||
|
||||
// Request
|
||||
|
||||
Request.include({
|
||||
|
||||
/**
|
||||
* Transfer static file at the given _path_ with optional _callback_.
|
||||
* _options_ are passed to fs.createReadStream().
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {object} options
|
||||
* @param {function} callback
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sendfile: function(path, options, callback) {
|
||||
var self = this
|
||||
if (options instanceof Function)
|
||||
callback = options,
|
||||
options = {}
|
||||
if (path.indexOf('..') !== -1)
|
||||
Error.raise('InvalidPathError', "`" + path + "' is not a valid path")
|
||||
fs.stat(path, function(err, stat){
|
||||
if (err)
|
||||
return 'errno' in err && err.errno === 2
|
||||
? self.notFound()
|
||||
: self.error(err, callback)
|
||||
var etag = stat.ino + '-' + stat.size + '-' + Number(stat.mtime)
|
||||
if (self.header('If-None-Match') &&
|
||||
self.header('If-None-Match') == etag)
|
||||
return self.respond(304, null)
|
||||
self.header('Content-Length', stat.size)
|
||||
self.header('ETag', etag)
|
||||
self.stream(fs.createReadStream(path, options))
|
||||
})
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static _file_ as an attachment.
|
||||
*
|
||||
* The basename of _file_ is used as the attachment _filename_ when
|
||||
* not explicitly passed.
|
||||
*
|
||||
* @param {string} file
|
||||
* @param {string} filename
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
download: function(file, filename) {
|
||||
return this.attachment(filename || path.basename(file)).sendfile(file)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
+116
-40
@@ -5,17 +5,41 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var posix = require('posix'),
|
||||
utils = require('express/utils')
|
||||
var Request = require('express/request').Request,
|
||||
extname = require('path').extname,
|
||||
fs = require('fs')
|
||||
|
||||
/**
|
||||
* Supported template engines.
|
||||
* Cache supported template engine exports.
|
||||
*/
|
||||
|
||||
var engine = {
|
||||
ejs: require('ejs'),
|
||||
haml: require('haml'),
|
||||
sass: require('sass')
|
||||
var engines = {}
|
||||
|
||||
/**
|
||||
* View cache.
|
||||
*/
|
||||
|
||||
var cache = { views: {}, partials: {}}
|
||||
|
||||
/**
|
||||
* Cache view files where _type_ is
|
||||
* "partials" or "views".
|
||||
*
|
||||
* @param {string} type
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function cacheFiles(type) {
|
||||
var dir = set(type)
|
||||
try {
|
||||
fs.readdirSync(dir).each(function(file){
|
||||
file = dir + '/' + file
|
||||
if (!fs.statSync(file).isFile()) return
|
||||
cache[type][file] = fs.readFileSync(file)
|
||||
})
|
||||
} catch (e) {
|
||||
if (e.errno !== process.ENOENT) throw e
|
||||
}
|
||||
}
|
||||
|
||||
// --- View
|
||||
@@ -31,68 +55,120 @@ exports.View = Plugin.extend({
|
||||
|
||||
// Settings
|
||||
|
||||
set('views', function(){ return set('root') + '/views' })
|
||||
if (!set('views'))
|
||||
set('views', function(){ return set('root') + '/views' })
|
||||
|
||||
if (!set('partials'))
|
||||
set('partials', function(){ return set('views') + '/partials' })
|
||||
|
||||
// Cache views in memory
|
||||
|
||||
if (set('cache view contents'))
|
||||
cacheFiles('views')
|
||||
|
||||
if (set('cache view partials'))
|
||||
cacheFiles('partials')
|
||||
|
||||
// Request
|
||||
|
||||
Request.include({
|
||||
|
||||
/**
|
||||
* Render _view_ partial with _options_.
|
||||
* View Request#render() for additional options.
|
||||
*
|
||||
* Options:
|
||||
* - as: String name for the id used to which "collection" assign it's current value.
|
||||
* - collection: Array of objects, the name is derived from
|
||||
* the view name itself. For example 'video.haml.html'
|
||||
* will have an object "video" available to it.
|
||||
*
|
||||
* @param {string} view
|
||||
* @param {hash} options
|
||||
* @settings 'partials', 'cache view partials'
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
partial: function(view, options) {
|
||||
var options = options || {}
|
||||
options.partial = true
|
||||
options.layout = false
|
||||
if (options.collection) {
|
||||
var name = options.as || view.split('.').first,
|
||||
len = options.collection.length
|
||||
options.locals = options.locals || {}
|
||||
options.locals.__length__ = len
|
||||
return options.collection.map(function(val, i){
|
||||
options.locals.__isFirst__ = i === 0
|
||||
options.locals.__index__ = i
|
||||
options.locals.__isLast__ = i === len - 1
|
||||
options.locals[name] = val
|
||||
return this.render(view, options)
|
||||
}, this).join('')
|
||||
} else
|
||||
return this.render(view, options)
|
||||
},
|
||||
|
||||
/**
|
||||
* Render _view_ with _options_.
|
||||
*
|
||||
* Views are looked up relative to the'views' path setting.
|
||||
* View filenames should conform to ANY.ENGINE.TYPE so for example
|
||||
* 'layout.ejs.html', 'ejs' represents the template engine, 'html'
|
||||
* View filenames should conform to NAME.TYPE.ENGINE so for example
|
||||
* 'layout.html.ejs', 'ejs' represents the template engine, 'html'
|
||||
* represents the type of content being rendered, which is then passed
|
||||
* to contentType().
|
||||
*
|
||||
* Engines must export a render() method accepting the template string
|
||||
* and a hash of options.
|
||||
* and a hash of options. Engines can respond to the options listed below
|
||||
* as well as their own arbitrary ones. The "filename" option is always
|
||||
* passed as the path to the given _view_, allowing engines to perform
|
||||
* better error reporting.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - encoding: Passed to Request#respond()
|
||||
* - layout: The layout to use, none when falsey. Defaults to 'layout'
|
||||
* - locals: Most engines support a hash of local variable names / values.
|
||||
* - context: Most engines support an evaluation context (the 'this' keyword).
|
||||
* Defaults to the current Request instance.
|
||||
*
|
||||
* Optionally you may also pass a _callback_ function which
|
||||
* will be called instead of responding with the 200 status code.
|
||||
*
|
||||
* @param {string} view
|
||||
* @param {hash} options
|
||||
* @param {object} options
|
||||
* @param {function} callback
|
||||
* @settings 'views', 'cache view contents'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
render: function(view, options) {
|
||||
var self = this,
|
||||
options = options || {},
|
||||
path = set('views') + '/' + view,
|
||||
type = path.split('.').slice(-2)[0],
|
||||
ext = utils.extname(path),
|
||||
render: function(view, options, callback) {
|
||||
var options = options || {},
|
||||
type = options.partial ? 'partials' : 'views',
|
||||
path = set(type) + '/' + view,
|
||||
parts = view.split('.'),
|
||||
engine = parts.last,
|
||||
contentType = parts.slice(-2)[0],
|
||||
layout = options.layout === undefined ? 'layout' : options.layout
|
||||
options.filename = path
|
||||
if (set('cache view contents'))
|
||||
options.cache = true
|
||||
var content = cache[type][path] || fs.readFileSync(path)
|
||||
options.context = options.context || this
|
||||
self.contentType(ext)
|
||||
function render(content) {
|
||||
content = engine[type].render(content, options)
|
||||
if (layout)
|
||||
self.render(layout + '.' + type + '.' + ext, process.mixin(true, options, {
|
||||
layout: false,
|
||||
locals: {
|
||||
body: content
|
||||
}
|
||||
}))
|
||||
else
|
||||
self.halt(200, content)
|
||||
}
|
||||
if (set('cache view contents') && self.cache.get(path))
|
||||
render(self.cache.get(path))
|
||||
content = (engines[engine] = engines[engine] || require(engine)).render(content, options)
|
||||
if (type === 'views') this.contentType(contentType)
|
||||
if (layout)
|
||||
this.render([layout, contentType, engine].join('.'), Object.mergeDeep(options, {
|
||||
layout: false,
|
||||
locals: { body: content }
|
||||
}), callback)
|
||||
else if (type === 'partials')
|
||||
return content
|
||||
else if (callback)
|
||||
callback.call(this, null, content)
|
||||
else
|
||||
posix.cat(path).addCallback(function(content){
|
||||
set('cache view contents') ?
|
||||
render(self.cache.set(path, content)) :
|
||||
render(content)
|
||||
}).addErrback(function(e){
|
||||
throw e
|
||||
})
|
||||
this.respond(200, content, options.encoding)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+267
-101
@@ -5,29 +5,15 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var StaticFile = require('express/static').File,
|
||||
var Event = require('express/event').Event,
|
||||
showExceptions = require('express/pages/show-exceptions'),
|
||||
notFound = require('express/pages/not-found'),
|
||||
statusBodies = require('http').STATUS_CODES,
|
||||
queryString = require('querystring'),
|
||||
mime = require('express/mime'),
|
||||
url = require('url')
|
||||
|
||||
// --- InvalidStatusCode
|
||||
|
||||
InvalidStatusCode = ExpressError.extend({
|
||||
name: 'InvalidStatusCode',
|
||||
init: function(status) {
|
||||
this.message = status + ' is an invalid HTTP response code'
|
||||
}
|
||||
})
|
||||
|
||||
// --- InvalidResponseBody
|
||||
|
||||
InvalidResponseBody = ExpressError.extend({
|
||||
name: 'InvalidResponseBody',
|
||||
init: function(request) {
|
||||
this.message = request.method + ' ' + JSON.encode(request.url.pathname) + ' did not respond with a body string'
|
||||
}
|
||||
})
|
||||
url = require('url'),
|
||||
ext = require('ext'),
|
||||
sys = require('sys')
|
||||
|
||||
// --- Helpers
|
||||
|
||||
@@ -46,7 +32,7 @@ exports.normalizePath = function(path) {
|
||||
|
||||
// --- Request
|
||||
|
||||
exports.Request = Class({
|
||||
exports.Request = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with node's _request_ and _response_ objects.
|
||||
@@ -61,20 +47,21 @@ exports.Request = Class({
|
||||
* @param {object} response
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(request, response) {
|
||||
process.mixin(true, this, request)
|
||||
|
||||
constructor: function(request, response) {
|
||||
Object.merge(this, request)
|
||||
response.headers = {}
|
||||
this.response = response
|
||||
this.response = response
|
||||
this.url = url.parse(this.url)
|
||||
this.url.pathname = exports.normalizePath(this.url.pathname)
|
||||
this.params = this.params || {}
|
||||
this.params.path = {}
|
||||
this.url.pathname = exports.normalizePath(this.url.pathname)
|
||||
this.params = {}
|
||||
this.params.path = {}
|
||||
this.params.get = this.url.query ? queryString.parseQuery(this.url.query) : {}
|
||||
this.params.post = this.params.post || {}
|
||||
this.plugins = $(Express.plugins).map(function(plugin){
|
||||
this.plugins = Express.plugins.map(function(plugin){
|
||||
return new plugin.klass(plugin.options)
|
||||
})
|
||||
this.reversedPlugins = this.plugins.slice(0).reverse()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -88,17 +75,17 @@ exports.Request = Class({
|
||||
*/
|
||||
|
||||
header: function(key, val) {
|
||||
return val === undefined ?
|
||||
this.headers[key.toLowerCase()] :
|
||||
this.response.headers[key.toLowerCase()] = val
|
||||
return val === undefined
|
||||
? this.headers[key.lowercase]
|
||||
: this.response.headers[key] = val
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a param _key_ value or null.
|
||||
* Return the param _key_ value or undefined.
|
||||
*
|
||||
* - Checks route populated path parameters
|
||||
* - Checks POST parameters
|
||||
* - Checks GET parameters
|
||||
* - Checks POST parameters
|
||||
* - Checks route populated path parameters
|
||||
*
|
||||
* @param {string} key
|
||||
* @return {string}
|
||||
@@ -106,9 +93,12 @@ exports.Request = Class({
|
||||
*/
|
||||
|
||||
param: function(key) {
|
||||
return this.params.get[key] ||
|
||||
this.params.post[key] ||
|
||||
this.params.path[key]
|
||||
if (key in this.params.get)
|
||||
return this.params.get[key]
|
||||
if (key in this.params.post)
|
||||
return this.params.post[key]
|
||||
if (key in this.params.path)
|
||||
return this.params.path[key]
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -132,20 +122,30 @@ exports.Request = Class({
|
||||
|
||||
accepts: function() {
|
||||
var accept = this.header('accept')
|
||||
return accept ?
|
||||
$(arguments).any(function(path){
|
||||
var type = mime.type(path)
|
||||
return accept.indexOf(type) !== -1 ||
|
||||
accept.indexOf(type.split('/')[0]+'/*') !== -1
|
||||
}) :
|
||||
true
|
||||
if (!accept || accept === '*/*') return true
|
||||
return Object.values(arguments).any(function(path){
|
||||
var type = mime.type(path)
|
||||
return accept.indexOf(type) !== -1 ||
|
||||
accept.indexOf(type.split('/')[0] + '/*') !== -1
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the request was an xmlHttpRequest (ajax).
|
||||
*
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get isXHR() {
|
||||
return (this.header('X-Requested-With') || '').lowercase === 'xmlhttprequest'
|
||||
},
|
||||
|
||||
/**
|
||||
* Set response status to _code_.
|
||||
*
|
||||
* @param {int} code
|
||||
* @return {Request)
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -155,45 +155,138 @@ exports.Request = Class({
|
||||
},
|
||||
|
||||
/**
|
||||
* Immediately respond with response _code_, _body_ and optional _encoding_.
|
||||
* Respond with response _code_, and optional _body_.
|
||||
*
|
||||
* The status _code_ defaults to to 404, and _body_ will
|
||||
* default to the default body associated with the response
|
||||
* _code_.
|
||||
*
|
||||
* When an invalid status _code_ is passed, InvalidStatusCode
|
||||
* will be thrown.
|
||||
* The _callback_ may be given as the 3rd or 4th argument,
|
||||
* which is then called when an error occurs OR when the response
|
||||
* has been completed.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* request.respond()
|
||||
* // 404 "Not Found"
|
||||
*
|
||||
* request.respond(500)
|
||||
* // 500 "Internal Server Error"
|
||||
*
|
||||
* request.respond(200, "im ok")
|
||||
* // 200 "im ok"
|
||||
*
|
||||
* request.respond(200, "√ woot", "utf8")
|
||||
* // "utf8" will set Request#charset to 'UTF-8'
|
||||
*
|
||||
* @param {int} code
|
||||
* @param {string} body
|
||||
* @param {string} encoding
|
||||
* @param {string|function} encoding
|
||||
* @param {function} callback
|
||||
* @see statusBodies
|
||||
* @api public
|
||||
*/
|
||||
|
||||
halt: function(code, body, encoding) {
|
||||
respond: function(code, body, encoding, callback) {
|
||||
var self = this
|
||||
this.status(code = code || 404)
|
||||
if (body = body || statusBodies[code])
|
||||
return this.respond(body, encoding)
|
||||
throw new InvalidStatusCode(code)
|
||||
if (encoding instanceof Function)
|
||||
callback = encoding,
|
||||
encoding = null
|
||||
if (body !== null)
|
||||
body = body || statusBodies[code]
|
||||
if (encoding === 'utf8' ||
|
||||
encoding === 'utf-8')
|
||||
this.charset = 'UTF-8'
|
||||
this.response.body = body
|
||||
this.trigger('response', function(err) {
|
||||
if (err) return self.error(err, callback)
|
||||
self.sendHead()
|
||||
if (body) self.response.write(self.response.body, encoding)
|
||||
self.response.end()
|
||||
if (callback) callback()
|
||||
}, true)
|
||||
},
|
||||
|
||||
// >>> DEPRECATED: remove in 1.0
|
||||
|
||||
halt: function(){
|
||||
ext.warn('Request#halt() has been renamed Request#respond()')
|
||||
this.respond.apply(this, arguments)
|
||||
},
|
||||
|
||||
// <<< DEPRECATED
|
||||
|
||||
/**
|
||||
* Transfer the given _stream_ with optional _callback_.
|
||||
*
|
||||
* @param {Stream} stream
|
||||
* @param {function} callback
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
stream: function(stream, callback) {
|
||||
var self = this,
|
||||
first = true
|
||||
stream
|
||||
.addListener('error', function(err){
|
||||
if (first)
|
||||
return self.error(err, callback)
|
||||
stream.destroy()
|
||||
self.response.end()
|
||||
})
|
||||
.addListener('data', function(data){
|
||||
if (first) {
|
||||
first = false
|
||||
self.header('Transfer-Encoding', 'chunked')
|
||||
self.status(200)
|
||||
self.contentType(stream.path)
|
||||
self.sendHead()
|
||||
}
|
||||
self.response.write(data, 'binary')
|
||||
})
|
||||
.addListener('end', function(){
|
||||
self.trigger('response', function(err){
|
||||
if (err)
|
||||
return self.error(err, callback)
|
||||
self.response.end()
|
||||
}, true)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Respond with _body_ and optional _encoding_.
|
||||
* Send the response header.
|
||||
*
|
||||
* @param {string} body
|
||||
* @param {string} encoding
|
||||
* @see Request#halt()
|
||||
* @api private
|
||||
* If Request#charset is defined, and the
|
||||
* "Content-Type" header, then "; charset=CHARSET" is
|
||||
* appended.
|
||||
*
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
respond: function(body, encoding) {
|
||||
this.response.body = body
|
||||
this.trigger('response')
|
||||
if (typeof this.response.body != 'string') throw new InvalidResponseBody(this)
|
||||
if (typeof this.response.status != 'number') throw new InvalidStatusCode(this.response.status)
|
||||
this.response.sendHeader(this.response.status, this.response.headers)
|
||||
this.response.sendBody(this.response.body, encoding)
|
||||
this.response.finish()
|
||||
sendHead: function(){
|
||||
if (this.charset && this.response.headers['Content-Type'])
|
||||
this.response.headers['Content-Type'] += '; charset=' + this.charset
|
||||
this.response.writeHead(this.response.status, this.response.headers)
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Pass control to the next matching route, or
|
||||
* the given _path_.
|
||||
*
|
||||
* NOTE: _path_ may be the request pathname only,
|
||||
* and may not contain a query string etc.
|
||||
*
|
||||
* @param {string} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
pass: function(path) {
|
||||
this.passed = path || true
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -206,69 +299,142 @@ exports.Request = Class({
|
||||
*/
|
||||
|
||||
contentType: function(path) {
|
||||
this.header('content-type', mime.type(path))
|
||||
this.header('Content-Type', mime.type(path))
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger even _name_ with optional _data_.
|
||||
* Trigger event _name_ with optional _data_ and _callback_ function.
|
||||
* The _callback_ function may be the second or third argument.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {hash} data
|
||||
* @param {object} data
|
||||
* @param {function} callback
|
||||
* @param {bool} reverse
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
trigger: function(name, data) {
|
||||
data = process.mixin(data || {}, {
|
||||
request: this,
|
||||
response: this.response
|
||||
})
|
||||
this.plugins.each(function(plugin){
|
||||
plugin.trigger(new Event(name, data))
|
||||
})
|
||||
trigger: function(name, data, callback, reverse) {
|
||||
if (data instanceof Function)
|
||||
reverse = callback,
|
||||
callback = data,
|
||||
data = null
|
||||
data = Object.merge({ request: this, response: this.response }, data)
|
||||
var self = this,
|
||||
complete = 0,
|
||||
total = this.plugins.length,
|
||||
plugins = reverse
|
||||
? self.reversedPlugins
|
||||
: self.plugins
|
||||
;(function next(err) {
|
||||
if (err || complete === total)
|
||||
callback(err)
|
||||
else {
|
||||
// TODO: remove when this issue is resolved...
|
||||
if (plugins[complete] === undefined)
|
||||
++complete,
|
||||
next()
|
||||
else if (plugins[complete++].trigger(new Event(name, data), next) !== true)
|
||||
next()
|
||||
}
|
||||
})()
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static file at the given _path_ as an attachment.
|
||||
* The basename of _path_ is used as the attachment filename.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
download: function(path) {
|
||||
return this.attachment(basename(path)).sendfile(path)
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Content-Disposition header to 'attachment',
|
||||
* with optional file _path_.
|
||||
* with optional file _filename_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {string} filename
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
attachment: function(path) {
|
||||
this.header('content-disposition', path ?
|
||||
'attachment; filename="' + path + '"' :
|
||||
'attachment')
|
||||
attachment: function(filename) {
|
||||
this.header('Content-Disposition', filename
|
||||
? 'attachment; filename="' + filename + '"'
|
||||
: 'attachment')
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static file at the given _path_.
|
||||
* Handle exceptions.
|
||||
*
|
||||
* When an error route is defined via the DSL it
|
||||
* will then be called regardless of any setting that
|
||||
* may be present. ("throw exceptions" will still work)
|
||||
*
|
||||
* error(function(err){
|
||||
* this.respond(500, 'your app sucks!')
|
||||
* })
|
||||
*
|
||||
* When "show exceptions" is enabled the show-exceptions page will be shown for "text/html",
|
||||
* a plain-text representation of the error for "text/plain" and JSON for "application/json".
|
||||
* Otherwise the request will halt with default 500 status body.
|
||||
*
|
||||
* When "throw exceptions" is enabled the error will be
|
||||
* re-thrown, terminating the process (unless otherwise caught).
|
||||
*
|
||||
* Also a _callback_ function may be supplied, which when defined
|
||||
* will be called, by-passing the process mentioned above.
|
||||
*
|
||||
* @param {Error} err
|
||||
* @param {function} callback
|
||||
* @return {Request}
|
||||
* @settings 'throw exceptions', 'show exceptions'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
error: function(err, callback) {
|
||||
if (callback)
|
||||
return callback(err)
|
||||
if (Express.error)
|
||||
Express.error.call(this, err)
|
||||
else if (set('show exceptions'))
|
||||
if (this.accepts('html'))
|
||||
this.respond(500, showExceptions.render(this, err))
|
||||
else if (this.accepts('json'))
|
||||
this.respond(500, showExceptions.renderJSON(this, err))
|
||||
else if (this.accepts('text'))
|
||||
this.respond(500, showExceptions.renderText(this, err))
|
||||
else
|
||||
this.respond(500)
|
||||
else
|
||||
this.respond(500)
|
||||
if (set('dump exceptions'))
|
||||
sys.puts(err.stack)
|
||||
if (set('throw exceptions'))
|
||||
throw err
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Halt with 404 Not Found.
|
||||
*
|
||||
* When a notFound route is defined via the DSL it
|
||||
* will then be called regardless of any settings that
|
||||
* may be present.
|
||||
*
|
||||
* notFound(function(){
|
||||
* this.respond(404, 'Sorry your page cannot be found')
|
||||
* })
|
||||
*
|
||||
* When "html" is accepted, and "helpful 404" is enabled
|
||||
* the not-found page will be shown, otherwise the
|
||||
* request will halt with default 404 status body.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sendfile: function(path) {
|
||||
(new StaticFile(path)).send(this)
|
||||
notFound: function() {
|
||||
if (Express.notFound)
|
||||
Express.notFound.call(this)
|
||||
else if (this.accepts('html') && set('helpful 404'))
|
||||
this.respond(404, notFound.render(this))
|
||||
else
|
||||
this.respond()
|
||||
return this
|
||||
}
|
||||
})
|
||||
+69
-20
@@ -1,9 +1,57 @@
|
||||
|
||||
// Express - MockRequest - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('express/request').Request,
|
||||
path = require('path'),
|
||||
fs = require('fs')
|
||||
|
||||
/**
|
||||
* Sync fs.readFile()
|
||||
*/
|
||||
|
||||
fs.readFile = function(path, encoding, callback) {
|
||||
if (encoding instanceof Function)
|
||||
callback = encoding,
|
||||
encoding = null
|
||||
try {
|
||||
callback(null, fs.readFileSync(path, encoding))
|
||||
} catch (e) {
|
||||
callback(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync path.exists()
|
||||
*/
|
||||
|
||||
path.exists = function(path, callback) {
|
||||
try {
|
||||
fs.statSync(path)
|
||||
callback(true)
|
||||
} catch (e) {
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync fs.stat()
|
||||
*/
|
||||
|
||||
fs.stat = function(path, callback) {
|
||||
try {
|
||||
callback(null, fs.statSync(path))
|
||||
} catch (e) {
|
||||
callback(e)
|
||||
}
|
||||
}
|
||||
|
||||
// --- MockRequest
|
||||
|
||||
var MockRequest = Class({
|
||||
var MockRequest = new Class({
|
||||
|
||||
/**
|
||||
* Default HTTP version.
|
||||
@@ -20,12 +68,10 @@ var MockRequest = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(method, path, options) {
|
||||
constructor: function(method, path, options) {
|
||||
this.method = method
|
||||
this.url = path
|
||||
this.connection = {
|
||||
remoteAddress: '127.0.0.1'
|
||||
}
|
||||
this.connection = { remoteAddress: '127.0.0.1' }
|
||||
this.headers = {
|
||||
'host': 'localhost',
|
||||
'user-agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-us) AppleWebKit/530.19.2 (KHTML, like Gecko) Version/4.0.2 Safari/530.19',
|
||||
@@ -33,19 +79,19 @@ var MockRequest = Class({
|
||||
'accept-language': 'en-us',
|
||||
'connection': 'keep-alive'
|
||||
}
|
||||
process.mixin(true, this, options)
|
||||
Object.mergeDeep(this, options)
|
||||
}
|
||||
})
|
||||
|
||||
// --- MockResponse
|
||||
|
||||
var MockResponse = Class({
|
||||
var MockResponse = new Class({
|
||||
|
||||
/**
|
||||
* Store _code_ and _headers_.
|
||||
*/
|
||||
|
||||
sendHeader: function(code, headers) {
|
||||
writeHead: function(code, headers) {
|
||||
this.status = code
|
||||
this.headers = headers
|
||||
},
|
||||
@@ -54,7 +100,7 @@ var MockResponse = Class({
|
||||
* Store _body_.
|
||||
*/
|
||||
|
||||
sendBody: function(body) {
|
||||
write: function(body) {
|
||||
this.body = body
|
||||
},
|
||||
|
||||
@@ -62,7 +108,7 @@ var MockResponse = Class({
|
||||
* Flag response as finished.
|
||||
*/
|
||||
|
||||
finish: function() {
|
||||
end: function() {
|
||||
this.finished = true
|
||||
}
|
||||
})
|
||||
@@ -81,11 +127,11 @@ var MockResponse = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function request(method, path, options, fn) {
|
||||
var response = new MockResponse
|
||||
var request = new MockRequest(method, path, options)
|
||||
Express.server.route(request, response)
|
||||
return response
|
||||
function request(method, path, options, callback) {
|
||||
var req = new MockRequest(method, path, options),
|
||||
res = new MockResponse
|
||||
Express.server.route(new Request(req, res))
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +145,9 @@ reset = function() {
|
||||
Express.routes = []
|
||||
Express.plugins = []
|
||||
Express.settings = {}
|
||||
Express.params = {}
|
||||
delete Express.notFound
|
||||
delete Express.error
|
||||
configure('test')
|
||||
}
|
||||
|
||||
@@ -111,13 +160,13 @@ reset = function() {
|
||||
*/
|
||||
|
||||
function route(method) {
|
||||
return function(path, options, fn){
|
||||
return function(path, options, callback){
|
||||
if (options instanceof Function)
|
||||
fn = options, options = {}
|
||||
if (fn instanceof Function)
|
||||
Express.routes.push(new Route(method, path, fn, options))
|
||||
callback = options, options = {}
|
||||
if (callback instanceof Function)
|
||||
Express.routes.push(new Route(method, path, callback, options))
|
||||
else
|
||||
return request(method, path, options, fn)
|
||||
return request(method, path, options, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
|
||||
// Express - Static - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var path = require('path'),
|
||||
posix = require('posix')
|
||||
|
||||
// --- InvalidPathError
|
||||
|
||||
InvalidPathError = ExpressError.extend({
|
||||
name: 'InvalidPathError',
|
||||
init: function(path) {
|
||||
this.message = "`" + path + "' is not a valid path"
|
||||
}
|
||||
})
|
||||
|
||||
// --- File
|
||||
|
||||
exports.File = Class({
|
||||
|
||||
/**
|
||||
* Initialize with file _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
init: function(path) {
|
||||
this.path = path
|
||||
if (path.indexOf('..') != -1) throw new InvalidPathError(path)
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static file to the given _request_.
|
||||
*
|
||||
* - Ensures the file exists
|
||||
* - Ensures the file is a regular file (not FIFO, Socket, etc)
|
||||
* - Automatically assigns content type
|
||||
* - Halts with 404 when failing
|
||||
*
|
||||
* @param {Request} request
|
||||
* @settings 'cache static files'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
send: function(request) {
|
||||
var cache, file = this.path
|
||||
if (set('cache static files') && (cache = request.cache.get(file)))
|
||||
return request.contentType(cache.type),
|
||||
request.halt(200, cache.content, 'binary')
|
||||
path.exists(file, function(exists){
|
||||
if (!exists) return request.halt()
|
||||
posix.stat(file).addCallback(function(stats){
|
||||
if (!stats.isFile()) return request.halt()
|
||||
posix.cat(file, 'binary').addCallback(function(content){
|
||||
request.contentType(file)
|
||||
if (set('cache static files'))
|
||||
request.cache.set(file, { type: file, content: content })
|
||||
request.halt(200, content, 'binary')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
+7
-65
@@ -28,32 +28,6 @@ exports.uid = function() {
|
||||
return uid
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the extension name of the given _path_,
|
||||
* or null when not present.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.extname = function(path) {
|
||||
if (path.lastIndexOf('.') < 0) return
|
||||
return path.slice(path.lastIndexOf('.') + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the basename of the given _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.basename = function(path) {
|
||||
return path.split('/').slice(-1)[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters in _html_.
|
||||
*
|
||||
@@ -63,45 +37,13 @@ exports.basename = function(path) {
|
||||
*/
|
||||
|
||||
exports.escape = function(html) {
|
||||
return html.toString()
|
||||
return String(html)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert native array-like objects into an
|
||||
* array with optional _offset_.
|
||||
*
|
||||
* @param {object} arr
|
||||
* @param {int} offset
|
||||
* @return {array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.toArray = function(arr, offset) {
|
||||
return Array.prototype.slice.call(arr, offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape RegExp _chars_ in _string_. Where _chars_
|
||||
* defaults to regular expression special characters.
|
||||
*
|
||||
* _chars_ should be a space delimited list of characters,
|
||||
* for example '[ ] ( )'.
|
||||
*
|
||||
* @param {string} string
|
||||
* @param {string} chars
|
||||
* @return {Type}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.escapeRegexp = function(string, chars) {
|
||||
var specials = (chars || '/ . * + ? | ( ) [ ] { } \\').split(' ').join('|\\')
|
||||
return string.replace(new RegExp('(\\' + specials + ')', 'g'), '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge param _key_ and _val_ into _params_. Key
|
||||
* should be a query string key such as 'user[name]',
|
||||
@@ -118,17 +60,17 @@ exports.mergeParam = function(key, val, params) {
|
||||
var orig = params,
|
||||
keys = key.trim().match(/\w+/g),
|
||||
array = /\[\]$/.test(key)
|
||||
$(keys).reduce(queryString.parseQuery(key), function(parts, key, i){
|
||||
keys.reduce(function(parts, key, i){
|
||||
if (i === keys.length - 1)
|
||||
if (key in params)
|
||||
params[key] instanceof Array ?
|
||||
params[key].push(val) :
|
||||
params[key] = [params[key], val]
|
||||
params[key] instanceof Array
|
||||
? params[key].push(val)
|
||||
: params[key] = [params[key], val]
|
||||
else
|
||||
params[key] = array ? [val] : val
|
||||
if (!(key in params)) params[key] = {}
|
||||
params = params[key]
|
||||
return parts[key]
|
||||
})
|
||||
}, queryString.parseQuery(key))
|
||||
return orig
|
||||
}
|
||||
}
|
||||
|
||||
Submódulo
+1
Submodule lib/support/class added at 5ed0e4aaec
@@ -1,53 +1,25 @@
|
||||
/* Ejs template parser for CommonJS
|
||||
*
|
||||
* Copyright (c) 2009, Howard Rauscher
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* base on:
|
||||
* Simple JavaScript Templating (http://ejohn.org/blog/javascript-micro-templating/)
|
||||
* John Resig - http://ejohn.org/ - MIT Licensed
|
||||
|
||||
/**
|
||||
* Compiled template cache.
|
||||
*/
|
||||
|
||||
(function(){
|
||||
var cache = {};
|
||||
|
||||
var ejs = this.ejs = {};
|
||||
|
||||
ejs.parse = function tmpl(str, options) {
|
||||
options = options || {};
|
||||
options.context = options.context || {};
|
||||
options.locals = options.locals || {};
|
||||
|
||||
// Figure out if we're getting a template, or if we need to
|
||||
// load the template - and be sure to cache the result.
|
||||
var fn = cache[str] ||
|
||||
|
||||
// Generate a reusable function that will serve as a template
|
||||
// generator (and which will be cached).
|
||||
new Function("obj",
|
||||
"var p=[];" +
|
||||
|
||||
// Introduce the data as local variables using with(){}
|
||||
"with(obj){p.push('" +
|
||||
|
||||
// Convert the template into pure JavaScript
|
||||
str
|
||||
.replace(/\-%>(\n|\r)/g, "%>")
|
||||
.replace(/[\t\b\f]/g, " ")
|
||||
.replace(/[\n\r]/g, "\f")
|
||||
.split("<%").join("\t")
|
||||
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
||||
.replace(/\t=(.*?)%>/g, "',$1,'")
|
||||
.split("\t").join("');")
|
||||
.split("%>").join("p.push('")
|
||||
.split("\r").join("\\'").replace(/\f+/g, '\\n') +
|
||||
"');}return p.join('');");
|
||||
|
||||
cache[str] = fn;
|
||||
|
||||
// Provide some basic currying to the user
|
||||
return fn.call(options.context, options.locals);
|
||||
};
|
||||
})();
|
||||
|
||||
exports.render = ejs.parse;
|
||||
|
||||
var cache = {}
|
||||
|
||||
exports.render = function(str, options) {
|
||||
options = options || {}
|
||||
var fn = cache[str] = cache[str] ||
|
||||
new Function("locals",
|
||||
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
||||
"with(locals){p.push('" +
|
||||
str
|
||||
.replace(/[\r\t\n]/g, " ")
|
||||
.split("<%").join("\t")
|
||||
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
||||
.replace(/\t=(.*?)%>/g, "',$1,'")
|
||||
.split("\t").join("');")
|
||||
.split("%>").join("p.push('")
|
||||
.split("\r").join("\\'")
|
||||
+ "');}return p.join('');")
|
||||
return fn(options.locals || {})
|
||||
}
|
||||
|
||||
|
||||
Submódulo
+1
Submodule lib/support/ext added at 99948529a6
+1
-1
Submodule lib/support/haml updated: 908480ed5d...3574abea73
Submodule lib/support/js-oo deleted from 1f94bd8979
Submódulo
+1
Submodule lib/support/multipart added at 0b77a68582
+1
-1
Submodule lib/support/sass updated: 0dd500e7cd...fac9a896b3
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "0.10.0",
|
||||
"keywords": ["framework", "sinatra", "web", "rest", "restful"],
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "git submodule update --init",
|
||||
"test": "make test"
|
||||
},
|
||||
"engines": { "node": ">= 0.1.93" }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
name: Express
|
||||
description: Sinatra inspired web development framework
|
||||
version: 0.10.1
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
%h2!= this.name
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
%h2 Hello
|
||||
externo
+2
@@ -0,0 +1,2 @@
|
||||
%html
|
||||
%body!= body
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
%title= "Viewing " + name
|
||||
%body!= body
|
||||
externo
+3
@@ -0,0 +1,3 @@
|
||||
%ul
|
||||
- each item in items
|
||||
!= this.partial('item.html.haml', { locals: { item: item }})
|
||||
externo
+2
@@ -0,0 +1,2 @@
|
||||
%title= this.title
|
||||
%body!= body
|
||||
@@ -0,0 +1,7 @@
|
||||
%ul
|
||||
- if (__isFirst__)
|
||||
%li.first= article
|
||||
- if (__isLast__)
|
||||
%li.last= article
|
||||
- if (!__isLast__ && !__isFirst__)
|
||||
%li{ class: __index__ }= article
|
||||
+1
@@ -0,0 +1 @@
|
||||
%li= item
|
||||
@@ -0,0 +1 @@
|
||||
%li= vid
|
||||
externo
+2
@@ -0,0 +1,2 @@
|
||||
%h1= name
|
||||
%p= email
|
||||
@@ -24,18 +24,20 @@ JSpec
|
||||
|
||||
// --- Matchers
|
||||
|
||||
matchers : {
|
||||
have_tag : "jQuery(expected, actual).length == 1",
|
||||
matchers : {
|
||||
have_tag : "jQuery(expected, actual).length === 1",
|
||||
have_one : "alias have_tag",
|
||||
have_tags : "jQuery(expected, actual).length > 1",
|
||||
have_many : "alias have_tags",
|
||||
have_child : "jQuery(actual).children(expected).length == 1",
|
||||
have_any : "alias have_tags",
|
||||
have_child : "jQuery(actual).children(expected).length === 1",
|
||||
have_children : "jQuery(actual).children(expected).length > 1",
|
||||
have_text : "jQuery(actual).text() == expected",
|
||||
have_value : "jQuery(actual).val() == expected",
|
||||
have_text : "jQuery(actual).text() === expected",
|
||||
have_value : "jQuery(actual).val() === expected",
|
||||
be_enabled : "!jQuery(actual).attr('disabled')",
|
||||
have_class : "jQuery(actual).hasClass(expected)",
|
||||
|
||||
be_animated : "jQuery(actual).queue().length > 0",
|
||||
|
||||
be_visible : function(actual) {
|
||||
return jQuery(actual).css('display') != 'none' &&
|
||||
jQuery(actual).css('visibility') != 'hidden' &&
|
||||
@@ -45,7 +47,7 @@ JSpec
|
||||
be_hidden : function(actual) {
|
||||
return !JSpec.does(actual, 'be_visible')
|
||||
},
|
||||
|
||||
|
||||
have_classes : function(actual) {
|
||||
return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){
|
||||
return !JSpec.does(actual, 'have_class', arg)
|
||||
@@ -57,6 +59,12 @@ JSpec
|
||||
jQuery(actual).attr(attr)
|
||||
},
|
||||
|
||||
have_event_handlers : function(actual, expected) {
|
||||
return jQuery(actual).data('events') ?
|
||||
jQuery(actual).data('events').hasOwnProperty(expected) :
|
||||
false
|
||||
},
|
||||
|
||||
'be disabled selected checked' : function(attr) {
|
||||
return 'jQuery(actual).attr("' + attr + '")'
|
||||
},
|
||||
|
||||
+260
-144
@@ -4,12 +4,13 @@
|
||||
;(function(){
|
||||
|
||||
JSpec = {
|
||||
version : '3.2.1',
|
||||
version : '4.2.1',
|
||||
assert : true,
|
||||
cache : {},
|
||||
suites : [],
|
||||
modules : [],
|
||||
allSuites : [],
|
||||
sharedBehaviors: [],
|
||||
matchers : {},
|
||||
stubbed : [],
|
||||
options : {},
|
||||
@@ -68,6 +69,31 @@
|
||||
return JSpec.cache[path] =
|
||||
JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) ||
|
||||
JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.html')
|
||||
},
|
||||
|
||||
/**
|
||||
* Load json fixture at _path_.
|
||||
*
|
||||
* JSON fixtures are resolved as:
|
||||
*
|
||||
* - <path>
|
||||
* - <path>.json
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
json_fixture: function(path) {
|
||||
if (!JSpec.cache['json:' + path])
|
||||
JSpec.cache['json:' + path] =
|
||||
JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) ||
|
||||
JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.json')
|
||||
try {
|
||||
return eval('(' + JSpec.cache['json:' + path] + ')')
|
||||
} catch (e) {
|
||||
throw 'json_fixture("' + path + '"): ' + e
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -92,7 +118,7 @@
|
||||
stats: JSpec.stats,
|
||||
options: options,
|
||||
results: map(results.allSuites, function(suite) {
|
||||
if (suite.hasSpecs())
|
||||
if (suite.isExecutable())
|
||||
return {
|
||||
description: suite.description,
|
||||
specs: map(suite.specs, function(spec) {
|
||||
@@ -126,10 +152,10 @@
|
||||
*/
|
||||
|
||||
DOM : function(results, options) {
|
||||
var id = option('reportToId') || 'jspec'
|
||||
var report = document.getElementById(id)
|
||||
var failuresOnly = option('failuresOnly')
|
||||
var classes = results.stats.failures ? 'has-failures' : ''
|
||||
var id = option('reportToId') || 'jspec',
|
||||
report = document.getElementById(id),
|
||||
failuresOnly = option('failuresOnly'),
|
||||
classes = results.stats.failures ? 'has-failures' : ''
|
||||
if (!report) throw 'JSpec requires the element #' + id + ' to output its reports'
|
||||
|
||||
function bodyContents(body) {
|
||||
@@ -145,7 +171,7 @@
|
||||
<span class="passes">Duration: <em>' + results.duration + '</em> ms</span> \
|
||||
</div><table class="suites">' + map(results.allSuites, function(suite) {
|
||||
var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
|
||||
if (displaySuite && suite.hasSpecs())
|
||||
if (displaySuite && suite.isExecutable())
|
||||
return '<tr class="description"><td colspan="2">' + escape(suite.description) + '</td></tr>' +
|
||||
map(suite.specs, function(i, spec) {
|
||||
return '<tr class="' + (i % 2 ? 'odd' : 'even') + '">' +
|
||||
@@ -170,7 +196,7 @@
|
||||
*/
|
||||
|
||||
Terminal : function(results, options) {
|
||||
failuresOnly = option('failuresOnly')
|
||||
var failuresOnly = option('failuresOnly')
|
||||
print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
|
||||
color(" Failures: ", 'bold') + color(results.stats.failures, 'red') +
|
||||
color(" Duration: ", 'bold') + color(results.duration, 'green') + " ms \n")
|
||||
@@ -181,7 +207,7 @@
|
||||
|
||||
each(results.allSuites, function(suite) {
|
||||
var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
|
||||
if (displaySuite && suite.hasSpecs()) {
|
||||
if (displaySuite && suite.isExecutable()) {
|
||||
print(color(' ' + suite.description, 'bold'))
|
||||
each(suite.specs, function(spec){
|
||||
var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
|
||||
@@ -200,33 +226,7 @@
|
||||
})
|
||||
|
||||
quit(results.stats.failures)
|
||||
},
|
||||
|
||||
/**
|
||||
* Console reporter.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Console : function(results, options) {
|
||||
console.log('')
|
||||
console.log('Passes: ' + results.stats.passes + ' Failures: ' + results.stats.failures)
|
||||
each(results.allSuites, function(suite) {
|
||||
if (suite.ran) {
|
||||
console.group(suite.description)
|
||||
each(suite.specs, function(spec){
|
||||
var assertionCount = spec.assertions.length + ':'
|
||||
if (spec.requiresImplementation())
|
||||
console.warn(spec.description)
|
||||
else if (spec.passed())
|
||||
console.log(assertionCount + ' ' + spec.description)
|
||||
else
|
||||
console.error(assertionCount + ' ' + spec.description + ', ' + spec.failure().message)
|
||||
})
|
||||
console.groupEnd()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Assertion : function(matcher, actual, expected, negate) {
|
||||
@@ -260,14 +260,14 @@
|
||||
},
|
||||
|
||||
ProxyAssertion : function(object, method, times, negate) {
|
||||
var self = this
|
||||
var old = object[method]
|
||||
var self = this,
|
||||
old = object[method]
|
||||
|
||||
// Proxy
|
||||
|
||||
object[method] = function(){
|
||||
args = toArray(arguments)
|
||||
result = old.apply(object, args)
|
||||
var args = toArray(arguments),
|
||||
result = old.apply(object, args)
|
||||
self.calls.push({ args : args, result : result })
|
||||
return result
|
||||
}
|
||||
@@ -418,17 +418,21 @@
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Suite : function(description, body) {
|
||||
Suite : function(description, body, isShared) {
|
||||
var self = this
|
||||
extend(this, {
|
||||
body: body,
|
||||
description: description,
|
||||
suites: [],
|
||||
sharedBehaviors: [],
|
||||
specs: [],
|
||||
ran: false,
|
||||
hooks: { 'before' : [], 'after' : [], 'before_each' : [], 'after_each' : [] },
|
||||
shared: isShared,
|
||||
hooks: { 'before' : [], 'after' : [],
|
||||
'before_each' : [], 'after_each' : [],
|
||||
'before_nested' : [], 'after_nested' : []},
|
||||
|
||||
// Add a spec to the suite
|
||||
// Add a spec to the suite
|
||||
|
||||
addSpec : function(description, body) {
|
||||
var spec = new JSpec.Spec(description, body)
|
||||
@@ -437,16 +441,30 @@
|
||||
spec.suite = this
|
||||
},
|
||||
|
||||
// Add a hook to the suite
|
||||
// Add a before hook to the suite
|
||||
|
||||
addBefore : function(options, body) {
|
||||
body.options = options || {}
|
||||
this.befores.push(body)
|
||||
},
|
||||
|
||||
// Add an after hook to the suite
|
||||
|
||||
addAfter : function(options, body) {
|
||||
body.options = options || {}
|
||||
this.afters.unshift(body)
|
||||
},
|
||||
|
||||
// Add a hook to the suite
|
||||
|
||||
addHook : function(hook, body) {
|
||||
this.hooks[hook].push(body)
|
||||
},
|
||||
|
||||
// Add a nested suite
|
||||
|
||||
addSuite : function(description, body) {
|
||||
var suite = new JSpec.Suite(description, body)
|
||||
addSuite : function(description, body, isShared) {
|
||||
var suite = new JSpec.Suite(description, body, isShared)
|
||||
JSpec.allSuites.push(suite)
|
||||
suite.name = suite.description
|
||||
suite.description = this.description + ' ' + suite.description
|
||||
@@ -454,15 +472,17 @@
|
||||
suite.suite = this
|
||||
},
|
||||
|
||||
// Invoke a hook in context to this suite
|
||||
// Invoke a hook in context to this suite
|
||||
|
||||
hook : function(hook) {
|
||||
if (this.suite) this.suite.hook(hook)
|
||||
if (hook != 'before' && hook != 'after')
|
||||
if (this.suite) this.suite.hook(hook)
|
||||
|
||||
each(this.hooks[hook], function(body) {
|
||||
JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + self.description + "': ")
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
// Check if nested suites are present
|
||||
|
||||
hasSuites : function() {
|
||||
@@ -481,7 +501,15 @@
|
||||
return !any(this.specs, function(spec){
|
||||
return !spec.passed()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
isShared : function(){
|
||||
return this.shared
|
||||
},
|
||||
|
||||
isExecutable : function() {
|
||||
return !this.isShared() && this.hasSpecs()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -629,7 +657,7 @@
|
||||
},
|
||||
|
||||
describe : function(description, body) {
|
||||
return JSpec.currentSuite.addSuite(description, body)
|
||||
return JSpec.currentSuite.addSuite(description, body, false)
|
||||
},
|
||||
|
||||
it : function(description, body) {
|
||||
@@ -639,19 +667,31 @@
|
||||
before : function(body) {
|
||||
return JSpec.currentSuite.addHook('before', body)
|
||||
},
|
||||
|
||||
|
||||
after : function(body) {
|
||||
return JSpec.currentSuite.addHook('after', body)
|
||||
},
|
||||
|
||||
|
||||
before_each : function(body) {
|
||||
return JSpec.currentSuite.addHook('before_each', body)
|
||||
},
|
||||
|
||||
|
||||
after_each : function(body) {
|
||||
return JSpec.currentSuite.addHook('after_each', body)
|
||||
},
|
||||
|
||||
before_nested : function(body) {
|
||||
return JSpec.currentSuite.addHook('before_nested', body)
|
||||
},
|
||||
|
||||
after_nested : function(body){
|
||||
return JSpec.currentSuite.addhook('after_nested', body)
|
||||
},
|
||||
|
||||
shared_behaviors_for : function(description, body){
|
||||
return JSpec.currentSuite.addSuite(description, body, true)
|
||||
},
|
||||
|
||||
should_behave_like : function(description) {
|
||||
return JSpec.shareBehaviorsOf(description)
|
||||
}
|
||||
@@ -728,8 +768,7 @@
|
||||
|
||||
evalHook : function(module, name, args) {
|
||||
hook('evaluatingHookBody', module, name)
|
||||
try { return module[name].apply(module, args) }
|
||||
catch(e) { error('Error in hook ' + module.name + '.' + name + ': ', e) }
|
||||
return module[name].apply(module, args)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -752,19 +791,69 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Find a suite by its description or name.
|
||||
* Find a shared example suite by its description or name.
|
||||
* First searches parent tree of suites for shared behavior
|
||||
* before falling back to global scoped nested behaviors.
|
||||
*
|
||||
* @param {string} description
|
||||
* @return {Suite}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
findSuite : function(description) {
|
||||
return find(this.allSuites, function(suite){
|
||||
return suite.name == description || suite.description == description
|
||||
})
|
||||
findSharedBehavior : function(description) {
|
||||
var behavior
|
||||
return (behavior = JSpec.findLocalSharedBehavior(description))
|
||||
? behavior
|
||||
: JSpec.findGlobalSharedBehavior(description)
|
||||
},
|
||||
|
||||
/**
|
||||
* Find a shared example suite within the current suite's
|
||||
* parent tree by its description or name.
|
||||
*
|
||||
* @param {string} description
|
||||
* @return {Suite}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
findLocalSharedBehavior : function(description) {
|
||||
var behavior,
|
||||
currentSuite = JSpec.currentSuite.suite
|
||||
while (currentSuite)
|
||||
if (behavior = find(currentSuite.suites, JSpec.suiteDescriptionPredicate(description)))
|
||||
return behavior
|
||||
else
|
||||
currentSuite = currentSuite.suite
|
||||
},
|
||||
|
||||
/**
|
||||
* Find a shared example suite within the global
|
||||
* scope by its description or name.
|
||||
*
|
||||
* @param {string} description
|
||||
* @return {Suite}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
findGlobalSharedBehavior : function(description) {
|
||||
return find(JSpec.suites, JSpec.suiteDescriptionPredicate(description))
|
||||
},
|
||||
|
||||
/**
|
||||
* Build a predicate that will match a suite based on name or description
|
||||
*
|
||||
* @param {string} description
|
||||
* @return {function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
suiteDescriptionPredicate : function(description) {
|
||||
return function(suite){
|
||||
return suite.name === description ||
|
||||
suite.description === description
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Share behaviors (specs) of the given suite with
|
||||
* the current suite.
|
||||
@@ -774,24 +863,13 @@
|
||||
*/
|
||||
|
||||
shareBehaviorsOf : function(description) {
|
||||
if (suite = this.findSuite(description)) this.copySpecs(suite, this.currentSuite)
|
||||
else throw 'failed to share behaviors. ' + puts(description) + ' is not a valid Suite name'
|
||||
var suite = JSpec.findSharedBehavior(description)
|
||||
if (suite)
|
||||
JSpec.evalBody(suite.body)
|
||||
else
|
||||
throw new Error("failed to find shared behaviors named `" + description + "'")
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy specs from one suite to another.
|
||||
*
|
||||
* @param {Suite} fromSuite
|
||||
* @param {Suite} toSuite
|
||||
* @api public
|
||||
*/
|
||||
|
||||
copySpecs : function(fromSuite, toSuite) {
|
||||
each(fromSuite.specs, function(spec){
|
||||
spec.assertions = []
|
||||
toSuite.specs.push(spec)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert arguments to an array.
|
||||
@@ -816,17 +894,21 @@
|
||||
*/
|
||||
|
||||
color : function(string, color) {
|
||||
return "\u001B[" + {
|
||||
bold : 1,
|
||||
black : 30,
|
||||
red : 31,
|
||||
green : 32,
|
||||
yellow : 33,
|
||||
blue : 34,
|
||||
magenta : 35,
|
||||
cyan : 36,
|
||||
white : 37
|
||||
}[color] + 'm' + string + "\u001B[0m"
|
||||
if (option('disableColors')) {
|
||||
return string
|
||||
} else {
|
||||
return "\u001B[" + {
|
||||
bold : 1,
|
||||
black : 30,
|
||||
red : 31,
|
||||
green : 32,
|
||||
yellow : 33,
|
||||
blue : 34,
|
||||
magenta : 35,
|
||||
cyan : 36,
|
||||
white : 37
|
||||
}[color] + 'm' + string + "\u001B[0m"
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -878,6 +960,7 @@
|
||||
*/
|
||||
|
||||
normalizeMatcherBody : function(body) {
|
||||
var captures
|
||||
switch (body.constructor) {
|
||||
case String:
|
||||
if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
|
||||
@@ -992,6 +1075,26 @@
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse an XML String and return a 'document'.
|
||||
*
|
||||
* @param {string} text
|
||||
* @return {document}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
parseXML : function(text) {
|
||||
var xmlDoc
|
||||
if (window.DOMParser)
|
||||
xmlDoc = (new DOMParser()).parseFromString(text, "text/xml")
|
||||
else {
|
||||
xmlDoc = new ActiveXObject("Microsoft.XMLDOM")
|
||||
xmlDoc.async = "false"
|
||||
xmlDoc.loadXML(text)
|
||||
}
|
||||
return xmlDoc
|
||||
},
|
||||
|
||||
/**
|
||||
* Escape HTML.
|
||||
*
|
||||
@@ -1049,7 +1152,7 @@
|
||||
function assert(matcher, args, negate) {
|
||||
var expected = toArray(args, 1)
|
||||
matcher.negate = negate
|
||||
assertion = new JSpec.Assertion(matcher, actual, expected, negate)
|
||||
var assertion = new JSpec.Assertion(matcher, actual, expected, negate)
|
||||
hook('beforeAssertion', assertion)
|
||||
if (matcher.defer) assertion.run()
|
||||
else JSpec.currentSpec.assertions.push(assertion.run().report()), hook('afterAssertion', assertion)
|
||||
@@ -1167,6 +1270,7 @@
|
||||
*/
|
||||
|
||||
destub : function(object, method) {
|
||||
var captures
|
||||
if (method) {
|
||||
if (object['__prototype__' + method])
|
||||
delete object[method]
|
||||
@@ -1301,12 +1405,27 @@
|
||||
*/
|
||||
|
||||
describe : function(description, body) {
|
||||
var suite = new JSpec.Suite(description, body)
|
||||
var suite = new JSpec.Suite(description, body, false)
|
||||
hook('addingSuite', suite)
|
||||
this.allSuites.push(suite)
|
||||
this.suites.push(suite)
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a shared example suite to JSpec.
|
||||
*
|
||||
* @param {string} description
|
||||
* @param {body} function
|
||||
* @api public
|
||||
*/
|
||||
|
||||
shared_behaviors_for : function(description, body) {
|
||||
var suite = new JSpec.Suite(description, body, true)
|
||||
hook('addingSuite', suite)
|
||||
this.allSuites.push(suite)
|
||||
this.suites.push(suite)
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the contents of a function body.
|
||||
*
|
||||
@@ -1333,9 +1452,8 @@
|
||||
var matchers = this.matchers
|
||||
var context = this.context || this.defaultContext
|
||||
var contents = this.contentsOf(body)
|
||||
hook('evaluatingBody', dsl, matchers, context, contents)
|
||||
try { with (dsl){ with (context) { with (matchers) { eval(contents) }}} }
|
||||
catch(e) { error(errorMessage, e) }
|
||||
hook('evaluatingBody', dsl, matchers, context, contents)
|
||||
with (dsl){ with (context) { with (matchers) { eval(contents) }}}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1355,8 +1473,9 @@
|
||||
split('__END__')[0].
|
||||
replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)').
|
||||
replace(/describe\s+(.*?)$/gm, 'describe($1, function(){').
|
||||
replace(/shared_behaviors_for\s+(.*?)$/gm, 'shared_behaviors_for($1, function(){').
|
||||
replace(/^\s+it\s+(.*?)$/gm, ' it($1, function(){').
|
||||
replace(/^ *(before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
|
||||
replace(/^ *(before_nested|after_nested|before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
|
||||
replace(/^\s*end(?=\s|$)/gm, '});').
|
||||
replace(/-\{/g, 'function(){').
|
||||
replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
|
||||
@@ -1419,25 +1538,28 @@
|
||||
*/
|
||||
|
||||
runSuite : function(suite) {
|
||||
this.currentSuite = suite
|
||||
this.evalBody(suite.body)
|
||||
suite.ran = true
|
||||
hook('beforeSuite', suite), suite.hook('before')
|
||||
each(suite.specs, function(spec) {
|
||||
hook('beforeSpec', spec)
|
||||
suite.hook('before_each')
|
||||
JSpec.runSpec(spec)
|
||||
hook('afterSpec', spec)
|
||||
suite.hook('after_each')
|
||||
})
|
||||
if (suite.hasSuites()) {
|
||||
each(suite.suites, function(suite) {
|
||||
JSpec.runSuite(suite)
|
||||
})
|
||||
}
|
||||
hook('afterSuite', suite), suite.hook('after')
|
||||
this.stats.suitesFinished++
|
||||
},
|
||||
if (!suite.isShared())
|
||||
{
|
||||
this.currentSuite = suite
|
||||
this.evalBody(suite.body)
|
||||
suite.ran = true
|
||||
hook('beforeSuite', suite), suite.hook('before'), suite.hook('before_nested')
|
||||
each(suite.specs, function(spec) {
|
||||
hook('beforeSpec', spec)
|
||||
suite.hook('before_each')
|
||||
JSpec.runSpec(spec)
|
||||
hook('afterSpec', spec)
|
||||
suite.hook('after_each')
|
||||
})
|
||||
if (suite.hasSuites()) {
|
||||
each(suite.suites, function(suite) {
|
||||
JSpec.runSuite(suite)
|
||||
})
|
||||
}
|
||||
hook('afterSuite', suite), suite.hook('after_nested'), suite.hook('after')
|
||||
this.stats.suitesFinished++
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Report a failure for the current spec.
|
||||
@@ -1512,19 +1634,6 @@
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Throw a JSpec related error.
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {Exception} e
|
||||
* @api public
|
||||
*/
|
||||
|
||||
error : function(message, e) {
|
||||
throw (message ? message : '') + e.toString() +
|
||||
(e.line ? ' near line ' + e.line : '')
|
||||
},
|
||||
|
||||
/**
|
||||
* Ad-hoc POST request for JSpec server usage.
|
||||
*
|
||||
@@ -1620,7 +1729,7 @@
|
||||
return request.responseText
|
||||
}
|
||||
else
|
||||
error("failed to load `" + file + "'")
|
||||
throw new Error("failed to load `" + file + "'")
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1637,12 +1746,19 @@
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Node.js support
|
||||
|
||||
if (typeof GLOBAL === 'object' && typeof exports === 'object')
|
||||
quit = process.exit,
|
||||
print = require('sys').puts,
|
||||
readFile = require('fs').readFileSync
|
||||
|
||||
// --- Utility functions
|
||||
|
||||
var main = this
|
||||
var find = JSpec.any
|
||||
var utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \
|
||||
var main = this,
|
||||
find = JSpec.any,
|
||||
utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \
|
||||
error escape extend puts query strip color does addMatchers callIterator toArray equal'.split(/\s+/)
|
||||
while (utils.length) eval('var ' + utils[0] + ' = JSpec.' + utils.shift())
|
||||
if (!main.setTimeout) main.setTimeout = function(callback){ callback() }
|
||||
@@ -1672,7 +1788,7 @@
|
||||
have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)",
|
||||
|
||||
receive : { defer : true, match : function(actual, method, times) {
|
||||
proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
|
||||
var proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
|
||||
JSpec.currentSpec.assertions.push(proxy)
|
||||
return proxy
|
||||
}},
|
||||
@@ -1685,8 +1801,8 @@
|
||||
},
|
||||
|
||||
include : function(actual) {
|
||||
for (state = true, i = 1; i < arguments.length; i++) {
|
||||
arg = arguments[i]
|
||||
for (var state = true, i = 1; i < arguments.length; i++) {
|
||||
var arg = arguments[i]
|
||||
switch (actual.constructor) {
|
||||
case String:
|
||||
case Number:
|
||||
@@ -1733,41 +1849,41 @@
|
||||
case Function : return expected[i].name || 'Error'
|
||||
}
|
||||
}
|
||||
exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '')
|
||||
var exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '')
|
||||
return 'expected ' + exception + (negate ? ' not ' : '' ) +
|
||||
' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was')
|
||||
}},
|
||||
|
||||
have : function(actual, length, property) {
|
||||
return actual[property].length == length
|
||||
return actual[property] == null ? false : actual[property].length == length
|
||||
},
|
||||
|
||||
have_at_least : function(actual, length, property) {
|
||||
return actual[property].length >= length
|
||||
return actual[property] == null ? (length === 0) : actual[property].length >= length
|
||||
},
|
||||
|
||||
have_at_most :function(actual, length, property) {
|
||||
return actual[property].length <= length
|
||||
return actual[property] == null || actual[property].length <= length
|
||||
},
|
||||
|
||||
have_within : function(actual, range, property) {
|
||||
length = actual[property].length
|
||||
var length = actual[property] == undefined ? 0 : actual[property].length
|
||||
return length >= range.shift() && length <= range.pop()
|
||||
},
|
||||
|
||||
have_prop : function(actual, property, value) {
|
||||
return actual[property] == null ||
|
||||
return actual[property] === undefined ||
|
||||
actual[property] instanceof Function ? false:
|
||||
value == null ? true:
|
||||
value === undefined ? true:
|
||||
does(actual[property], 'eql', value)
|
||||
},
|
||||
|
||||
have_property : function(actual, property, value) {
|
||||
return actual[property] == null ||
|
||||
return actual[property] === undefined ||
|
||||
actual[property] instanceof Function ? false:
|
||||
value == null ? true:
|
||||
value === undefined ? true:
|
||||
value === actual[property]
|
||||
}
|
||||
})
|
||||
|
||||
})()
|
||||
})()
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
// JSpec - node - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
JSpec
|
||||
.include({
|
||||
name: 'node',
|
||||
|
||||
// --- Matchers
|
||||
|
||||
matchers : {
|
||||
have_enumerable_property: 'actual.propertyIsEnumerable(expected)',
|
||||
have_writable_property: 'Object.getOwnPropertyDescriptor(actual, expected).writable === true',
|
||||
have_configurable_property: 'Object.getOwnPropertyDescriptor(actual, expected).configurable === true',
|
||||
have_keys: 'does(Object.keys(actual), "eql", expected)',
|
||||
have_prototype: 'Object.getPrototypeOf(actual) === expected'
|
||||
}
|
||||
})
|
||||
|
||||
+35
-20
@@ -2,26 +2,29 @@
|
||||
// JSpec - XHR - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
(function(){
|
||||
|
||||
|
||||
var lastRequest
|
||||
|
||||
// --- Original XMLHttpRequest
|
||||
|
||||
var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
|
||||
|
||||
var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
|
||||
XMLHttpRequest :
|
||||
function(){}
|
||||
var OriginalActiveXObject = 'ActiveXObject' in this ?
|
||||
ActiveXObject :
|
||||
undefined
|
||||
|
||||
|
||||
// --- MockXMLHttpRequest
|
||||
|
||||
var MockXMLHttpRequest = function() {
|
||||
this.requestHeaders = {}
|
||||
}
|
||||
|
||||
|
||||
MockXMLHttpRequest.prototype = {
|
||||
status: 0,
|
||||
async: true,
|
||||
readyState: 0,
|
||||
responseXML: null,
|
||||
responseText: '',
|
||||
abort: function(){},
|
||||
onreadystatechange: function(){},
|
||||
@@ -33,7 +36,7 @@
|
||||
getAllResponseHeaders : function(){
|
||||
return this.responseHeaders
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Return case-insensitive value for header _name_.
|
||||
*/
|
||||
@@ -41,7 +44,7 @@
|
||||
getResponseHeader : function(name) {
|
||||
return this.responseHeaders[name.toLowerCase()]
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Set case-insensitive _value_ for header _name_.
|
||||
*/
|
||||
@@ -49,7 +52,7 @@
|
||||
setRequestHeader : function(name, value) {
|
||||
this.requestHeaders[name.toLowerCase()] = value
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Open mock request.
|
||||
*/
|
||||
@@ -63,7 +66,7 @@
|
||||
if (async != undefined) this.async = async
|
||||
if (this.async) this.onreadystatechange()
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Send request _data_.
|
||||
*/
|
||||
@@ -75,14 +78,26 @@
|
||||
if (this.method == 'HEAD') this.responseText = null
|
||||
this.responseHeaders['content-length'] = (this.responseText || '').length
|
||||
if(this.async) this.onreadystatechange()
|
||||
this.populateResponseXML()
|
||||
lastRequest = function(){
|
||||
return self
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse request body and populate responseXML if response-type is xml
|
||||
* Based on the standard specification : http://www.w3.org/TR/XMLHttpRequest/
|
||||
*/
|
||||
populateResponseXML: function() {
|
||||
var type = this.getResponseHeader("content-type")
|
||||
if (!type || !this.responseText || !type.match(/(text\/xml|application\/xml|\+xml$)/g))
|
||||
return
|
||||
this.responseXML = JSpec.parseXML(this.responseText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Response status codes
|
||||
|
||||
|
||||
JSpec.statusCodes = {
|
||||
100: 'Continue',
|
||||
101: 'Switching Protocols',
|
||||
@@ -126,7 +141,7 @@
|
||||
504: 'Gateway Timeout',
|
||||
505: 'HTTP Version Not Supported'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mock XMLHttpRequest requests.
|
||||
*
|
||||
@@ -135,7 +150,7 @@
|
||||
* @return {hash}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function mockRequest() {
|
||||
return { and_return : function(body, type, status, headers) {
|
||||
XMLHttpRequest = MockXMLHttpRequest
|
||||
@@ -151,18 +166,18 @@
|
||||
})
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unmock XMLHttpRequest requests.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function unmockRequest() {
|
||||
XMLHttpRequest = OriginalXMLHttpRequest
|
||||
ActiveXObject = OriginalActiveXObject
|
||||
}
|
||||
|
||||
|
||||
JSpec.include({
|
||||
name: 'Mock XHR',
|
||||
|
||||
@@ -178,9 +193,9 @@
|
||||
afterSpec : function() {
|
||||
unmockRequest()
|
||||
},
|
||||
|
||||
|
||||
// --- DSLs
|
||||
|
||||
|
||||
DSLs : {
|
||||
snake : {
|
||||
mock_request: mockRequest,
|
||||
@@ -190,4 +205,4 @@
|
||||
}
|
||||
|
||||
})
|
||||
})()
|
||||
})()
|
||||
|
||||
+5
-26
@@ -1,21 +1,12 @@
|
||||
|
||||
require.paths.unshift('spec', 'lib', 'spec/lib', 'spec/support/libxmljs')
|
||||
require.paths.unshift('spec', 'lib', 'spec/lib')
|
||||
require("jspec")
|
||||
require("express")
|
||||
require("express/spec")
|
||||
|
||||
print = require('sys').puts
|
||||
quit = process.exit
|
||||
print = puts
|
||||
|
||||
readFile = function(path) {
|
||||
var result
|
||||
require('posix')
|
||||
.cat(path, "utf8")
|
||||
.addCallback(function(contents){ result = contents })
|
||||
.addErrback(function(){ throw new Error("failed to read file `" + path + "'") })
|
||||
.wait()
|
||||
return result
|
||||
}
|
||||
readFile = require('fs').readFileSync
|
||||
|
||||
function run(specs) {
|
||||
specs.forEach(function(spec){
|
||||
@@ -30,12 +21,9 @@ specs = {
|
||||
'utils',
|
||||
'request',
|
||||
'mime',
|
||||
'static',
|
||||
'collection',
|
||||
'plugins',
|
||||
'plugins.cache',
|
||||
'plugins.view',
|
||||
'plugins.common-logger',
|
||||
'plugins.content-length',
|
||||
'plugins.method-override',
|
||||
'plugins.body-decoder',
|
||||
@@ -44,22 +32,13 @@ specs = {
|
||||
'plugins.cookie',
|
||||
'plugins.session',
|
||||
'plugins.flash',
|
||||
],
|
||||
dependant: [
|
||||
'element-collection'
|
||||
]
|
||||
'plugins.static',
|
||||
]
|
||||
}
|
||||
|
||||
switch (process.ARGV[2]) {
|
||||
case 'independant':
|
||||
run(specs.independant)
|
||||
break
|
||||
case 'dependant':
|
||||
run(specs.dependant)
|
||||
break
|
||||
case 'all':
|
||||
run(specs.independant)
|
||||
run(specs.dependant)
|
||||
break
|
||||
default:
|
||||
run([process.ARGV[2]])
|
||||
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário