Comparar commits
508 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 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 | |||
| 2f6dfbc165 | |||
| 49cb53d735 | |||
| 54f1a51a10 | |||
| c6a2674c2b | |||
| 282a10ec83 | |||
| 50276a06df | |||
| f452250f88 | |||
| 9d44e237a5 | |||
| 9f0e5899c2 | |||
| e351a02a06 | |||
| cd167ec777 | |||
| 3290412477 | |||
| acf0128fb4 | |||
| e91ee22a89 | |||
| 45ef08cf99 | |||
| baa7d12ed6 | |||
| 822de581b3 | |||
| 3863a76fc8 | |||
| 9727fac291 | |||
| 5cadbcbbd7 | |||
| 8ee0294672 | |||
| 99789c3182 | |||
| 406a7f4fc7 | |||
| 5a11f82e0e | |||
| 33eca37ec9 | |||
| fbd9cdd11e | |||
| 6ec6657512 | |||
| 8e91d2039a | |||
| 1879648be7 | |||
| 490770171d | |||
| 621063cc18 | |||
| 821defc11b | |||
| dbc1709e0e | |||
| 4d1bda0601 | |||
| 1a9a3674c2 | |||
| 99b7e74422 | |||
| add0a43c40 | |||
| 3dc7c6a254 | |||
| e645123fbd | |||
| 4b104db212 | |||
| 7cdbca0dc9 | |||
| 7d5f06b048 | |||
| e823e31550 | |||
| 34de3ede95 | |||
| 35c7317004 | |||
| 2a89f375f4 | |||
| 8d06b6752a | |||
| dba453345a | |||
| 1f76d5c7d6 | |||
| 8309537527 | |||
| d0f14f8488 | |||
| cee857af9a | |||
| c8dd169ad9 | |||
| 5495fbcd0c | |||
| eb94667ec8 | |||
| cf31355515 | |||
| 604c359a1c | |||
| dad64b8a3b | |||
| ffa432baa2 | |||
| 5c4a356348 | |||
| ef949aec20 | |||
| eaea3f188d | |||
| 818135789b | |||
| 2bbe573748 | |||
| 9efb15b267 | |||
| 5ce1306b75 | |||
| 75c85663ad | |||
| 8c1bcc4c47 | |||
| 200d09c7bd | |||
| ef86474830 | |||
| e2fee9b353 | |||
| 542cab1123 | |||
| 315c05034f | |||
| 148d34629b | |||
| 82891ea148 | |||
| cd1aa92d07 | |||
| 2a4f3525d9 | |||
| 944f99abe0 | |||
| e083321a9d | |||
| a0a1a543e8 | |||
| cf74bc655d | |||
| 0587278cda | |||
| b333dccc2e | |||
| a71201bd55 | |||
| cec677062b | |||
| 327d5b0f88 | |||
| 20cf8f81a2 | |||
| 4d5a1b5f4d | |||
| 9d5a6f9412 | |||
| 7e92012415 | |||
| 6ee83ba8d0 | |||
| 70a99ade67 | |||
| 634503b8ba | |||
| 2a4cbb1174 | |||
| ca7d0f3e34 | |||
| 9cb2645d1c | |||
| acf0a652b7 | |||
| 2701dfd80b | |||
| 047499dcd1 | |||
| fa37e5a35f | |||
| 4adbb4971f | |||
| 8ed99f6bb8 | |||
| fbf547d127 | |||
| f4b4c03cb5 | |||
| 6b9a079bbf | |||
| 7238afbf4b | |||
| 0186659469 | |||
| 50d14230f5 | |||
| 29fe71c8f7 | |||
| 5fe033cef1 | |||
| 6490f0c193 | |||
| 09ccd043f2 | |||
| 825e8b6f0c | |||
| 1726dfb3b3 | |||
| 8a7ac0a6af | |||
| d66ce086d4 | |||
| 2bbf6cb8f9 | |||
| 4a52e641cf | |||
| b4c90def81 | |||
| 50c0277e7f | |||
| 5a5f23fc88 | |||
| ae8f9e1137 | |||
| 2b92e4053b | |||
| 8717e2e970 | |||
| d0a9b0cb81 | |||
| 844eb5f7e9 | |||
| 41913fd8b9 | |||
| 2a5ccd8826 | |||
| 497402311f | |||
| d52090619b | |||
| 9d0d6e412b | |||
| 9b5605a944 | |||
| 51f4c965b5 | |||
| 046bee8844 | |||
| 971739089a | |||
| f962f34e53 | |||
| 34a88d029d | |||
| d3b2e6057d | |||
| 12a58361b4 | |||
| 0085b0a33b | |||
| bf9505a4ce | |||
| 4c94457380 | |||
| af313e5e8c | |||
| eb7b96efe1 | |||
| 5fe35bf97e | |||
| 12bc374ea9 | |||
| 68c57e08ae | |||
| f73c3fc404 | |||
| 4d926d3840 | |||
| 817ff41b0d | |||
| f1614a5946 | |||
| 17c1207d76 | |||
| a92667fdad | |||
| f03694e21e | |||
| 83fcf8b594 | |||
| 7afb895f1b | |||
| 98e566ad69 | |||
| 7d0470d285 | |||
| f8879ac5d1 | |||
| 9f263e357b | |||
| ae33e7b673 | |||
| b766234b93 | |||
| a2899a0acd | |||
| 43da20a6b3 | |||
| 25cf42250a | |||
| 6b6ec3f19f | |||
| 1a2e4342e2 | |||
| 1ce1abf216 | |||
| 704c933927 | |||
| 8207e7088f | |||
| 46fdf10c97 | |||
| 61b82480b0 | |||
| 4ce5c7650e | |||
| 965cbf63be | |||
| d4d76cda58 | |||
| f2d08aa2e3 | |||
| a82b1b9270 | |||
| c1617508c6 | |||
| e125ae2c48 | |||
| 3f9cbbbffb | |||
| 6f7141a266 | |||
| 99e3130f3c | |||
| 774f96fcb9 | |||
| 17ab8e81aa | |||
| b02b384f14 | |||
| 3ec970bb67 | |||
| 22e338fbd4 |
@@ -1 +1,3 @@
|
||||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
+13
-7
@@ -1,9 +1,15 @@
|
||||
[submodule "spec/support/libxmljs"]
|
||||
path = spec/support/libxmljs
|
||||
url = git://github.com/sprsquish/libxmljs.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/creationix/haml-js.git
|
||||
[submodule "lib/support/js-oo"]
|
||||
path = lib/support/js-oo
|
||||
url = git://github.com/visionmedia/js-oo.git
|
||||
url = git://github.com/visionmedia/haml.js.git
|
||||
[submodule "lib/support/multipart"]
|
||||
path = lib/support/multipart
|
||||
url = git://github.com/isaacs/multipart-js.git
|
||||
|
||||
+231
@@ -1,4 +1,235 @@
|
||||
|
||||
0.9.0 / 2010-04-14
|
||||
==================
|
||||
|
||||
* Added [haml.js](http://github.com/visionmedia/haml.js) submodule; removed haml-js
|
||||
* 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
|
||||
* Updated support to node --version 0.1.90
|
||||
* Updated dependencies
|
||||
|
||||
* Added "magic" variables to collection partials (__index__, __length__, __isFirst__, __isLast__). Closes #254
|
||||
* Request instance created before body parsing. Closes #262
|
||||
* Fixed post param issue
|
||||
* Fixed mocks to work with new routing api
|
||||
* .
|
||||
* Docs
|
||||
* Merge branch 'upload-limit'
|
||||
* Added "max upload size" setting
|
||||
* Updated ext. Closes #256
|
||||
* Added Request#render() callback function. Closes #258
|
||||
* Merge branch 'integration'
|
||||
* fn -> callback
|
||||
* Typo
|
||||
* Merge branch 'master' of git://github.com/aheckmann/express into integration
|
||||
* Updated to JSpec 4.0.0
|
||||
* error() is passed the exception
|
||||
* fn -> callback
|
||||
* Added DSL level error() route support
|
||||
* Added DSL level notFound() route support
|
||||
* Added specs for Request#notFound()
|
||||
* More Request#error() specs
|
||||
* Added specs for Request#error()
|
||||
* Merge branch 'errors'
|
||||
* Added publish Request#notFound()
|
||||
* Removed Express.error(), Added public Request#error()
|
||||
* Request#halt() accepts callback function as 3rd/4th arg
|
||||
* Merge branch 'error-handling'
|
||||
* Misc error handling improvements
|
||||
* Removed unused variable
|
||||
* Merge branch 'error-handling'
|
||||
* Caching notFound / showException modules
|
||||
* Express.error() now acts as the core exception handler
|
||||
* request response event is now fired in reverse.
|
||||
* Handle when a plugin response fails
|
||||
* next -> callback
|
||||
* Styling
|
||||
* Removed set("session cookie") in favour of use(Session, { cookie: { ... }})
|
||||
* Docs for stable / edge
|
||||
* Merge branch 'route-wildcards'
|
||||
* Added preprocessing of route param wildcards using param(). Closes #251
|
||||
* Added specs for param()
|
||||
* Added more route wildcard specs
|
||||
* Merge branch 'pre-cache-views'
|
||||
* Pre-caching views in memory. Closes #253
|
||||
* Started pre caching of views
|
||||
* Merge branch 'integration'
|
||||
* Added assertion to ensure that partials dir is relative to set("views")
|
||||
* Re-using variables
|
||||
* Using set("views") when setting set("partials")
|
||||
* partialscache -> partials
|
||||
* preload partials add settings bug fix, partial now works when cache view contents is true
|
||||
* add cache view partials
|
||||
* Removed utils.mixin() use Object#mergeDeep()
|
||||
* Merge branch 'partials'
|
||||
* Chat sample app using partials as an example
|
||||
* Added partial "as" option
|
||||
* Partial collection should not introduce newlines
|
||||
* Added partial collection support
|
||||
* Started view partial support
|
||||
* Updated ext submodule
|
||||
* Removed Request#_blendInNodeRequest()
|
||||
* Merge branch 'net2_fixes' of git://github.com/ciaranj/express
|
||||
* Updated support to v0.1.33
|
||||
* Various minor fixes required to make express work post the net2 merge branch
|
||||
|
||||
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
|
||||
==================
|
||||
|
||||
* Added flash() example to sample upload app
|
||||
* Added high level restful http client module (express/http)
|
||||
* Changed; RESTful route functions double as HTTP clients. Closes #69
|
||||
* Changed; throwing error when routes are added at runtime
|
||||
* Changed; defaulting render() context to the current Request. Closes #197
|
||||
* Updated haml submodule
|
||||
|
||||
0.3.0 / 2010-02-11
|
||||
==================
|
||||
|
||||
* Updated haml / sass submodules. Closes #200
|
||||
* Added flash message support. Closes #64
|
||||
* Added accepts() now allows multiple args. fixes #117
|
||||
* Added support for plugins to halt. Closes #189
|
||||
* Added alternate layout support. Closes #119
|
||||
* Removed Route#run(). Closes #188
|
||||
* Fixed broken specs due to use(Cookie) missing
|
||||
|
||||
0.2.1 / 2010-02-05
|
||||
==================
|
||||
|
||||
* Added "plot" format option for Profiler (for gnuplot processing)
|
||||
* Added request number to Profiler plugin
|
||||
* Fixed binary encoding for multi-part file uploads, was previously defaulting to UTF8
|
||||
* Fixed issue with routes not firing when not files are present. Closes #184
|
||||
* Fixed process.Promise -> events.Promise
|
||||
|
||||
0.2.0 / 2010-02-03
|
||||
==================
|
||||
|
||||
* Added parseParam() support for name[] etc. (allows for file inputs with "multiple" attr) Closes #180
|
||||
* Added Both Cache and Session option "reapInterval" may be "reapEvery". Closes #174
|
||||
* Added expiration support to cache api with reaper. Closes #133
|
||||
* Added cache Store.Memory#reap()
|
||||
* Added Cache; cache api now uses first class Cache instances
|
||||
* Added abstract session Store. Closes #172
|
||||
* Changed; cache Memory.Store#get() utilizing Collection
|
||||
* Renamed MemoryStore -> Store.Memory
|
||||
* Fixed use() of the same plugin several time will always use latest options. Closes #176
|
||||
|
||||
0.1.0 / 2010-02-03
|
||||
==================
|
||||
|
||||
* Changed; Hooks (before / after) pass request as arg as well as evaluated in their context
|
||||
* Updated node support to 0.1.27 Closes #169
|
||||
* Updated dirname(__filename) -> __dirname
|
||||
* Updated libxmljs support to v0.2.0
|
||||
* Added session support with memory store / reaping
|
||||
* Added quick uid() helper
|
||||
* Added multi-part upload support
|
||||
* Added Sass.js support / submodule
|
||||
* Added production env caching view contents and static files
|
||||
* Added static file caching. Closes #136
|
||||
* Added cache plugin with memory stores
|
||||
* Added support to StaticFile so that it works with non-textual files.
|
||||
* Removed dirname() helper
|
||||
* Removed several globals (now their modules must be required)
|
||||
|
||||
0.0.2 / 2010-01-10
|
||||
==================
|
||||
|
||||
|
||||
+16
-15
@@ -1,26 +1,27 @@
|
||||
|
||||
NODE = node
|
||||
COFFEE = coffee
|
||||
|
||||
init:
|
||||
@git submodule init && git submodule update
|
||||
all: test
|
||||
|
||||
install: bin/express
|
||||
install bin/express /usr/local/bin/express
|
||||
|
||||
test: init spec/support/libxmljs/libxmljs.node
|
||||
test:
|
||||
@$(NODE) spec/node.js all
|
||||
|
||||
test-independant: init
|
||||
@$(NODE) spec/node.js independant
|
||||
app: app-chat
|
||||
|
||||
test-dependant: init spec/support/libxmljs/libxmljs.node
|
||||
@$(NODE) spec/node.js dependant
|
||||
app-chat:
|
||||
@$(NODE) examples/chat/app.js
|
||||
|
||||
app:
|
||||
@$(NODE) examples/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
|
||||
compile-coffee:
|
||||
@$(COFFEE) examples/coffee-upload/app.coffee
|
||||
|
||||
.PHONY: init test benchmark app
|
||||
.PHONY: install test app
|
||||
+42
-56
@@ -11,91 +11,74 @@
|
||||
|
||||
* Sexy DSL with robust sinatra-like routing
|
||||
* High performance
|
||||
* Session support
|
||||
* Cache API
|
||||
* RESTful HTTP client
|
||||
* Mime helpers
|
||||
* 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
|
||||
* View support (ejs, haml, mustache)
|
||||
* 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)
|
||||
* 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:
|
||||
Install the [Kiwi package manager for nodejs](http://github.com/visionmedia/kiwi)
|
||||
and run:
|
||||
|
||||
$ kiwi -v install express
|
||||
|
||||
or
|
||||
|
||||
$ 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
|
||||
Install via git clone:
|
||||
|
||||
$ git clone git://github.com/visionmedia/express.git && cd express && git submodule update --init
|
||||
|
||||
## Performance
|
||||
|
||||
Extensive benchmarking will wait until a development version
|
||||
has been released.
|
||||
Extensive performance enhancements have not yet been made,
|
||||
since we are focusing on the framework itself at the moment.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
However if you are interested view the premature [benchmarks for Express framework](http://vision-media.ca/resources/nodejs/express-nodejs-web-development-framework-performance).
|
||||
|
||||
## Examples
|
||||
|
||||
require.paths.unshift('lib')
|
||||
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)
|
||||
use(Redirect)
|
||||
set('root', dirname(__filename))
|
||||
enable('cache views')
|
||||
get('/user', function(){
|
||||
this.redirect('/user/' + this.currentUser.id)
|
||||
})
|
||||
|
||||
get('/hello', function(){
|
||||
this.contentType('html')
|
||||
return '<h1>World<h1>'
|
||||
})
|
||||
|
||||
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,18 +86,21 @@ Run individual suites:
|
||||
$ node spec/node.js routing
|
||||
...
|
||||
|
||||
Express is currently being developed with node --version:
|
||||
v0.1.24-13-ge2abc5f
|
||||
The latest release of Express is compatible with node --version:
|
||||
v0.1.90
|
||||
|
||||
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
|
||||
|
||||
## Contributors
|
||||
|
||||
* TJ Holowaychuk (visionmedia) <tj@vision-media.ca>
|
||||
* Aaron Heckmann (aheckmann) <aaron.heckmann+github@gmail.com>
|
||||
* Ciaran Jessup (ciaranj) <ciaranj@gmail.com>
|
||||
* Gareth Jones (csausdev) <gareth.jones@sensis.com.au>
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -1,35 +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('support/ejs/ejs'),
|
||||
haml: require('support/haml/lib/haml')
|
||||
}
|
||||
|
||||
options = { locals: { article: { title: 'Foo', body: 'bar' }}}
|
||||
|
||||
ejs = ' \n\
|
||||
<h1><%= article.title %></h1> \n\
|
||||
<p><%= article.body %></p> \n\
|
||||
'
|
||||
|
||||
haml = ' \n\
|
||||
%h1= article.title\n\
|
||||
%p= article.body \n\
|
||||
'
|
||||
|
||||
suite('Template Engines', 1000, function(){
|
||||
benchmark('ejs', function(){
|
||||
engine.ejs.render(ejs, options)
|
||||
})
|
||||
|
||||
benchmark('haml', function(){
|
||||
engine.haml.render(haml, options)
|
||||
})
|
||||
})
|
||||
Arquivo executável
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var arg,
|
||||
dir = '.',
|
||||
fs = require('fs'),
|
||||
sys = require('sys'),
|
||||
args = process.argv.slice(2)
|
||||
|
||||
// Parse arguments
|
||||
|
||||
while (arg = args.shift())
|
||||
switch (arg) {
|
||||
case '-h':
|
||||
case '--help':
|
||||
sys.print('Usage: express [options] [dir]\n')
|
||||
break
|
||||
default:
|
||||
dir = arg
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
function confirm(msg, fn) {
|
||||
sys.print(msg)
|
||||
var stdin = process.openStdin()
|
||||
stdin.setEncoding('utf8')
|
||||
stdin.addListener('data', function(chunk){
|
||||
switch (chunk.trim().toLowerCase()) {
|
||||
case 'yes':
|
||||
case 'y':
|
||||
return stdin.destroy(), fn(true)
|
||||
case 'no':
|
||||
case 'n':
|
||||
return stdin.destroy(), fn(false)
|
||||
default:
|
||||
sys.print(msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createTemplateIn(dir, mode) {
|
||||
fs.writeFile(dir + '/app.js', '// your app here')
|
||||
fs.mkdir(dir + '/views', mode)
|
||||
fs.mkdir(dir + '/public', mode, function(err){
|
||||
if (err) throw err
|
||||
fs.mkdir(dir + '/public/javascripts', mode)
|
||||
fs.mkdir(dir + '/public/stylesheets', mode)
|
||||
fs.mkdir(dir + '/public/images', mode)
|
||||
})
|
||||
}
|
||||
|
||||
// Setup template
|
||||
|
||||
fs.readdir(dir, function(err, files){
|
||||
if (err) throw err
|
||||
if (files.length)
|
||||
confirm(dir + ' is not empty, continue? ', function(ok){
|
||||
if (ok) createTemplateIn(dir, 0666)
|
||||
})
|
||||
else
|
||||
createTemplateIn(dir, 0666)
|
||||
})
|
||||
@@ -1,62 +0,0 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(CommonLogger)
|
||||
set('root', dirname(__filename))
|
||||
enable('cache views')
|
||||
})
|
||||
|
||||
var messages = [],
|
||||
StaticFile = require('express/static').File
|
||||
|
||||
get('/', function(){
|
||||
this.redirect('/chat')
|
||||
})
|
||||
|
||||
get('/chat', function(){
|
||||
this.render('chat.haml.html', {
|
||||
locals: {
|
||||
messages: messages
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
post('/chat', function(){
|
||||
messages.push(escape(this.param('message')).replace(/:\)/g, '<img src="http://icons3.iconfinder.netdna-cdn.com/data/icons/ledicons/emoticon_smile.png">'))
|
||||
this.halt(200)
|
||||
})
|
||||
|
||||
get('/chat/messages', function(){
|
||||
var self = this,
|
||||
previousLength = messages.length,
|
||||
timer = setInterval(function(){
|
||||
if (messages.length > previousLength)
|
||||
self.contentType('json'),
|
||||
previousLength = messages.length,
|
||||
self.halt(200, JSON.encode(messages)),
|
||||
clearInterval(timer)
|
||||
}, 100)
|
||||
})
|
||||
|
||||
get('/public/*', function(file){
|
||||
this.sendfile(dirname(__filename) + '/public/' + file)
|
||||
})
|
||||
|
||||
get('/error/view', function(){
|
||||
this.render('does.not.exist')
|
||||
})
|
||||
|
||||
get('/error', function(){
|
||||
throw new Error('oh noes!')
|
||||
})
|
||||
|
||||
get('/simple', function(){
|
||||
return 'Hello :)'
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
var messages = [],
|
||||
utils = require('express/utils'),
|
||||
http = require('express/http')
|
||||
|
||||
configure(function(){
|
||||
use(Logger)
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(Cookie)
|
||||
use(Cache, { lifetime: (5).minutes, reapInterval: (1).minute })
|
||||
use(Session, { lifetime: (15).minutes, reapInterval: (1).minute })
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.pass('/chat')
|
||||
})
|
||||
|
||||
get('/chat', function(){
|
||||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
post('/chat', function(){
|
||||
this.session.name = this.param('name')
|
||||
messages
|
||||
.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)
|
||||
})
|
||||
|
||||
get('/chat/messages', function(){
|
||||
var self = this,
|
||||
previousLength = messages.length,
|
||||
timer = setInterval(function(){
|
||||
if (messages.length > previousLength)
|
||||
self.contentType('json'),
|
||||
previousLength = messages.length,
|
||||
self.halt(200, JSON.encode(messages)),
|
||||
clearInterval(timer)
|
||||
}, 100)
|
||||
})
|
||||
|
||||
get('/*.css', function(file){
|
||||
this.render(file + '.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
get('/error/view', function(){
|
||||
this.render('does.not.exist')
|
||||
})
|
||||
|
||||
get('/error', function(){
|
||||
throw new Error('oh noes!')
|
||||
})
|
||||
|
||||
get('/simple', function(){
|
||||
return 'Hello :)'
|
||||
})
|
||||
|
||||
get('/favicon.ico', function(){
|
||||
this.halt()
|
||||
})
|
||||
|
||||
run()
|
||||
|
Antes Largura: | Altura: | Tamanho: 5.2 KiB Depois Largura: | Altura: | Tamanho: 5.2 KiB |
@@ -5,7 +5,7 @@ $(function(){
|
||||
var message = $('input[name=message]'),
|
||||
name = $('input[name=name]')
|
||||
if (message.val())
|
||||
$.post('/chat', { message: $.trim(name.val()) + ': ' + message.val() }, function(){
|
||||
$.post('/chat', { name: name.val(), message: message.val() }, function(){
|
||||
message.val('')
|
||||
})
|
||||
else
|
||||
@@ -1,10 +1,9 @@
|
||||
%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: 'guest' }
|
||||
%input{ type: 'text', name: 'name', value: name || 'guest' }
|
||||
%input{ type: 'text', name: 'message' }
|
||||
%input{ type: 'submit', value: 'Send' }
|
||||
@@ -0,0 +1,12 @@
|
||||
%html
|
||||
%head
|
||||
%title= title
|
||||
%script{ src: '/public/javascripts/jquery.js' }
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper
|
||||
!= body
|
||||
#online
|
||||
Online:
|
||||
%strong= usersOnline
|
||||
@@ -0,0 +1 @@
|
||||
%li= message
|
||||
@@ -0,0 +1,81 @@
|
||||
body
|
||||
:font-family "Helvetica Neue", "Lucida Grande", "Arial"
|
||||
:font-size 13px
|
||||
:text-align center
|
||||
=text-stroke 1px rgba(255, 255, 255, 0.1)
|
||||
: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
|
||||
=border-radius 3px
|
||||
=border-radius 3px
|
||||
li:hover
|
||||
:cursor pointer
|
||||
:color #2E2E2E
|
||||
|
||||
input[type=text]
|
||||
:padding 5px
|
||||
:border 1px solid #ddd
|
||||
:outline none
|
||||
=border-radius 2px
|
||||
input[type=text]:focus
|
||||
:border-color #00C3FF
|
||||
|
||||
input[type=submit]
|
||||
=border-radius 2px
|
||||
=box-shadow 0 1px 2px #ddd
|
||||
: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
|
||||
=transition-property padding
|
||||
=transition-duration 0.15s
|
||||
a:hover
|
||||
:padding 0 5px
|
||||
a:hover:before
|
||||
:content 'visit: '
|
||||
|
||||
#online
|
||||
:font-size 12px
|
||||
@@ -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
@@ -1,84 +0,0 @@
|
||||
body {
|
||||
font-family: "Helvetica Neue", "Lucida Grande", "Arial";
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
-webkit-text-stroke: 1px rgba(255, 255, 255, 0.1);
|
||||
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;
|
||||
}
|
||||
ul li {
|
||||
margin: 5px 0;
|
||||
padding: 3px 8px;
|
||||
list-style: none;
|
||||
border: 1px solid #eee;
|
||||
-webkit-border-radius: 3px;
|
||||
-mox-border-radius: 3px;
|
||||
-webkit-transition-property: color;
|
||||
-webkit-transition-duration: 0.1s;
|
||||
}
|
||||
ul li:hover {
|
||||
cursor: pointer;
|
||||
color: #2E2E2E;
|
||||
}
|
||||
ul {
|
||||
max-height: 300px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
input[type=text] {
|
||||
padding: 5px;
|
||||
border: 1px solid #ddd;
|
||||
outline: none;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
}
|
||||
input[type=text]:focus {
|
||||
border-color: #00C3FF;
|
||||
}
|
||||
input[type=submit] {
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-box-shadow: 0 1px 2px #ddd;
|
||||
-moz-box-shadow: 0 1px 2px #ddd;
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
var sys = require('sys')
|
||||
|
||||
configure(function(){
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(Cookie)
|
||||
use(Session)
|
||||
use(Flash)
|
||||
use(Logger)
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
set('max upload size', (5).megabytes)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.redirect('/upload')
|
||||
})
|
||||
|
||||
get('/upload', function(){
|
||||
this.render('upload.html.haml', {
|
||||
locals: {
|
||||
flashes: this.flash('info')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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('/*.css', function(file){
|
||||
this.render(file + '.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
run()
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,14 @@
|
||||
%html
|
||||
%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)
|
||||
%ul.messages.info
|
||||
- each msg in flashes
|
||||
%li= msg
|
||||
.body!= body
|
||||
@@ -0,0 +1,68 @@
|
||||
body
|
||||
:font-family "Helvetica Neue", "Lucida Grande", "Arial"
|
||||
:font-size 13px
|
||||
:text-align center
|
||||
:-webkit-text-stroke 1px rgba(255, 255, 255, 0.1)
|
||||
:color #555
|
||||
|
||||
h1, h2
|
||||
:margin 0 0 15px 0
|
||||
:font-size 22px
|
||||
:color #343434
|
||||
h1
|
||||
:text-shadow 1px 2px 2px #ddd
|
||||
:font-size 60px
|
||||
h2
|
||||
:margin-top 15px
|
||||
|
||||
#wrapper
|
||||
:position relative
|
||||
:margin 100px auto
|
||||
:width 500px
|
||||
:text-align left
|
||||
|
||||
input[type=file]
|
||||
:padding 5px
|
||||
:border 1px solid #ddd
|
||||
:outline none
|
||||
:-webkit-border-radius 2px
|
||||
:-moz-border-radius 2px
|
||||
input[type=file]:focus
|
||||
:border-color #00C3FF
|
||||
|
||||
input[type=submit]
|
||||
:-webkit-border-radius 2px
|
||||
:-moz-border-radius 2px
|
||||
:-webkit-box-shadow 0 1px 2px #ddd
|
||||
:-moz-box-shadow 0 1px 2px #ddd
|
||||
: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
|
||||
|
||||
form
|
||||
.panel
|
||||
:float left
|
||||
:width 100%
|
||||
:margin-bottom 15px
|
||||
|
||||
.messages
|
||||
:margin 0
|
||||
:padding 0
|
||||
:border 1px solid #eee
|
||||
=box-shadow 2px 2px 5px #eee
|
||||
li
|
||||
:padding 5px 10px
|
||||
:list-style none
|
||||
@@ -0,0 +1,18 @@
|
||||
- if (typeof images !== 'undefined')
|
||||
.images
|
||||
- each img in images
|
||||
%img{ src: img }
|
||||
|
||||
%h2 Singles
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[0]' }
|
||||
%input{ type: 'file', name: 'images[1]' }
|
||||
%input{ type: 'file', name: 'images[2]' }
|
||||
%div.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
|
||||
%h2 Multiple
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[]', multiple: 'multiple' }
|
||||
%div.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
@@ -1,8 +0,0 @@
|
||||
%html
|
||||
%head
|
||||
%title Chat
|
||||
%script{ src: '/public/javascripts/jquery.js' }
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/public/stylesheets/style.css' }
|
||||
%body
|
||||
#wrapper= body
|
||||
+7
-2
@@ -1,3 +1,8 @@
|
||||
|
||||
require('support/js-oo/lib/oo')
|
||||
require('express/core')
|
||||
require.paths.unshift(__dirname + '/support/ext/lib')
|
||||
require.paths.unshift(__dirname + '/support/haml/lib')
|
||||
require.paths.unshift(__dirname + '/support/sass/lib')
|
||||
require.paths.unshift(__dirname + '/support/multipart/lib')
|
||||
require('ext')
|
||||
Class = require('support/class/lib/class').Class
|
||||
require('express/core')
|
||||
@@ -1,544 +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)
|
||||
}
|
||||
+131
-75
@@ -1,23 +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/helpers'))
|
||||
process.mixin(require('express/plugin'))
|
||||
process.mixin(require('express/mime'))
|
||||
process.mixin(require('express/dsl'))
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
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')
|
||||
|
||||
global.merge(require('express/plugin'))
|
||||
global.merge(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
|
||||
@@ -25,29 +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
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute this route's #fn with _args_,
|
||||
* against _context_ or GLOBAL.
|
||||
*
|
||||
* @param {array} args
|
||||
* @return {mixed}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
run: function(args, context) {
|
||||
return this.fn.apply(context || GLOBAL, args)
|
||||
this.callback = callback
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -77,7 +69,7 @@ Route = Class({
|
||||
var self = this
|
||||
this.keys = []
|
||||
if (path instanceof RegExp) return path
|
||||
return new RegExp('^' + escapeRegexp(normalizePath(path), '.')
|
||||
return new RegExp('^' + RegExp.escape(normalizePath(path), '.')
|
||||
.replace(/\*/g, '(.+)')
|
||||
.replace(/(\/|\\\.):(\w+)\?/g, function(_, c, key){
|
||||
self.keys.push(key)
|
||||
@@ -92,7 +84,7 @@ Route = Class({
|
||||
|
||||
// --- Router
|
||||
|
||||
Router = Class({
|
||||
Router = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with _request_ and parse url.
|
||||
@@ -101,7 +93,7 @@ Router = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(request) {
|
||||
constructor: function(request) {
|
||||
this.request = request
|
||||
},
|
||||
|
||||
@@ -112,14 +104,21 @@ Router = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
route: function(){
|
||||
var route = this.matchingRoute()
|
||||
if (route)
|
||||
return route.run(this.request.captures.slice(1), this.request)
|
||||
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()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -129,11 +128,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
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -155,23 +156,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.
|
||||
@@ -210,53 +214,98 @@ Server = Class({
|
||||
if (host !== undefined) this.host = host
|
||||
if (port !== undefined) this.port = port
|
||||
if (backlog !== undefined) this.backlog = backlog
|
||||
require('http')
|
||||
.createServer(function(request, response){
|
||||
http
|
||||
.createServer(function(req, response){
|
||||
var request, pendingFiles = 0
|
||||
req.setBodyEncoding('binary')
|
||||
request = new Request(req, response)
|
||||
request.body = ''
|
||||
request.setBodyEncoding('utf8')
|
||||
request.addListener('body', function(chunk){ request.body += chunk })
|
||||
request.addListener('complete', 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)
|
||||
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
|
||||
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')
|
||||
sys.puts('Express started at http://' + this.host + ':' + this.port + '/ in ' + Express.environment + ' mode')
|
||||
},
|
||||
|
||||
/**
|
||||
* 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')
|
||||
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.halt(200, body, function(err){
|
||||
if (err) request.error(err)
|
||||
})
|
||||
} catch (err) {
|
||||
request.error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// --- Express
|
||||
|
||||
Express = {
|
||||
version: '0.0.2',
|
||||
version: '0.9.0',
|
||||
config: [],
|
||||
routes: [],
|
||||
plugins: [],
|
||||
settings: {},
|
||||
params: {},
|
||||
server: new Server
|
||||
}
|
||||
|
||||
@@ -264,6 +313,7 @@ Express = {
|
||||
|
||||
configure(function(){
|
||||
use(require('express/plugins/view').View)
|
||||
use(require('express/plugins/cache').Cache)
|
||||
use(require('express/plugins/redirect').Redirect)
|
||||
use(require('express/plugins/body-decoder').BodyDecoder)
|
||||
})
|
||||
@@ -276,3 +326,9 @@ configure('development', function(){
|
||||
configure('test', function(){
|
||||
enable('throw exceptions')
|
||||
})
|
||||
|
||||
configure('production', function(){
|
||||
enable('cache view contents')
|
||||
enable('cache view partials')
|
||||
enable('cache static files')
|
||||
})
|
||||
|
||||
+67
-24
@@ -10,10 +10,10 @@
|
||||
*/
|
||||
|
||||
function route(method) {
|
||||
return function(path, options, fn){
|
||||
return function(path, options, callback){
|
||||
if (options instanceof Function)
|
||||
fn = options, options = {}
|
||||
Express.routes.push(new Route(method, path, fn, options))
|
||||
callback = options, options = {}
|
||||
Express.routes.push(new Route(method, path, callback, options))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,12 +68,16 @@ exports.disable = function(option) {
|
||||
*/
|
||||
|
||||
exports.run = function() {
|
||||
configure(Express.environment = process.ENV.EXPRESS_ENV || 'development')
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure _environment_ with _fn_.
|
||||
* Configure _env_ with _callback_.
|
||||
*
|
||||
* Global configuration, disregards which
|
||||
* environment is active:
|
||||
@@ -92,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 'environment require'
|
||||
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
|
||||
@@ -115,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)
|
||||
|
||||
/**
|
||||
* Load libxml support.
|
||||
*/
|
||||
|
||||
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).prev_sibling()])
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the next element.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
next: function() {
|
||||
return $([this.at(0).next_sibling()])
|
||||
},
|
||||
|
||||
/**
|
||||
* 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)
|
||||
this.merge(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
|
||||
}
|
||||
})
|
||||
@@ -1,95 +0,0 @@
|
||||
|
||||
// Express - Helpers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* JSON aliases.
|
||||
*/
|
||||
|
||||
JSON.encode = JSON.stringify
|
||||
JSON.decode = JSON.parse
|
||||
|
||||
/**
|
||||
* Return the directory name of the given _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.dirname = function(path) {
|
||||
return path.split('/').slice(0, -1).join('/')
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_.
|
||||
*
|
||||
* @param {string} html
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.escape = function(html) {
|
||||
return html.toString()
|
||||
.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')
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
|
||||
// Express - HTTP - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http'),
|
||||
parse = require('url').parse,
|
||||
queryString = require('querystring')
|
||||
|
||||
/**
|
||||
* Mega super awesome private request utility.
|
||||
*
|
||||
* @param {string} method
|
||||
* @param {string} url
|
||||
* @param {hash} data
|
||||
* @param {hash} headers
|
||||
* @param {function} callback
|
||||
* @param {number} redirects
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function request(method, url, data, headers, callback, redirects) {
|
||||
var buf = '',
|
||||
redirects = redirects || 3,
|
||||
url = parse(url),
|
||||
path = url.pathname || '/',
|
||||
search = url.search || '',
|
||||
hash = url.hash || '',
|
||||
port = url.port || 80,
|
||||
headers = { host: url.hostname }.merge(headers || {}),
|
||||
client = http.createClient(port, url.hostname)
|
||||
if (headers.redirect)
|
||||
redirects = headers.redirect,
|
||||
delete headers.redirect
|
||||
if (data) {
|
||||
data = queryString.stringify(data)
|
||||
if (method === 'GET')
|
||||
search += (search ? '&' : '?') + data
|
||||
else
|
||||
headers['content-length'] = data.length,
|
||||
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 (req.statusCode < 200 || req.statusCode >= 400)
|
||||
callback(new Error('request failed with status ' + res.statusCode + ' "' + http.STATUS_CODES[res.statusCode] + '"'))
|
||||
else if (res.statusCode >= 300 && res.statusCode < 400)
|
||||
if (--redirects)
|
||||
request(method, res.headers.location, headers, data, callback, redirects)
|
||||
else
|
||||
callback(new Error('maximum number of redirects reached'))
|
||||
else {
|
||||
res.setBodyEncoding('utf8')
|
||||
res
|
||||
.addListener('data', function(chunk){ buf += chunk })
|
||||
.addListener('end', function(){ callback(null, buf, res) })
|
||||
}
|
||||
})
|
||||
req.end()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTTP Client function for the given _method_.
|
||||
*
|
||||
* @param {string} method
|
||||
* @return {function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function client(method) {
|
||||
return function() {
|
||||
var headers, data,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Public API
|
||||
|
||||
exports.get = exports.view = client('get')
|
||||
exports.post = exports.create = client('post')
|
||||
exports.put = exports.update = client('put')
|
||||
exports.del = exports.destroy = client('delete')
|
||||
+182
-9
@@ -1,136 +1,278 @@
|
||||
|
||||
// Express - Mime - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var types = {
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var extname = require('path').extname
|
||||
|
||||
/**
|
||||
* Mime type lookup table.
|
||||
*/
|
||||
|
||||
exports.types = {
|
||||
'323' : 'text/h323',
|
||||
'3gp' : 'video/3gpp',
|
||||
'a' : 'application/octet-stream',
|
||||
'acx' : 'application/internet-property-stream',
|
||||
'ai' : 'application/postscript',
|
||||
'aif' : 'audio/x-aiff',
|
||||
'aifc' : 'audio/x-aiff',
|
||||
'aiff' : 'audio/x-aiff',
|
||||
'asc' : 'application/pgp-signature',
|
||||
'asf' : 'video/x-ms-asf',
|
||||
'asr' : 'video/x-ms-asf',
|
||||
'asm' : 'text/x-asm',
|
||||
'asx' : 'video/x-ms-asf',
|
||||
'atom' : 'application/atom+xml',
|
||||
'au' : 'audio/basic',
|
||||
'avi' : 'video/x-msvideo',
|
||||
'axs' : 'application/olescript',
|
||||
'bas' : 'text/plain',
|
||||
'bat' : 'application/x-msdownload',
|
||||
'bcpio' : 'application/x-bcpio',
|
||||
'bin' : 'application/octet-stream',
|
||||
'bmp' : 'image/bmp',
|
||||
'bz2' : 'application/x-bzip2',
|
||||
'c' : 'text/x-c',
|
||||
'cab' : 'application/vnd.ms-cab-compressed',
|
||||
'cat' : 'application/vnd.ms-pkiseccat',
|
||||
'cc' : 'text/x-c',
|
||||
'cdf' : 'application/x-netcdf',
|
||||
'cer' : 'application/x-x509-ca-cert',
|
||||
'cgm' : 'image/cgm',
|
||||
'chm' : 'application/vnd.ms-htmlhelp',
|
||||
'class' : 'application/octet-stream',
|
||||
'clp' : 'application/x-msclip',
|
||||
'cmx' : 'image/x-cmx',
|
||||
'cod' : 'image/cis-cod',
|
||||
'com' : 'application/x-msdownload',
|
||||
'conf' : 'text/plain',
|
||||
'cpio' : 'application/x-cpio',
|
||||
'cpp' : 'text/x-c',
|
||||
'cpt' : 'application/mac-compactpro',
|
||||
'crd' : 'application/x-mscardfile',
|
||||
'crl' : 'application/pkix-crl',
|
||||
'crt' : 'application/x-x509-ca-cert',
|
||||
'csh' : 'application/x-csh',
|
||||
'css' : 'text/css',
|
||||
'csv' : 'text/csv',
|
||||
'cxx' : 'text/x-c',
|
||||
'dcr' : 'application/x-director',
|
||||
'deb' : 'application/x-debian-package',
|
||||
'der' : 'application/x-x509-ca-cert',
|
||||
'diff' : 'text/x-diff',
|
||||
'dir' : 'application/x-director',
|
||||
'djv' : 'image/vnd.djvu',
|
||||
'djvu' : 'image/vnd.djvu',
|
||||
'dll' : 'application/x-msdownload',
|
||||
'dmg' : 'application/octet-stream',
|
||||
'dms' : 'application/octet-stream',
|
||||
'doc' : 'application/msword',
|
||||
'dot' : 'application/msword',
|
||||
'dtd' : 'application/xml-dtd',
|
||||
'dv' : 'video/x-dv',
|
||||
'dvi' : 'application/x-dvi',
|
||||
'dxr' : 'application/x-director',
|
||||
'ear' : 'application/java-archive',
|
||||
'eml' : 'message/rfc822',
|
||||
'eps' : 'application/postscript',
|
||||
'etx' : 'text/x-setext',
|
||||
'evy' : 'application/envoy',
|
||||
'exe' : 'application/x-msdownload',
|
||||
'ez' : 'application/andrew-inset',
|
||||
'f' : 'text/x-fortran',
|
||||
'f77' : 'text/x-fortran',
|
||||
'f90' : 'text/x-fortran',
|
||||
'fif' : 'application/fractals',
|
||||
'flr' : 'x-world/x-vrml',
|
||||
'flv' : 'video/x-flv',
|
||||
'for' : 'text/x-fortran',
|
||||
'gem' : 'application/octet-stream',
|
||||
'gemspec' : 'text/x-script.ruby',
|
||||
'gif' : 'image/gif',
|
||||
'gram' : 'application/srgs',
|
||||
'grxml' : 'application/srgs+xml',
|
||||
'gtar' : 'application/x-gtar',
|
||||
'gz' : 'application/x-gzip',
|
||||
'h' : 'text/x-c',
|
||||
'hdf' : 'application/x-hdf',
|
||||
'hh' : 'text/x-c',
|
||||
'hlp' : 'application/winhlp',
|
||||
'hqx' : 'application/mac-binhex40',
|
||||
'hta' : 'application/hta',
|
||||
'htc' : 'text/x-component',
|
||||
'htm' : 'text/html',
|
||||
'html' : 'text/html',
|
||||
'htt' : 'text/webviewhtml',
|
||||
'ice' : 'x-conference/x-cooltalk',
|
||||
'ico' : 'image/vnd.microsoft.icon',
|
||||
'ics' : 'text/calendar',
|
||||
'ief' : 'image/ief',
|
||||
'ifb' : 'text/calendar',
|
||||
'iges' : 'model/iges',
|
||||
'igs' : 'model/iges',
|
||||
'iii' : 'application/x-iphone',
|
||||
'ins' : 'application/x-internet-signup',
|
||||
'isp' : 'application/x-internet-signup',
|
||||
'iso' : 'application/octet-stream',
|
||||
'jar' : 'application/java-archive',
|
||||
'java' : 'text/x-java-source',
|
||||
'jfif' : 'image/pipeg',
|
||||
'jnlp' : 'application/x-java-jnlp-file',
|
||||
'jp2' : 'image/jp2',
|
||||
'jpe' : 'image/jpeg',
|
||||
'jpeg' : 'image/jpeg',
|
||||
'jpg' : 'image/jpeg',
|
||||
'js' : 'application/javascript',
|
||||
'json' : 'application/json',
|
||||
'kar' : 'audio/midi',
|
||||
'latex' : 'application/x-latex',
|
||||
'lha' : 'application/octet-stream',
|
||||
'lsf' : 'video/x-la-asf',
|
||||
'lsx' : 'video/x-la-asf',
|
||||
'lzh' : 'application/octet-stream',
|
||||
'log' : 'text/plain',
|
||||
'm13' : 'application/x-msmediaview',
|
||||
'm14' : 'application/x-msmediaview',
|
||||
'm3u' : 'audio/x-mpegurl',
|
||||
'm4a' : 'audio/mp4a-latm',
|
||||
'm4b' : 'audio/mp4a-latm',
|
||||
'm4p' : 'audio/mp4a-latm',
|
||||
'm4u' : 'video/vnd.mpegurl',
|
||||
'm4v' : 'video/mp4',
|
||||
'mac' : 'image/x-macpaint',
|
||||
'man' : 'text/troff',
|
||||
'mathml' : 'application/mathml+xml',
|
||||
'mbox' : 'application/mbox',
|
||||
'mdb' : 'application/x-msaccess',
|
||||
'mdoc' : 'text/troff',
|
||||
'me' : 'text/troff',
|
||||
'mesh' : 'model/mesh',
|
||||
'mht' : 'message/rfc822',
|
||||
'mhtml' : 'message/rfc822',
|
||||
'mid' : 'audio/midi',
|
||||
'midi' : 'audio/midi',
|
||||
'mif' : 'application/vnd.mif',
|
||||
'mime' : 'message/rfc822',
|
||||
'mml' : 'application/mathml+xml',
|
||||
'mng' : 'video/x-mng',
|
||||
'mny' : 'application/x-msmoney',
|
||||
'mov' : 'video/quicktime',
|
||||
'movie' : 'video/x-sgi-movie',
|
||||
'mp2' : 'video/mpeg',
|
||||
'mp3' : 'audio/mpeg',
|
||||
'mp4' : 'video/mp4',
|
||||
'mp4v' : 'video/mp4',
|
||||
'mpa' : 'video/mpeg',
|
||||
'mpe' : 'video/mpeg',
|
||||
'mpeg' : 'video/mpeg',
|
||||
'mpg' : 'video/mpeg',
|
||||
'mpga' : 'audio/mpeg',
|
||||
'mpp' : 'application/vnd.ms-project',
|
||||
'mpv2' : 'video/mpeg',
|
||||
'ms' : 'text/troff',
|
||||
'msh' : 'model/mesh',
|
||||
'msi' : 'application/x-msdownload',
|
||||
'mvb' : 'application/x-msmediaview',
|
||||
'mxu' : 'video/vnd.mpegurl',
|
||||
'nc' : 'application/x-netcdf',
|
||||
'nws' : 'message/rfc822',
|
||||
'oda' : 'application/oda',
|
||||
'odp' : 'application/vnd.oasis.opendocument.presentation',
|
||||
'ods' : 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'odt' : 'application/vnd.oasis.opendocument.text',
|
||||
'ogg' : 'application/ogg',
|
||||
'p' : 'text/x-pascal',
|
||||
'p10' : 'application/pkcs10',
|
||||
'p12' : 'application/x-pkcs12',
|
||||
'p7b' : 'application/x-pkcs7-certificates',
|
||||
'p7c' : 'application/x-pkcs7-mime',
|
||||
'p7m' : 'application/x-pkcs7-mime',
|
||||
'p7r' : 'application/x-pkcs7-certreqresp',
|
||||
'p7s' : 'application/x-pkcs7-signature',
|
||||
'pas' : 'text/x-pascal',
|
||||
'pbm' : 'image/x-portable-bitmap',
|
||||
'pct' : 'image/pict',
|
||||
'pdb' : 'chemical/x-pdb',
|
||||
'pdf' : 'application/pdf',
|
||||
'pem' : 'application/x-x509-ca-cert',
|
||||
'pfx' : 'application/x-pkcs12',
|
||||
'pgm' : 'image/x-portable-graymap',
|
||||
'pgn' : 'application/x-chess-pgn',
|
||||
'pgp' : 'application/pgp-encrypted',
|
||||
'pic' : 'image/pict',
|
||||
'pict' : 'image/pict',
|
||||
'pkg' : 'application/octet-stream',
|
||||
'pko' : 'application/ynd.ms-pkipko',
|
||||
'pl' : 'text/x-script.perl',
|
||||
'pm' : 'text/x-script.perl-module',
|
||||
'pma' : 'application/x-perfmon',
|
||||
'pmc' : 'application/x-perfmon',
|
||||
'pml' : 'application/x-perfmon',
|
||||
'pmr' : 'application/x-perfmon',
|
||||
'pmw' : 'application/x-perfmon',
|
||||
'png' : 'image/png',
|
||||
'pnm' : 'image/x-portable-anymap',
|
||||
'pnt' : 'image/x-macpaint',
|
||||
'pntg' : 'image/x-macpaint',
|
||||
'pot' : 'application/vnd.ms-powerpoint',
|
||||
'ppm' : 'image/x-portable-pixmap',
|
||||
'pps' : 'application/vnd.ms-powerpoint',
|
||||
'ppt' : 'application/vnd.ms-powerpoint',
|
||||
'prf' : 'application/pics-rules',
|
||||
'ps' : 'application/postscript',
|
||||
'psd' : 'image/vnd.adobe.photoshop',
|
||||
'pub' : 'application/x-mspublisher',
|
||||
'py' : 'text/x-script.python',
|
||||
'qt' : 'video/quicktime',
|
||||
'qti' : 'image/x-quicktime',
|
||||
'qtif' : 'image/x-quicktime',
|
||||
'ra' : 'audio/x-pn-realaudio',
|
||||
'rake' : 'text/x-script.ruby',
|
||||
'ram' : 'audio/x-pn-realaudio',
|
||||
'rar' : 'application/x-rar-compressed',
|
||||
'ras' : 'image/x-cmu-raster',
|
||||
'rb' : 'text/x-script.ruby',
|
||||
'rdf' : 'application/rdf+xml',
|
||||
'rgb' : 'image/x-rgb',
|
||||
'rm' : 'application/vnd.rn-realmedia',
|
||||
'rmi' : 'audio/mid',
|
||||
'roff' : 'text/troff',
|
||||
'rpm' : 'application/x-redhat-package-manager',
|
||||
'rss' : 'application/rss+xml',
|
||||
'rtf' : 'application/rtf',
|
||||
'rtx' : 'text/richtext',
|
||||
'ru' : 'text/x-script.ruby',
|
||||
's' : 'text/x-asm',
|
||||
'scd' : 'application/x-msschedule',
|
||||
'sct' : 'text/scriptlet',
|
||||
'setpay' : 'application/set-payment-initiation',
|
||||
'setreg' : 'application/set-registration-initiation',
|
||||
'sgm' : 'text/sgml',
|
||||
'sgml' : 'text/sgml',
|
||||
'sh' : 'application/x-sh',
|
||||
'shar' : 'application/x-shar',
|
||||
'sig' : 'application/pgp-signature',
|
||||
'silo' : 'model/mesh',
|
||||
'sit' : 'application/x-stuffit',
|
||||
'skd' : 'application/x-koan',
|
||||
'skm' : 'application/x-koan',
|
||||
'skp' : 'application/x-koan',
|
||||
'skt' : 'application/x-koan',
|
||||
'smi' : 'application/smil',
|
||||
'smil' : 'application/smil',
|
||||
'snd' : 'audio/basic',
|
||||
'so' : 'application/octet-stream',
|
||||
'spc' : 'application/x-pkcs7-certificates',
|
||||
'spl' : 'application/x-futuresplash',
|
||||
'src' : 'application/x-wais-source',
|
||||
'sst' : 'application/vnd.ms-pkicertstore',
|
||||
'stl' : 'application/vnd.ms-pkistl',
|
||||
'stm' : 'text/html',
|
||||
'sv4cpio' : 'application/x-sv4cpio',
|
||||
'sv4crc' : 'application/x-sv4crc',
|
||||
'svg' : 'image/svg+xml',
|
||||
'svgz' : 'image/svg+xml',
|
||||
'swf' : 'application/x-shockwave-flash',
|
||||
@@ -142,30 +284,60 @@ var types = {
|
||||
'texi' : 'application/x-texinfo',
|
||||
'texinfo' : 'application/x-texinfo',
|
||||
'text' : 'text/plain',
|
||||
'tgz' : 'application/x-compressed',
|
||||
'tif' : 'image/tiff',
|
||||
'tiff' : 'image/tiff',
|
||||
'torrent' : 'application/x-bittorrent',
|
||||
'tr' : 'text/troff',
|
||||
'trm' : 'application/x-msterminal',
|
||||
'tsv' : 'text/tab-seperated-values',
|
||||
'txt' : 'text/plain',
|
||||
'uls' : 'text/iuls',
|
||||
'ustar' : 'application/x-ustar',
|
||||
'vcd' : 'application/x-cdlink',
|
||||
'vcf' : 'text/x-vcard',
|
||||
'vcs' : 'text/x-vcalendar',
|
||||
'vrml' : 'model/vrml',
|
||||
'vxml' : 'application/voicexml+xml',
|
||||
'war' : 'application/java-archive',
|
||||
'wav' : 'audio/x-wav',
|
||||
'wbmp' : 'image/vnd.wap.wbmp',
|
||||
'wbxml' : 'application/vnd.wap.wbxml',
|
||||
'wcm' : 'application/vnd.ms-works',
|
||||
'wdb' : 'application/vnd.ms-works',
|
||||
'wks' : 'application/vnd.ms-works',
|
||||
'wma' : 'audio/x-ms-wma',
|
||||
'wmf' : 'application/x-msmetafile',
|
||||
'wml' : 'text/vnd.wap.wml',
|
||||
'wmls' : 'text/vnd.wap.wmlscript',
|
||||
'wmlsc' : 'application/vnd.wap.wmlscriptc',
|
||||
'wmv' : 'video/x-ms-wmv',
|
||||
'wmx' : 'video/x-ms-wmx',
|
||||
'wps' : 'application/vnd.ms-works',
|
||||
'wri' : 'application/x-mswrite',
|
||||
'wrl' : 'model/vrml',
|
||||
'wrz' : 'x-world/x-vrml',
|
||||
'wsdl' : 'application/wsdl+xml',
|
||||
'xaf' : 'x-world/x-vrml',
|
||||
'xbm' : 'image/x-xbitmap',
|
||||
'xht' : 'application/xhtml+xml',
|
||||
'xhtml' : 'application/xhtml+xml',
|
||||
'xla' : 'application/vnd.ms-excel',
|
||||
'xlc' : 'application/vnd.ms-excel',
|
||||
'xlm' : 'application/vnd.ms-excel',
|
||||
'xls' : 'application/vnd.ms-excel',
|
||||
'xlt' : 'application/vnd.ms-excel',
|
||||
'xml' : 'application/xml',
|
||||
'xof' : 'x-world/x-vrml',
|
||||
'xpm' : 'image/x-xpixmap',
|
||||
'xsl' : 'application/xml',
|
||||
'xslt' : 'application/xslt+xml',
|
||||
'xul' : 'application/vnd.mozilla.xul+xml',
|
||||
'xwd' : 'image/x-xwindowdump',
|
||||
'xyz' : 'chemical/x-xyz',
|
||||
'yaml' : 'text/yaml',
|
||||
'yml' : 'text/yaml',
|
||||
'z' : 'application/x-compress',
|
||||
'zip' : 'application/zip'
|
||||
}
|
||||
|
||||
@@ -178,10 +350,11 @@ var types = {
|
||||
* however this can be altered using the 'default mime type'
|
||||
* setting.
|
||||
*
|
||||
* mime('png') // => 'image/png'
|
||||
* mime('.png') // => 'image/png'
|
||||
* mime('image.png') // => 'image/png'
|
||||
* mime('path/to/image.png') // => 'image/png'
|
||||
* var mime = require('express/mime')
|
||||
* mime.type('png') // => 'image/png'
|
||||
* mime.type('.png') // => 'image/png'
|
||||
* mime.type('image.png') // => 'image/png'
|
||||
* mime.type('path/to/image.png') // => 'image/png'
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
@@ -189,9 +362,9 @@ var types = {
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.mime = function(path) {
|
||||
return types[path] ||
|
||||
types[extname(path)] ||
|
||||
exports.type = function(path) {
|
||||
return exports.types[path] ||
|
||||
exports.types[extname(path).substr(1)] ||
|
||||
set('default mime type') ||
|
||||
'application/octet-stream'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ var style = require('express/pages/style').style
|
||||
|
||||
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
|
||||
@@ -32,8 +32,8 @@ function stack(e) {
|
||||
*/
|
||||
|
||||
function hash(hash) {
|
||||
if (!$(hash).length()) return '<tr><td class="empty" colspan="2">Empty</td></tr>'
|
||||
return $(hash).map(function(val, key){
|
||||
if (!hash || !hash.values.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')
|
||||
}
|
||||
@@ -62,15 +62,15 @@ 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\
|
||||
|
||||
+11
-16
@@ -3,6 +3,8 @@
|
||||
|
||||
/**
|
||||
* Push _plugin_ with _options_ to the plugin stack.
|
||||
* If _plugin_ has already been pushed, then it's options
|
||||
* will override any previously set.
|
||||
*
|
||||
* @param {Plugin} plugin
|
||||
* @param {hash} options
|
||||
@@ -10,7 +12,12 @@
|
||||
*/
|
||||
|
||||
exports.use = function(plugin, options) {
|
||||
if ('init' in plugin) plugin.init()
|
||||
if (Express.environment === 'test' && 'init' in plugin)
|
||||
plugin.init(options)
|
||||
Express.plugins.each(function(other, i){
|
||||
if (other.klass === plugin)
|
||||
delete Express.plugins[i]
|
||||
})
|
||||
Express.plugins.push({
|
||||
klass: plugin,
|
||||
options: options
|
||||
@@ -19,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_.
|
||||
@@ -40,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,9 +1,12 @@
|
||||
|
||||
// Express - Plugins - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
process.mixin(require('express/plugins/hooks'))
|
||||
process.mixin(require('express/plugins/cookie'))
|
||||
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'))
|
||||
global.merge(require('express/plugins/hooks'))
|
||||
global.merge(require('express/plugins/static'))
|
||||
global.merge(require('express/plugins/flash'))
|
||||
global.merge(require('express/plugins/cache'))
|
||||
global.merge(require('express/plugins/cookie'))
|
||||
global.merge(require('express/plugins/session'))
|
||||
global.merge(require('express/plugins/logger'))
|
||||
global.merge(require('express/plugins/content-length'))
|
||||
global.merge(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: {
|
||||
|
||||
@@ -15,7 +21,7 @@ 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)
|
||||
request.header('content-type').includes('application/x-www-form-urlencoded'))
|
||||
request.params.post = queryString.parseQuery(request.body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
|
||||
// Express - Cache - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('express/request').Request
|
||||
|
||||
// --- Cache
|
||||
|
||||
var Cache = new Class({
|
||||
|
||||
/**
|
||||
* Initialize cache with _key_ and _val_.
|
||||
*/
|
||||
|
||||
constructor: function(key, val) {
|
||||
this.key = key
|
||||
this.val = val
|
||||
this.created = Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store
|
||||
|
||||
exports.Store = new Class({
|
||||
|
||||
/**
|
||||
* Ensure that the given _key_ is a string.
|
||||
* Override in subclass to provide data-store specific functionality.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set: function(key, val, callback) {
|
||||
if (typeof key !== 'string')
|
||||
throw new Error(this.name + ' store #set() key must be a string')
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure that the given _key_ is a string.
|
||||
* Override in subclass to provide data-store specific functionality.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get: function(key, callback) {
|
||||
if (typeof key !== 'string')
|
||||
throw new Error(this.name + 'store #get() key must be a string')
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert to '[NAME Store]'.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toString: function() {
|
||||
return '[' + this.name + ' Store]'
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store.Memory
|
||||
|
||||
exports.Store.Memory = exports.Store.extend({
|
||||
|
||||
/**
|
||||
* Datastore name.
|
||||
*/
|
||||
|
||||
name: 'Memory',
|
||||
|
||||
/**
|
||||
* Initialize data.
|
||||
*/
|
||||
|
||||
constructor: function() {
|
||||
this.data = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the given _key_ to _val_.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @param {function} callback
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set: function(key, val, callback) {
|
||||
exports.Store.prototype.set.apply(this, arguments)
|
||||
this.data[key] = new Cache(key, val)
|
||||
if (callback) callback(val)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get data found matching the given _key_.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* cache.get('page:front', function(val){})
|
||||
* // => '<html>...</html>'
|
||||
*
|
||||
* 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, callback) {
|
||||
exports.Store.prototype.get.apply(this, arguments)
|
||||
if (key.indexOf('*') === -1)
|
||||
return callback(this.data[key] instanceof Cache
|
||||
? this.data[key].val
|
||||
: null)
|
||||
var regexp = this.normalize(key)
|
||||
callback(this.data.reduce(function(vals, cache){
|
||||
if (regexp.test(cache.key))
|
||||
vals[cache.key] = cache.val
|
||||
return vals
|
||||
}, {}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear data matching the given _key_.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* cache.clear('page:front', function(){})
|
||||
* cache.clear('page:*', function(){})
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clear: function(key, callback) {
|
||||
if (key.indexOf('*') === -1)
|
||||
delete this.data[key]
|
||||
else {
|
||||
var regexp = this.normalize(key)
|
||||
for (var key in this.data)
|
||||
if (this.data.hasOwnProperty(key))
|
||||
if (regexp.test(key))
|
||||
delete this.data[key]
|
||||
}
|
||||
callback()
|
||||
},
|
||||
|
||||
/**
|
||||
* Reap caches older than _ms_.
|
||||
*
|
||||
* @param {int} ms
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms, callback) {
|
||||
var self = this,
|
||||
threshold = +new Date(Date.now() - ms)
|
||||
this.data.each(function(cache){
|
||||
if (cache.created < threshold)
|
||||
self.clear(cache.key, function(){})
|
||||
})
|
||||
if (callback) callback()
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert the given key matching _pattern_
|
||||
* into a RegExp.
|
||||
*
|
||||
* - * is converted to (.*?)
|
||||
*
|
||||
* @param {string} pattern
|
||||
* @return {regexp}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
normalize: function(pattern) {
|
||||
return new RegExp('^' + pattern.replace(/[*]/g, '(.*?)') + '$')
|
||||
}
|
||||
})
|
||||
|
||||
// --- Cache
|
||||
|
||||
exports.Cache = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize memory store and start reaper.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - dataStore constructor name of cache data store, defaults to Store.Memory
|
||||
* - lifetime lifetime of cache in milliseconds, defaults to one day
|
||||
* - reapInterval, reapEvery interval in milliseconds in which to reap old caches, defaults to one hour
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
this.merge(options || {})
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
Request.include({ cache: this.store })
|
||||
this.startReaper()
|
||||
},
|
||||
|
||||
/**
|
||||
* Start reaper.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
startReaper: function(callbac) {
|
||||
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(' '))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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,11 @@ exports.parseCookie = function(cookie) {
|
||||
|
||||
exports.compileCookie = function(name, val, options) {
|
||||
if (!options) return name + '=' + val
|
||||
return name + '=' + val + '; ' + $(options).map(function(val, key){
|
||||
return name + '=' + val + '; ' + options.map(function(val, key){
|
||||
if (val instanceof Date)
|
||||
val = val.toString()
|
||||
.replace(/^(\w+)/, '$1,')
|
||||
.replace(/(\w+) (\d+) (\d+)/, '$2-$1-$3')
|
||||
.replace(/GMT.*$/, 'GMT')
|
||||
val = val.toGMTString()
|
||||
return val === true ? key : key + '=' + val
|
||||
}).toArray().join('; ')
|
||||
}).join('; ')
|
||||
}
|
||||
|
||||
// --- Cookie
|
||||
@@ -56,12 +59,12 @@ exports.Cookie = Plugin.extend({
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - path: Cookie path, defaults to '/'
|
||||
* - domain: Tail matched domain name such as 'vision-media.ca' or 'blog.vision-media.ca' etc
|
||||
* - expires: Date object converted to 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'
|
||||
* - path Cookie path, defaults to '/'
|
||||
* - domain Tail matched domain name such as 'vision-media.ca' or 'blog.vision-media.ca' etc
|
||||
* - expires Date object converted to 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'
|
||||
* when undefined the cookie will last the duration of a the
|
||||
* client's session.
|
||||
* - secure: When true the cookie will be sent by the client only when transfering data via HTTPS
|
||||
* - secure When true the cookie will be sent by the client only when transfering data via HTTPS
|
||||
* - httpOnly When true the cookie will be sent to the server only and will not be accessable via
|
||||
* client-side scripting.
|
||||
*
|
||||
@@ -73,6 +76,8 @@ exports.Cookie = Plugin.extend({
|
||||
*/
|
||||
|
||||
cookie: function(name, val, options) {
|
||||
options = options || {}
|
||||
options.path = options.path || '/'
|
||||
return val ?
|
||||
this.response.cookies.push(exports.compileCookie(name, val, options)) :
|
||||
this.cookies[name]
|
||||
@@ -86,13 +91,14 @@ exports.Cookie = Plugin.extend({
|
||||
on: {
|
||||
|
||||
/**
|
||||
* Parser request cookie data.
|
||||
* Parse request cookie data.
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
event.request.response.cookies = []
|
||||
if (event.request.headers.cookie)
|
||||
event.request.cookies = exports.parseCookie(event.request.headers.cookie)
|
||||
event.request.cookies = event.request.headers.cookie
|
||||
? exports.parseCookie(event.request.headers.cookie)
|
||||
: {}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -100,8 +106,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: '))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
// 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: {
|
||||
|
||||
/**
|
||||
* Initialize extensions.
|
||||
*/
|
||||
|
||||
init: function(){
|
||||
Request.include({
|
||||
|
||||
/**
|
||||
* Get / set flash _key_ and _val_.
|
||||
*
|
||||
* When a flash _key_ and _val_ are present,
|
||||
* 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')
|
||||
* this.flash('info', 'email received')
|
||||
* this.flash('info')
|
||||
* // => ['email sent', 'email received']
|
||||
*
|
||||
* this.flash('info')
|
||||
* // => null
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @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
|
||||
else if (key) {
|
||||
var vals = this.session.flash[key]
|
||||
delete this.session.flash[key]
|
||||
if (vals.length) return vals
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,29 +1,32 @@
|
||||
|
||||
// Express - Hooks - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var before = [],
|
||||
after = []
|
||||
/**
|
||||
* 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) {
|
||||
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) {
|
||||
after.push(fn)
|
||||
exports.after = function(callback) {
|
||||
exports.callbacks.after.push(callback)
|
||||
}
|
||||
|
||||
// --- Hooks
|
||||
@@ -36,7 +39,10 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
process.mixin(GLOBAL, exports)
|
||||
global.merge({
|
||||
before: exports.before,
|
||||
after: exports.after
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -49,8 +55,8 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
$(before).each(function(fn){
|
||||
fn.call(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) {
|
||||
$(after).each(function(fn){
|
||||
fn.call(event.request)
|
||||
exports.callbacks.after.each(function(callback){
|
||||
callback.call(event.request, event.request)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
|
||||
// Express - Logger - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var sys = require('sys')
|
||||
|
||||
/**
|
||||
* Log formats
|
||||
*/
|
||||
|
||||
var formats = {
|
||||
common: function(event, start) {
|
||||
printf('%s - - [%s] "%s %s HTTP/%d.%d" %s %d %0.3f',
|
||||
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
|
||||
* 'plot' outputs request duration in milliseconds only
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
this.merge(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,25 +0,0 @@
|
||||
|
||||
// Express - Profiler - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
exports.Profiler = Plugin.extend({
|
||||
on: {
|
||||
|
||||
/**
|
||||
* Start timer.
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
this.start = Number(new Date)
|
||||
},
|
||||
|
||||
/**
|
||||
* Output duration.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
puts(event.request.method + ' ' +
|
||||
event.request.url.pathname + ': ' +
|
||||
(Number(new Date) - this.start) + ' ms')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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,7 +40,7 @@ exports.Redirect = Plugin.extend({
|
||||
*/
|
||||
|
||||
redirect: function(url, code) {
|
||||
if (url == 'back' || url == 'home') url = this[url]
|
||||
if (url === 'back' || url === 'home') url = this[url]
|
||||
this.header('location', url)
|
||||
this.halt(code || 303)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
|
||||
// Express - Session - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('express/request').Request,
|
||||
utils = require('express/utils')
|
||||
|
||||
// --- Session
|
||||
|
||||
exports.Base = new Class({
|
||||
|
||||
/**
|
||||
* Initialize session _sid_.
|
||||
*/
|
||||
|
||||
constructor: function(sid) {
|
||||
this.id = sid
|
||||
this.touch()
|
||||
},
|
||||
|
||||
/**
|
||||
* Update last access time.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
touch: function() {
|
||||
this.lastAccess = Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store
|
||||
|
||||
exports.Store = new Class({
|
||||
|
||||
/**
|
||||
* Convert to '[NAME Store]'.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toString: function() {
|
||||
return '[' + this.name + ' Store]'
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store.Memory
|
||||
|
||||
exports.Store.Memory = exports.Store.extend({
|
||||
|
||||
/**
|
||||
* Datastore name.
|
||||
*/
|
||||
|
||||
name: 'Memory',
|
||||
|
||||
/**
|
||||
* Initialize in-memory session store.
|
||||
*/
|
||||
|
||||
constructor: function() {
|
||||
this.store = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch session with the given _sid_ or
|
||||
* a new Session is created.
|
||||
*
|
||||
* @param {number} sid
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
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, callback) {
|
||||
this.store[session.id] = session
|
||||
if (callback) callback(null, session)
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all sessions.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
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, callback) {
|
||||
delete this.store[sid]
|
||||
if (callback) callback(sid)
|
||||
},
|
||||
|
||||
/**
|
||||
* Pass the number of sessions currently stored.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
length: function(callback) {
|
||||
callback(null, this.store.values.length)
|
||||
},
|
||||
|
||||
/**
|
||||
* Reap sessions older than _ms_.
|
||||
*
|
||||
* @param {int} ms
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms, callback) {
|
||||
var threshold = +new Date(Date.now() - ms)
|
||||
this.store.each(function(session, sid){
|
||||
if (session.lastAccess < threshold)
|
||||
this.destroy(sid)
|
||||
}, this)
|
||||
if (callback) callback()
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and passes a shiny new session.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
generate: function(callback) {
|
||||
callback(null, new exports.Base(utils.uid()))
|
||||
}
|
||||
})
|
||||
|
||||
// --- Session
|
||||
|
||||
exports.Session = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize memory store and start reaper.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - 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) {
|
||||
this.cookie = {}
|
||||
this.merge(options || {})
|
||||
this.cookie.httpOnly = true
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
this.startReaper()
|
||||
},
|
||||
|
||||
/**
|
||||
* Start reaper.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
startReaper: function() {
|
||||
setInterval(function(self) {
|
||||
self.store.reap(self.lifetime || (1).day)
|
||||
}, this.reapInterval || this.reapEvery || (1).hour, this)
|
||||
}
|
||||
},
|
||||
|
||||
// --- Events
|
||||
|
||||
on: {
|
||||
|
||||
/**
|
||||
* Create session id when not found; delegate to store.
|
||||
*/
|
||||
|
||||
request: function(event, callback) {
|
||||
var sid = event.request.cookie('sid')
|
||||
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, callback) {
|
||||
if (event.request.session)
|
||||
return exports.Session.store.commit(event.request.session, callback),
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
// 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')
|
||||
|
||||
// --- File
|
||||
|
||||
exports.File = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with file _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
constructor: function(path) {
|
||||
this.path = path
|
||||
if (path.indexOf('..') != -1)
|
||||
Error.raise('InvalidPathError', "`" + path + "' is not a valid 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
|
||||
*/
|
||||
|
||||
sendTo: function(request) {
|
||||
var file = this.path
|
||||
function sendFromDisc() {
|
||||
path.exists(file, function(exists){
|
||||
if (!exists) return request.halt()
|
||||
fs.stat(file, function(err, stats){
|
||||
if (err) throw err
|
||||
if (!stats.isFile()) return request.halt()
|
||||
fs.readFile(file, 'binary', function(err, content){
|
||||
if (err) throw err
|
||||
request.contentType(file)
|
||||
if (set('cache static files'))
|
||||
request.cache.set('static:' + file, { type: file, content: content })
|
||||
request.halt(200, content, 'binary')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
if (set('cache static files'))
|
||||
request.cache.get('static:' + file, function(cache){
|
||||
if (cache)
|
||||
request.contentType(cache.type),
|
||||
request.halt(200, cache.content, 'binary')
|
||||
else
|
||||
sendFromDisc()
|
||||
})
|
||||
else
|
||||
sendFromDisc()
|
||||
}
|
||||
})
|
||||
|
||||
// --- 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_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sendfile: function(path) {
|
||||
(new exports.File(path)).sendTo(this)
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static _file_ as an attachment.
|
||||
* The basename of _file_ is used as the attachment filename.
|
||||
*
|
||||
* @param {string} file
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
download: function(file) {
|
||||
return this.attachment(path.basename(file)).sendfile(file)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
+118
-43
@@ -1,21 +1,41 @@
|
||||
|
||||
// Express - View - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var posix = require('posix')
|
||||
|
||||
/**
|
||||
* Template content cache.
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var cache = {}
|
||||
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('support/ejs/ejs'),
|
||||
haml: require('support/haml/lib/haml')
|
||||
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)
|
||||
fs.readdirSync(dir).each(function(file){
|
||||
file = dir + '/' + file
|
||||
if (!fs.statSync(file).isFile()) return
|
||||
cache[type][file] = fs.readFileSync(file)
|
||||
})
|
||||
}
|
||||
|
||||
// --- View
|
||||
@@ -31,64 +51,119 @@ 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:
|
||||
*
|
||||
* - layout: Whether or not to use a layout. Defaults to true
|
||||
* - context: Most engines support an evaluation context (the 'this' keyword)
|
||||
* - 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
|
||||
* @settings 'views', 'cache views'
|
||||
* @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 = extname(path),
|
||||
layout = options.layout === undefined ? true : options.layout
|
||||
self.contentType(ext)
|
||||
function render(content) {
|
||||
content = engine[type].render(content, options)
|
||||
if (layout)
|
||||
self.render('layout.' + type + '.' + ext, process.mixin(options, {
|
||||
layout: false,
|
||||
locals: {
|
||||
body: content
|
||||
}
|
||||
}))
|
||||
else
|
||||
self.halt(200, content)
|
||||
}
|
||||
if (set('cache views') && cache[view])
|
||||
render(cache[view])
|
||||
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
|
||||
content = (engines[engine] = engines[engine] || require(engine)).render(content, options)
|
||||
if (type === 'views') this.contentType(contentType)
|
||||
if (layout)
|
||||
this.render([layout, contentType, engine].join('.'), options.mergeDeep({
|
||||
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){
|
||||
render(cache[view] = content)
|
||||
}).addErrback(function(e){
|
||||
throw e
|
||||
})
|
||||
this.halt(200, content)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+179
-94
@@ -5,29 +5,14 @@
|
||||
* 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'
|
||||
}
|
||||
})
|
||||
|
||||
// --- Helpers
|
||||
|
||||
/**
|
||||
@@ -45,7 +30,7 @@ exports.normalizePath = function(path) {
|
||||
|
||||
// --- Request
|
||||
|
||||
exports.Request = Class({
|
||||
exports.Request = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with node's _request_ and _response_ objects.
|
||||
@@ -60,19 +45,18 @@ exports.Request = Class({
|
||||
* @param {object} response
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(request, response) {
|
||||
process.mixin(true, this, request)
|
||||
|
||||
constructor: function(request, response) {
|
||||
this.merge(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 = {
|
||||
get: this.url.query ? queryString.parseQuery(this.url.query) : {},
|
||||
post: {},
|
||||
path: {}
|
||||
}
|
||||
this.plugins = $(Express.plugins).map(function(plugin){
|
||||
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){
|
||||
return new plugin.klass(plugin.options)
|
||||
})
|
||||
},
|
||||
@@ -88,17 +72,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.lowercase] = 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,35 +90,59 @@ 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]
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if Accept header includes the mime type
|
||||
* for the given _path_, which calls mime().
|
||||
* for any of the given paths, which calls mime.type().
|
||||
*
|
||||
* When no Accept header is present true will be
|
||||
* returned as stated in the HTTP specification.
|
||||
*
|
||||
* @param {string} path
|
||||
* Example:
|
||||
*
|
||||
* this.accepts('png')
|
||||
* this.accepts('png', 'jpg', 'gif')
|
||||
* this.accepts('image.png')
|
||||
* this.accepts('path/to/image.png')
|
||||
*
|
||||
* @param {mixed} ...
|
||||
* @return {bool}
|
||||
* @see mime()
|
||||
* @api public
|
||||
*/
|
||||
|
||||
accepts: function(path) {
|
||||
return this.header('accept') ?
|
||||
this.header('accept').indexOf(mime(path)) !== -1 :
|
||||
true
|
||||
accepts: function() {
|
||||
var accept = this.header('accept')
|
||||
if (!accept) return true
|
||||
return 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
|
||||
*/
|
||||
|
||||
@@ -144,26 +152,30 @@ exports.Request = Class({
|
||||
},
|
||||
|
||||
/**
|
||||
* Immediately respond with response _code_, _body_ and optional _encoding_.
|
||||
* Immediately respond with response _code_, _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.
|
||||
* Optionally an _encoding_ may be passed, followed by
|
||||
* a _callback_ function.
|
||||
*
|
||||
* @param {int} code
|
||||
* @param {string} body
|
||||
* @param {string} encoding
|
||||
* @param {function} callback
|
||||
* @see statusBodies
|
||||
* @api public
|
||||
*/
|
||||
|
||||
halt: function(code, body, encoding) {
|
||||
halt: function(code, body, encoding, callback) {
|
||||
if (encoding instanceof Function)
|
||||
callback = encoding,
|
||||
encoding = null
|
||||
this.status(code = code || 404)
|
||||
if (body = body || statusBodies[code])
|
||||
return this.respond(body, encoding)
|
||||
throw new InvalidStatusCode(code)
|
||||
return this.respond(body, encoding, callback)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -175,64 +187,85 @@ exports.Request = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
respond: function(body, encoding) {
|
||||
respond: function(body, encoding, callback) {
|
||||
var self = this
|
||||
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()
|
||||
this.trigger('response', function(err) {
|
||||
if (err)
|
||||
if (callback !== undefined) callback(err)
|
||||
else self.error(err)
|
||||
self.response.writeHeader(self.response.status, self.response.headers)
|
||||
self.response.write(self.response.body, encoding)
|
||||
self.response.end()
|
||||
}, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Content-Type header to the mime type
|
||||
* for the given _path_, which calls mime().
|
||||
* for the given _path_, which calls mime.type().
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {Request}
|
||||
* @see mime()
|
||||
* @api public
|
||||
*/
|
||||
|
||||
contentType: function(path) {
|
||||
this.header('content-type', mime(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 = data || {}
|
||||
data.merge({ request: this, response: this.response })
|
||||
var self = this,
|
||||
complete = 0,
|
||||
total = this.plugins.length,
|
||||
plugins = reverse
|
||||
? self.plugins.reverse()
|
||||
: self.plugins
|
||||
;(function next(err) {
|
||||
if (err || complete === total)
|
||||
callback(err)
|
||||
else {
|
||||
if (plugins.at(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_.
|
||||
@@ -243,22 +276,74 @@ exports.Request = Class({
|
||||
*/
|
||||
|
||||
attachment: function(path) {
|
||||
this.header('content-disposition', path ?
|
||||
'attachment; filename="' + path + '"' :
|
||||
'attachment')
|
||||
this.header('content-disposition', path
|
||||
? 'attachment; filename="' + path + '"'
|
||||
: '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.halt(500, 'your app sucks!')
|
||||
* })
|
||||
*
|
||||
* When "html" is accepted, and "show exceptions" is enabled
|
||||
* the show-exceptions page will be shown. 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).
|
||||
*
|
||||
* @param {Error} err
|
||||
* @return {Request}
|
||||
* @settings 'throw exceptions', 'show exceptions'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
error: function(err) {
|
||||
if (Express.error)
|
||||
Express.error.call(this, err)
|
||||
else if (this.accepts('html') && set('show exceptions'))
|
||||
this.halt(500, showExceptions.render(this, err))
|
||||
else
|
||||
this.halt(500)
|
||||
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.halt(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.halt(404, notFound.render(this))
|
||||
else
|
||||
this.halt()
|
||||
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)
|
||||
this.mergeDeep(options)
|
||||
}
|
||||
})
|
||||
|
||||
// --- MockResponse
|
||||
|
||||
var MockResponse = Class({
|
||||
var MockResponse = new Class({
|
||||
|
||||
/**
|
||||
* Store _code_ and _headers_.
|
||||
*/
|
||||
|
||||
sendHeader: function(code, headers) {
|
||||
writeHeader: 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,56 +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
|
||||
*
|
||||
* @param {Request} request
|
||||
* @api public
|
||||
*/
|
||||
|
||||
send: function(request) {
|
||||
var file = this.path
|
||||
path.exists(file, function(exists){
|
||||
if (!exists) request.halt()
|
||||
posix.stat(file).addCallback(function(stats){
|
||||
if (!stats.isFile()) request.halt()
|
||||
posix.cat(file, 'binary').addCallback(function(content){
|
||||
request.contentType(file)
|
||||
request.halt(200, content, 'binary')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
// Express - Helpers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var queryString = require('querystring')
|
||||
|
||||
/**
|
||||
* JSON aliases.
|
||||
*/
|
||||
|
||||
JSON.encode = JSON.stringify
|
||||
JSON.decode = JSON.parse
|
||||
|
||||
/**
|
||||
* Return a unique identifier.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.uid = function() {
|
||||
var uid = ''
|
||||
for (var n = 4; n; --n)
|
||||
uid += (Math.abs((Math.random() * 0xFFFFFFF) | 0)).toString(16)
|
||||
return uid
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters in _html_.
|
||||
*
|
||||
* @param {string} html
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.escape = function(html) {
|
||||
return String(html)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge param _key_ and _val_ into _params_. Key
|
||||
* should be a query string key such as 'user[name]',
|
||||
* and _val_ is it's associated object. The root _params_
|
||||
* object is returned.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {mixed} val
|
||||
* @return {hash}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.mergeParam = function(key, val, params) {
|
||||
var orig = params,
|
||||
keys = key.trim().match(/\w+/g),
|
||||
array = /\[\]$/.test(key)
|
||||
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]
|
||||
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 +0,0 @@
|
||||
/* 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
|
||||
*/
|
||||
|
||||
(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;
|
||||
Submódulo
+1
Submodule lib/support/ext added at e90bcc0190
+1
-1
Submodule lib/support/haml updated: 53fad8e082...eaf43ac475
Submodule lib/support/js-oo deleted from 1f94bd8979
Submódulo
+1
Submodule lib/support/multipart added at 0b77a68582
Submódulo
+1
Submodule lib/support/sass added at fac9a896b3
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "0.9.0",
|
||||
"keywords": ["framework", "sinatra", "web", "rest", "restful"],
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "git submodule update --init",
|
||||
"test": "make test"
|
||||
},
|
||||
"engines": { "node": ">= 0.1.30" }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
name: Express
|
||||
description: Sinatra inspired web development framework
|
||||
version: 0.9.0
|
||||
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
|
||||
@@ -25,14 +25,15 @@ JSpec
|
||||
// --- Matchers
|
||||
|
||||
matchers : {
|
||||
have_tag : "jQuery(expected, actual).length == 1",
|
||||
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)",
|
||||
|
||||
|
||||
+210
-107
@@ -4,12 +4,13 @@
|
||||
;(function(){
|
||||
|
||||
JSpec = {
|
||||
version : '3.1.0',
|
||||
version : '4.0.0',
|
||||
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) {
|
||||
@@ -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.
|
||||
@@ -1046,7 +1124,7 @@
|
||||
*/
|
||||
|
||||
expect : function(actual) {
|
||||
assert = function(matcher, args, negate) {
|
||||
function assert(matcher, args, negate) {
|
||||
var expected = toArray(args, 1)
|
||||
matcher.negate = negate
|
||||
assertion = new JSpec.Assertion(matcher, actual, expected, negate)
|
||||
@@ -1056,11 +1134,11 @@
|
||||
return assertion.result
|
||||
}
|
||||
|
||||
to = function(matcher) {
|
||||
function to(matcher) {
|
||||
return assert(matcher, arguments, false)
|
||||
}
|
||||
|
||||
not_to = function(matcher) {
|
||||
function not_to(matcher) {
|
||||
return assert(matcher, arguments, true)
|
||||
}
|
||||
|
||||
@@ -1301,12 +1379,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 +1426,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 +1447,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 +1512,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.
|
||||
@@ -1620,7 +1716,7 @@
|
||||
return request.responseText
|
||||
}
|
||||
else
|
||||
error("failed to load `" + file + "'")
|
||||
throw new Error("failed to load `" + file + "'")
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1637,12 +1733,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() }
|
||||
@@ -1692,7 +1795,7 @@
|
||||
case Number:
|
||||
case RegExp:
|
||||
case Function:
|
||||
state = actual.toString().match(arg.toString())
|
||||
state = actual.toString().indexOf(arg) !== -1
|
||||
break
|
||||
|
||||
case Object:
|
||||
@@ -1770,4 +1873,4 @@
|
||||
}
|
||||
})
|
||||
|
||||
})()
|
||||
})()
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
(function(){
|
||||
|
||||
var lastRequest
|
||||
|
||||
// --- Original XMLHttpRequest
|
||||
|
||||
var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
|
||||
|
||||
+10
-27
@@ -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){
|
||||
@@ -27,39 +18,31 @@ specs = {
|
||||
independant: [
|
||||
'core',
|
||||
'routing',
|
||||
'helpers',
|
||||
'utils',
|
||||
'request',
|
||||
'mime',
|
||||
'static',
|
||||
'collection',
|
||||
'plugins',
|
||||
'plugins.cache',
|
||||
'plugins.view',
|
||||
'plugins.common-logger',
|
||||
'plugins.content-length',
|
||||
'plugins.method-override',
|
||||
'plugins.body-decoder',
|
||||
'plugins.redirect',
|
||||
'plugins.hooks',
|
||||
'plugins.cookie',
|
||||
],
|
||||
dependant: [
|
||||
'element-collection'
|
||||
]
|
||||
'plugins.session',
|
||||
'plugins.flash',
|
||||
'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]])
|
||||
}
|
||||
|
||||
Express.environment = 'test'
|
||||
JSpec.run({ reporter: JSpec.reporters.Terminal, failuresOnly: true }).report()
|
||||
@@ -1,472 +0,0 @@
|
||||
|
||||
process.mixin(require('express/collection'))
|
||||
|
||||
describe 'Express'
|
||||
describe 'Collection'
|
||||
describe '$(array)'
|
||||
it 'should return a Collection'
|
||||
$(['foo', 'bar']).should.be_an_instance_of Collection
|
||||
end
|
||||
end
|
||||
|
||||
describe '$(object)'
|
||||
it 'should return a Collection'
|
||||
$({ foo: 'bar' }).should.be_an_instance_of Collection
|
||||
end
|
||||
end
|
||||
|
||||
describe '$(Collection)'
|
||||
it 'should return the collection passed'
|
||||
var collection = $(['foo'])
|
||||
$(collection).should.equal collection
|
||||
end
|
||||
end
|
||||
|
||||
describe 'shorthand expressions'
|
||||
describe 'with 3 or less chars'
|
||||
it 'should be considered binary operator between a / b'
|
||||
$(5..1).sort('-').toArray().should.eql 1..5
|
||||
$(5..1).reduce(0, '+').should.eql 15
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with over 3 chars'
|
||||
it 'should be considered a return expression'
|
||||
$(5..1).sort('a - b').toArray().should.eql 1..5
|
||||
$(5..1).reduce(0, 'a + b').should.eql 15
|
||||
end
|
||||
|
||||
it 'should consider a single word a property on a'
|
||||
$(['foo', 'foobar']).map('length').toArray().should.eql [3, 6]
|
||||
end
|
||||
|
||||
it 'should consider a single function a method on a'
|
||||
$(['foo', 'foobar']).map('charAt(0)').toArray().should.eql ['f', 'f']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#at()'
|
||||
it 'should return the value at the given index'
|
||||
$(['foo', 'bar']).at(0).should.eql 'foo'
|
||||
$(['foo', 'bar']).at(1).should.eql 'bar'
|
||||
$(['foo', 'bar']).at(2).should.be_null
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
$({ foo: 'bar', baz: 'raz' }).at(0).should.eql 'bar'
|
||||
$({ foo: 'bar', baz: 'raz' }).at(1).should.eql 'raz'
|
||||
$({ foo: 'bar', baz: 'raz' }).at(2).should.be_null
|
||||
end
|
||||
end
|
||||
|
||||
describe '#each()'
|
||||
it 'should iterate passing index and value'
|
||||
var result = []
|
||||
$(['foo', 'bar']).each(function(val, i){
|
||||
result.push(i, val)
|
||||
})
|
||||
result.should.eql [0, 'foo', 1, 'bar']
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
var result = []
|
||||
$({ foo: 'bar', baz: 'raz' }).each(function(val, key){
|
||||
result.push(key, val)
|
||||
})
|
||||
result.should.eql ['foo', 'bar', 'baz', 'raz']
|
||||
end
|
||||
|
||||
it 'should return the collection'
|
||||
$([]).each(function(){}).should.be_an_instance_of Collection
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reduce()'
|
||||
it 'should iterate with memo object'
|
||||
var sum = $([1,2,3]).reduce(0, function(sum, n){ return sum + n })
|
||||
sum.should.eql 6
|
||||
end
|
||||
|
||||
it 'should allow shorthand expressions'
|
||||
$([1,2,3]).reduce(0, '+').should.eql 6
|
||||
$([1,2,3]).reduce(0, 'a + b').should.eql 6
|
||||
end
|
||||
end
|
||||
|
||||
describe '#map()'
|
||||
it 'should iterate collecting results into a new collection'
|
||||
var collection = $(['foo', 'bar']).map(function(val){ return val.toUpperCase() })
|
||||
collection.at(0).should.eql 'FOO'
|
||||
collection.at(1).should.eql 'BAR'
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
var collection = $({ foo: 'bar', baz: 'raz' }).map(function(val){ return val.toUpperCase() })
|
||||
collection.at(0).should.eql 'BAR'
|
||||
collection.at(1).should.eql 'RAZ'
|
||||
end
|
||||
|
||||
it 'should allow shorthand expressions'
|
||||
$(['foo', 'bar']).map('a.toUpperCase()').toArray().should.eql ['FOO', 'BAR']
|
||||
end
|
||||
end
|
||||
|
||||
describe '#first()'
|
||||
it 'should return the first value'
|
||||
$(['foo']).first().should.eql 'foo'
|
||||
end
|
||||
|
||||
it 'should return the first n values'
|
||||
$([5,4,3,2,1]).first(2).at(0).should.eql 5
|
||||
$([5,4,3,2,1]).first(2).at(1).should.eql 4
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
$({ foo: 'bar' }).first().should.eql 'bar'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#last()'
|
||||
it 'should return the last value'
|
||||
$(['foo', 'bar']).last().should.eql 'bar'
|
||||
end
|
||||
|
||||
it 'should return the last n values'
|
||||
$([5,4,3,2,1]).last(2).at(0).should.eql 2
|
||||
$([5,4,3,2,1]).last(2).at(1).should.eql 1
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
$({ a: 'foo', b: 'bar' }).last(2).at(0).should.eql 'foo'
|
||||
$({ a: 'foo', b: 'bar' }).last(2).at(1).should.eql 'bar'
|
||||
$({ a: 'foo', b: 'bar' }).last().should.eql 'bar'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#drop()'
|
||||
it 'should drop the first n values'
|
||||
$(1..5).drop(2).arr.should.eql 3..5
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find()'
|
||||
it 'should return the value of the first match'
|
||||
var result = $(['foo', 'bar']).find(function(val){ return val.charAt(0) == 'b' })
|
||||
result.should.eql 'bar'
|
||||
end
|
||||
|
||||
it 'should return null when nothing matches'
|
||||
var result = $(['foo', 'bar']).find(function(val){ return val.charAt(0) == 'a' })
|
||||
result.should.be_null
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
var result = $({ foo: 'bar', baz: 'raz' }).find(function(val, key){
|
||||
return val.charAt(0) == 'r'
|
||||
})
|
||||
result.should.eql 'raz'
|
||||
end
|
||||
|
||||
it 'should allow shorthand expressions'
|
||||
$(['foo', 'bar']).find("a.charAt(0) == 'b'").should.eql 'bar'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#all()'
|
||||
it 'should return true when all evaluate to true'
|
||||
$(['foo', 'foobar']).all(function(val){ return val.charAt(0) == 'f' }).should.be_true
|
||||
end
|
||||
|
||||
it 'should return false when any evaluate to false'
|
||||
$(['foo', 'bar', 'foobar']).all(function(val){ return val.charAt(0) == 'f' }).should.be_false
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
$({ a: 'foo', b: 'foobar' }).all(function(val){ return val.charAt(0) == 'f' }).should.be_true
|
||||
end
|
||||
|
||||
it 'should allow shorthand expressions'
|
||||
$(['foo', 'bar']).all('a.length > 2').should.be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#any()'
|
||||
it 'should return true when found'
|
||||
$(['foo', 'bar']).any(function(val){ return val.charAt(0) == 'b' }).should.be_true
|
||||
end
|
||||
|
||||
it 'should return false when not found'
|
||||
$(['foo', 'bar']).any(function(val){ return val.charAt(0) == 'r' }).should.be_false
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
$({ foo: 'bar' }).any(function(val){ return val.charAt(0) == 'b' }).should.be_true
|
||||
$({ foo: 'bar' }).any(function(val){ return val.charAt(0) == 'c' }).should.be_false
|
||||
end
|
||||
|
||||
it 'should allow shorthand expressions'
|
||||
$(['foo', 'bar']).any('a.length > 2').should.be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#select()'
|
||||
it 'should return values which evaluate to true'
|
||||
var result = $([1,2,3,4,5]).select(function(n){ return n % 2 })
|
||||
result.at(0).should.eql 1
|
||||
result.at(1).should.eql 3
|
||||
result.at(2).should.eql 5
|
||||
end
|
||||
|
||||
it 'should return a Collection'
|
||||
var result = $([1,2,3,4,5]).select(function(n){ return n % 2 })
|
||||
result.should.be_an_instance_of Collection
|
||||
end
|
||||
|
||||
it 'should allow shorthand expressions'
|
||||
$([1,2,3,4,5]).select('a % 2').toArray().should.eql [1,3,5]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reject()'
|
||||
it 'should return values which evaluate to false'
|
||||
var result = $([1,2,3,4,5,6]).reject(function(n){ return n % 2 })
|
||||
result.at(0).should.eql 2
|
||||
result.at(1).should.eql 4
|
||||
result.at(2).should.eql 6
|
||||
end
|
||||
|
||||
it 'should return a Collection'
|
||||
var result = $([1,2,3,4,5]).reject(function(n){ return n % 2 })
|
||||
result.should.be_an_instance_of Collection
|
||||
end
|
||||
|
||||
it 'should allow shorthand expressions'
|
||||
$(['foo', 'bar']).reject('a.charAt(0) == "b"').toArray().should.eql ['foo']
|
||||
end
|
||||
end
|
||||
|
||||
describe '#slice()'
|
||||
it 'should return a slice of values'
|
||||
var collection = $(['foo', 'bar', 'baz']).slice(1, 3)
|
||||
collection.at(0).should.eql 'bar'
|
||||
collection.at(1).should.eql 'baz'
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
var collection = $({ foo: 1, bar: 2, baz: 3, raz: 4 }).slice(1, 3)
|
||||
collection.at(0).should.eql 2
|
||||
collection.at(1).should.eql 3
|
||||
end
|
||||
end
|
||||
|
||||
describe '#grep()'
|
||||
it 'should select values matching the regular expression passed'
|
||||
var result = $(['foo', 'bar', 'foobar', 'baz']).grep(/foo(bar)?/)
|
||||
result.at(0).should.eql 'foo'
|
||||
result.at(1).should.eql 'foobar'
|
||||
result.at(2).should.be_null
|
||||
end
|
||||
end
|
||||
|
||||
describe '#keys()'
|
||||
it 'should return indices when array-like'
|
||||
$(['foo', 'bar']).keys().at(0).should.eql '0'
|
||||
$(['foo', 'bar']).keys().at(1).should.eql '1'
|
||||
end
|
||||
|
||||
it 'should return keys when an object'
|
||||
$({ foo: 'bar', baz: 'raz' }).keys().at(0).should.eql 'foo'
|
||||
$({ foo: 'bar', baz: 'raz' }).keys().at(1).should.eql 'baz'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#toArray()'
|
||||
it 'should return an array'
|
||||
$(['foo', 'bar']).keys().toArray().should.eql ['0', '1']
|
||||
end
|
||||
|
||||
it 'should work on nested collections'
|
||||
$([$(['foo']), $(['bar'])]).toArray().should.eql [['foo'], ['bar']]
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
$({ foo: 'bar', baz: { name: 'wahoo' }}).toArray().should.eql ['bar', { name: 'wahoo' }]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#min()'
|
||||
it 'should return the min value'
|
||||
$([4,5,2,3,62]).min().should.eql 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#max()'
|
||||
it 'should return the max value'
|
||||
$([3,5,2,3,43,2]).max().should.eql 43
|
||||
end
|
||||
end
|
||||
|
||||
describe '#length()'
|
||||
it 'should work with arrays'
|
||||
$([1,2,3]).length().should.eql 3
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
$({ a: 'b', c: 'd', e: 'f' }).length().should.eql 3
|
||||
end
|
||||
end
|
||||
|
||||
describe '#chunk()'
|
||||
it 'should group into chunks of the given size'
|
||||
$([1,1,2,2,3,3]).chunk(2).toArray().should.eql [[1,1],[2,2],[3,3]]
|
||||
$([1,1,2,2,3,3]).chunk(4).toArray().should.eql [[1,1,2,2],[3,3]]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sum()'
|
||||
it 'should return the sum of the numeric values'
|
||||
$([1,2,3]).sum().should.eql 6
|
||||
end
|
||||
end
|
||||
|
||||
describe '#avg()'
|
||||
it 'should return the average of numeric values'
|
||||
$([3,1]).avg().should.eql 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#merge()'
|
||||
it 'should merge two array collections'
|
||||
var a = $([1,2,3])
|
||||
var b = $([4,5,6])
|
||||
a.merge(b).toArray().should.eql [1,2,3,4,5,6]
|
||||
|
||||
var a = $([1,2,3])
|
||||
var b = [4,5,6]
|
||||
a.merge(b).toArray().should.eql [1,2,3,4,5,6]
|
||||
end
|
||||
|
||||
it 'should merge two object collections'
|
||||
var a = $({ a: 'b' })
|
||||
var b = $({ c: 'd' })
|
||||
a.merge(b).arr.should.eql { a: 'b', c: 'd' }
|
||||
|
||||
var a = $({ a: 'b' })
|
||||
var b = { c: 'd' }
|
||||
a.merge(b).arr.should.eql { a: 'b', c: 'd' }
|
||||
end
|
||||
|
||||
it 'should merge an array and object collection'
|
||||
var a = $([1,2])
|
||||
var b = $({ a: 'b', c: 'd' })
|
||||
a.merge(b).arr.should.eql [1, 2, 'b', 'd']
|
||||
|
||||
var a = $([1,2])
|
||||
var b = { a: 'b', c: 'd' }
|
||||
a.merge(b).arr.should.eql [1, 2, 'b', 'd']
|
||||
end
|
||||
|
||||
it 'should merge an object and array collection'
|
||||
var a = $({ a: 'b', c: 'd' })
|
||||
var b = $([1,2])
|
||||
a.merge(b).arr.should.eql { a: 'b', c: 'd', 0: 1, 1: 2 }
|
||||
|
||||
var a = $({ a: 'b', c: 'd' })
|
||||
var b = [1,2]
|
||||
a.merge(b).arr.should.eql { a: 'b', c: 'd', 0: 1, 1: 2 }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clone()'
|
||||
it 'should clone an array collection'
|
||||
var a = $([1,2,3])
|
||||
var b = a.clone()
|
||||
a.should.not.equal b
|
||||
b.arr.should.eql [1,2,3]
|
||||
end
|
||||
|
||||
it 'should clone an object collection'
|
||||
var a = $({ foo: 'bar' })
|
||||
var b = a.clone()
|
||||
a.should.not.equal b
|
||||
b.arr.should.eql { foo: 'bar' }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sample()'
|
||||
it 'should return a random value'
|
||||
Math.stub('random').and_return(0.1)
|
||||
$([1,2,3,4]).sample().should.eql 1
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reverse()'
|
||||
it 'should reverse a collection'
|
||||
$([1,2,3]).reverse().toArray().should.eql [3,2,1]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sort()'
|
||||
it 'should sort a collection'
|
||||
$([3,1,2]).sort().toArray().should.eql [1,2,3]
|
||||
end
|
||||
|
||||
it 'should sort with a function'
|
||||
$([3,1,2]).sort(function(a, b){ return b - a }).toArray().should.eql [3,2,1]
|
||||
end
|
||||
|
||||
it 'should allow shorthand expressions'
|
||||
$([3,1,2]).sort('-').toArray().should.eql [1,2,3]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#join()'
|
||||
it 'should join a collection with "" by default'
|
||||
$([1,2,3]).join().should.eql '123'
|
||||
end
|
||||
|
||||
it 'should join with an arbitrary string'
|
||||
$([1,2,3]).join(' ').should.eql '1 2 3'
|
||||
end
|
||||
|
||||
it 'should work with objects'
|
||||
$({ foo: 'bar', baz: 'raz' }).join(' ').should.eql 'bar raz'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#includes()'
|
||||
it 'should return true when the value is present'
|
||||
$([1,2,3]).includes(2).should.be_true
|
||||
end
|
||||
|
||||
it 'should return true when all values are present'
|
||||
$([1,2,3]).includes(2, 3).should.be_true
|
||||
end
|
||||
|
||||
it 'should return false when the value is not present'
|
||||
$([1,2,3]).includes(4).should.be_false
|
||||
end
|
||||
|
||||
it 'should return false when the any value is not present'
|
||||
$([1,2,3]).includes(1,2,4).should.be_false
|
||||
$([1,2,3]).includes(1,4,2).should.be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#toString()'
|
||||
it 'should output [Collection ...] for array'
|
||||
$([1,2,3]).toString().should.eql '[Collection 1,2,3]'
|
||||
end
|
||||
|
||||
it 'should output [Collection [object Object]] for object'
|
||||
$({ foo: "bar" }).toString().should.eql '[Collection [object Object]]'
|
||||
end
|
||||
|
||||
it 'should output [Collection [[Collection ...]]] for nested collections'
|
||||
$([$([1,2,3])]).toString().should.eql '[Collection [Collection 1,2,3]]'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1,83 +0,0 @@
|
||||
|
||||
process.mixin(require('express/collection'))
|
||||
process.mixin(require('express/element-collection'))
|
||||
|
||||
describe 'Express'
|
||||
describe 'ElementCollection'
|
||||
|
||||
describe '$("markup string")'
|
||||
it 'should return a ElementCollection'
|
||||
$('<p>foo</p>').should.be_an_instance_of Collection
|
||||
$('<p>foo</p>').should.be_an_instance_of ElementCollection
|
||||
end
|
||||
|
||||
it 'should wrap with <html><body>.. when not present'
|
||||
$('<p>foo</p>').at(0).name().should.eql 'html'
|
||||
end
|
||||
|
||||
it 'should not wrap with <html><body> when already present'
|
||||
$('<html><body></body></html>').at(0).name().should.eql 'html'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#name()'
|
||||
it 'should return the first elements name'
|
||||
$('<html><body></body></html>').name().should.eql 'html'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#xpath()'
|
||||
it 'should find children matching the given xpath'
|
||||
$('<li>1</li><li>2</li>').xpath('descendant-or-self::li').length().should.eql 2
|
||||
$('<li>1</li><li>2</li>').xpath('descendant-or-self::li').at(0).name().should.eql 'li'
|
||||
var items = $('<li><p>Foo</p></li><li><p>Bar</p></li>').xpath('descendant-or-self::li')
|
||||
items.xpath('descendant-or-self::p').length().should.eql 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#search()'
|
||||
it 'should find children matching the given css selector'
|
||||
$('<ul><li>foo</li><li>bar</li></ul>').search('ul > li:nth-child(2)').text().should.eql 'bar'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#children()'
|
||||
it 'should return children'
|
||||
$('<p><em>foo</em><strong>bar</strong></p>').children().length().should.eql 1
|
||||
$('<p><em>foo</em><strong>bar</strong></p>').xpath('descendant-or-self::p').children().length().should.eql 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parents()'
|
||||
it 'should return parents'
|
||||
$('<ul><li></li></ul><ul><li></li></ul>').xpath('descendant-or-self::li').parents().length().should.eql 2
|
||||
$('<ul><li></li></ul><ul><li></li></ul>').xpath("descendant-or-self::*/*[name() = 'ul' and (position() = 1)]/descendant::li").parents().length().should.eql 1
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parent()'
|
||||
it 'should return the first parent'
|
||||
$('<ul><li></li></ul><ul><li></li></ul>').xpath('descendant-or-self::li').parent().length().should.eql 1
|
||||
end
|
||||
end
|
||||
|
||||
describe '#text()'
|
||||
it 'should return an elements text'
|
||||
$('<p>foo bar</p>').text().should.eql 'foo bar'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#next()'
|
||||
it 'should return the next element'
|
||||
$('<em>foo</em><p>bar</p>').xpath('descendant-or-self::em').next().text().should.eql 'bar'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#prev()'
|
||||
it 'should return the previous element'
|
||||
$('<em>foo</em><p>bar</p>').xpath('descendant-or-self::p').prev().text().should.eql 'foo'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1,48 +0,0 @@
|
||||
|
||||
describe 'Express'
|
||||
describe 'toArray()'
|
||||
describe 'when given an array'
|
||||
it 'should return the array'
|
||||
toArray([1,2,3]).should.eql [1,2,3]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given an object with indexed values and length'
|
||||
it 'should return an array'
|
||||
var args = -{ return arguments }('foo', 'bar')
|
||||
toArray(args).should.eql ['foo', 'bar']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'escape()'
|
||||
it 'should escape html'
|
||||
escape('<p>this & that').should.eql '<p>this & that'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'extname()'
|
||||
it 'should return the a files extension'
|
||||
extname('image.png').should.eql 'png'
|
||||
extname('image.large.png').should.eql 'png'
|
||||
extname('/path/to/image.large.png').should.eql 'png'
|
||||
end
|
||||
|
||||
it 'should return null when not found'
|
||||
extname('path').should.be_null
|
||||
extname('/just/a/path').should.be_null
|
||||
end
|
||||
end
|
||||
|
||||
describe 'dirname()'
|
||||
it 'should return the directory path'
|
||||
dirname('/path/to/images/foo.bar.png').should.eql '/path/to/images'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'basename()'
|
||||
it 'should return a files basename'
|
||||
basename('foo/bar/baz.image.png').should.eql 'baz.image.png'
|
||||
end
|
||||
end
|
||||
end
|
||||
+30
-24
@@ -1,36 +1,42 @@
|
||||
|
||||
describe 'Express'
|
||||
before
|
||||
mime = require('express/mime')
|
||||
end
|
||||
|
||||
before_each
|
||||
reset()
|
||||
end
|
||||
|
||||
describe 'mime()'
|
||||
describe 'when given an extension with leading dot'
|
||||
it 'should return the associated mime type'
|
||||
mime('.png').should.eql 'image/png'
|
||||
describe 'mime'
|
||||
describe 'type()'
|
||||
describe 'when given an extension with leading dot'
|
||||
it 'should return the associated mime type'
|
||||
mime.type('.png').should.eql 'image/png'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given an extension without leading dot'
|
||||
it 'should return the associated mime type'
|
||||
mime('png').should.eql 'image/png'
|
||||
|
||||
describe 'when given an extension without leading dot'
|
||||
it 'should return the associated mime type'
|
||||
mime.type('png').should.eql 'image/png'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given a file path'
|
||||
it 'should return the associated mime type'
|
||||
mime('/path/to/an/image.png').should.eql 'image/png'
|
||||
|
||||
describe 'when given a file path'
|
||||
it 'should return the associated mime type'
|
||||
mime.type('/path/to/an/image.png').should.eql 'image/png'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given an unknown extension'
|
||||
it 'should default to the "default mime type" setting'
|
||||
set('default mime type', 'text/plain')
|
||||
mime('meow').should.eql 'text/plain'
|
||||
end
|
||||
|
||||
it 'should default to "application/octet-stream" otherwise'
|
||||
mime('meow').should.eql 'application/octet-stream'
|
||||
|
||||
describe 'when given an unknown extension'
|
||||
it 'should default to the "default mime type" setting'
|
||||
set('default mime type', 'text/plain')
|
||||
mime.type('meow').should.eql 'text/plain'
|
||||
end
|
||||
|
||||
it 'should default to "application/octet-stream" otherwise'
|
||||
mime.type('meow').should.eql 'application/octet-stream'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/cache').Cache)
|
||||
cache = require('express/plugins/cache')
|
||||
end
|
||||
|
||||
describe 'Cache'
|
||||
describe 'Request'
|
||||
describe '#cache'
|
||||
it 'should use memory store by default'
|
||||
get('/item', function(){
|
||||
return this.cache.toString()
|
||||
})
|
||||
get('/item').body.should.eql '[Memory Store]'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'cache Store.Memory'
|
||||
before_each
|
||||
store = new cache.Store.Memory
|
||||
end
|
||||
|
||||
describe '#toString()'
|
||||
it 'should return [Memory Store]'
|
||||
store.toString().should.eql '[Memory Store]'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set()'
|
||||
describe 'given a key and value'
|
||||
it 'should set the cache data'
|
||||
var result
|
||||
store.set('foo', 'bar', function(){
|
||||
store.get('foo', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
result.should.eql 'bar'
|
||||
end
|
||||
|
||||
it 'should override existing data'
|
||||
var result
|
||||
store.set('foo', 'bar', function(){
|
||||
store.set('foo', 'baz', function(){
|
||||
store.get('foo', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
})
|
||||
result.should.eql 'baz'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given an abitrary key'
|
||||
it 'should throw an error'
|
||||
-{ store.set({}, 'foo') }.should.throw_error
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given an abitrary value'
|
||||
it 'should serialize as JSON'
|
||||
var result
|
||||
store.set('user', { name: 'tj' }, function(val){
|
||||
result = val
|
||||
})
|
||||
result.should.eql { name: 'tj' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get()'
|
||||
describe 'given a key'
|
||||
it 'should unserialize JSON data'
|
||||
var result
|
||||
store.set('user', { name: 'tj' }, function(){
|
||||
store.get('user', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
result.should.eql { name: 'tj' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should pass a set of caches'
|
||||
var result
|
||||
store.set('user:1', 'a', function(){
|
||||
store.set('user:2', 'b', function(){
|
||||
store.set('foo', 'bar', function(){
|
||||
store.get('user:*', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
result.should.eql { 'user:1': 'a', 'user:2': 'b' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clear()'
|
||||
describe 'given a key'
|
||||
it 'should delete previous data'
|
||||
var result
|
||||
store.set('foo', 'bar', function(){
|
||||
store.clear('foo', function(){
|
||||
store.get('foo', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
})
|
||||
result.should.be_null
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should clear a set of caches'
|
||||
var results = []
|
||||
store.set('user:one', '1', function(){
|
||||
store.set('user:two', '2', function(){
|
||||
store.clear('user:*', function(){
|
||||
store.get('user:one', function(val){
|
||||
results.push(val)
|
||||
store.get('user:two', function(val){
|
||||
results.push(val)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
results.should.eql [null, null]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reap()'
|
||||
it 'should destroy caches older than the given age in milliseconds'
|
||||
store.set('user:one', '1')
|
||||
store.data['user:one'].created = Number((5).minutes.ago)
|
||||
store.set('user:two', '2')
|
||||
store.data['user:two'].created = Number((2).seconds.ago)
|
||||
store.reap((1).minute)
|
||||
store.get('user:one', function(val){
|
||||
val.should.be_null
|
||||
})
|
||||
store.get('user:two', function(val){
|
||||
val.should.not.be_null
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/common-logger').CommonLogger)
|
||||
end
|
||||
|
||||
describe 'CommonLogger'
|
||||
describe 'on'
|
||||
describe 'response'
|
||||
it 'should output in common log format'
|
||||
GLOBAL.stub('puts')
|
||||
GLOBAL.should.receive('puts')
|
||||
get('/style.css', function(){
|
||||
this.contentType('css')
|
||||
return 'body { background: #000; }'
|
||||
})
|
||||
get('/style.css')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -17,13 +17,13 @@ describe 'Express'
|
||||
compileCookie('foo', 'bar', options).should.eql 'foo=bar; path=/; domain=.vision-media.ca'
|
||||
end
|
||||
|
||||
it 'should currectly format any Date objects'
|
||||
it 'should correctly format any Date objects'
|
||||
var options = {
|
||||
expires: new Date('May 25, 1987 11:13:00'),
|
||||
expires: new Date(Date.parse('May 25, 1987 11:13:00 PDT')),
|
||||
path: '/foo',
|
||||
domain: '.vision-media.ca'
|
||||
}
|
||||
compileCookie('foo', 'bar', options).should.eql 'foo=bar; expires=Mon, 25-May-1987 11:13:00 GMT; path=/foo; domain=.vision-media.ca'
|
||||
compileCookie('foo', 'bar', options).should.eql 'foo=bar; expires=Mon, 25 May 1987 18:13:00 GMT; path=/foo; domain=.vision-media.ca'
|
||||
end
|
||||
|
||||
it 'should convert true to a key without a value'
|
||||
@@ -76,13 +76,13 @@ describe 'Express'
|
||||
get('/user').headers['set-cookie'].should.eql 'SID=732423sdfs73243; path=/; secure'
|
||||
end
|
||||
|
||||
it 'should join multiple cookies'
|
||||
it 'should set multiple cookies'
|
||||
get('/user', function(){
|
||||
this.cookie('SID', '732423sdfs73243', { path: '/', secure: true })
|
||||
this.cookie('foo', 'bar')
|
||||
return ''
|
||||
})
|
||||
get('/user').headers['set-cookie'].should.eql 'SID=732423sdfs73243; path=/; secure, foo=bar'
|
||||
get('/user').headers['set-cookie'].should.eql 'SID=732423sdfs73243; path=/; secure\nset-cookie: foo=bar; path=/'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/cookie').Cookie)
|
||||
use(require('express/plugins/session').Session)
|
||||
use(require('express/plugins/flash').Flash)
|
||||
Session.store.clear()
|
||||
var sess = new Base(123)
|
||||
Session.store.commit(sess, function(){})
|
||||
end
|
||||
|
||||
describe 'Flash'
|
||||
describe '#flash()'
|
||||
describe 'given a type and msg'
|
||||
it 'should push a flash message'
|
||||
var headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/', function(){ return this.flash('info', 'email sent') })
|
||||
get('/', function(){ return this.flash('info').join(', ') })
|
||||
post('/', headers)
|
||||
post('/', headers)
|
||||
get('/', headers).body.should.eql 'email sent, email sent'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given a type'
|
||||
describe 'when no messages have been pushed'
|
||||
it 'should return null'
|
||||
var headers = { headers: { cookie: 'sid=123' }}
|
||||
get('/', function(){ return this.flash('info') || 'empty' })
|
||||
get('/', headers).body.should.eql 'empty'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when messages have been pushed'
|
||||
it 'should persist only until flushed'
|
||||
var headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/', function(){ return this.flash('info', 'email sent') })
|
||||
get('/', function(){ return (this.flash('info') || ['nope!']).join(', ') })
|
||||
post('/', headers)
|
||||
post('/', headers)
|
||||
get('/', headers).body.should.eql 'email sent, email sent'
|
||||
get('/', headers).body.should.eql 'nope!'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given no arguments'
|
||||
it 'should provide access to all types'
|
||||
var flash, headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/', function(){ return this.flash('info', 'email sent') })
|
||||
post('/error', function(){ return this.flash('error', 'email failed to send') })
|
||||
get('/', function(){ return flash = this.flash(), '' })
|
||||
post('/', headers)
|
||||
post('/', headers)
|
||||
post('/error', headers)
|
||||
get('/')
|
||||
flash.should.eql { info: ['email sent', 'email sent'], error: ['email failed to send'] }
|
||||
end
|
||||
|
||||
it 'should persist only until flushed'
|
||||
var flash, headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/', function(){ return this.flash('info', 'email sent') })
|
||||
post('/error', function(){ return this.flash('error', 'email failed to send') })
|
||||
get('/', function(){ return flash = this.flash(), '' })
|
||||
post('/', headers)
|
||||
post('/', headers)
|
||||
post('/error', headers)
|
||||
get('/', headers)
|
||||
flash.should.eql { info: ['email sent', 'email sent'], error: ['email failed to send'] }
|
||||
get('/', headers)
|
||||
flash.should.eql {}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,10 @@
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/hooks').Hooks)
|
||||
hooks = require('express/plugins/hooks')
|
||||
use(hooks.Hooks)
|
||||
hooks.callbacks.before = []
|
||||
hooks.callbacks.after = []
|
||||
end
|
||||
|
||||
describe 'Hooks'
|
||||
@@ -17,6 +20,15 @@ describe 'Express'
|
||||
get('/user', function(){})
|
||||
get('/user').body.should.eql 'foobar'
|
||||
end
|
||||
|
||||
it 'should be able to halt the request'
|
||||
GLOBAL.before(function(){
|
||||
this.halt(404, 'woo!')
|
||||
})
|
||||
get('/user', function(){ return 'fail' })
|
||||
get('/user').status.should.eql 404
|
||||
get('/user').body.should.eql 'woo!'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after()'
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
var mime = require('express/mime')
|
||||
|
||||
CSSColors = Plugin.extend({
|
||||
extend: {
|
||||
init: function() {
|
||||
@@ -7,7 +9,7 @@ CSSColors = Plugin.extend({
|
||||
},
|
||||
on: {
|
||||
response: function(event) {
|
||||
if (event.response.headers['content-type'] == mime('css'))
|
||||
if (event.response.headers['content-type'] == mime.type('css'))
|
||||
event.response.body = event.response.body.replace('black', '#000')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/cookie').Cookie)
|
||||
use(Session = require('express/plugins/session').Session)
|
||||
Base = require('express/plugins/session').Base
|
||||
Session.store.clear()
|
||||
end
|
||||
|
||||
describe 'Session'
|
||||
describe 'when sid cookie is not present'
|
||||
it 'should set sid cookie'
|
||||
get('/login', function(){ return '' })
|
||||
get('/login').headers['set-cookie'].should.match(/^sid=(\w+);/)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when existing sid cookie is present'
|
||||
it 'should not set sid'
|
||||
Session.store.commit(new Base(123))
|
||||
get('/login', function(){ return '' })
|
||||
get('/login', { headers: { cookie: 'sid=123' }}).headers.should.not.have_property 'set-cookie'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when unknown sid cookie is present'
|
||||
it 'should set new sid'
|
||||
get('/login', function(){ return '' })
|
||||
var headers= get('/login', { headers: { cookie: 'sid=123' }}).headers
|
||||
|
||||
headers.should.have_property 'set-cookie'
|
||||
headers['set-cookie'].should.not.be '123'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'session Store.Memory'
|
||||
before_each
|
||||
Session.store= memory = new (require('express/plugins/session').Store.Memory)
|
||||
end
|
||||
|
||||
it 'should persist'
|
||||
post('/login', function(){
|
||||
return this.session.name = 'tj'
|
||||
})
|
||||
get('/login', function(){
|
||||
return this.session.name
|
||||
})
|
||||
memory.commit(new Base(123))
|
||||
var headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/login', headers)
|
||||
get('/login', headers).status.should.eql 200
|
||||
get('/login', headers).body.should.eql 'tj'
|
||||
end
|
||||
|
||||
describe '#toString()'
|
||||
it 'should return [Memory Store]'
|
||||
memory.toString().should.eql '[Memory Store]'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fetch()'
|
||||
describe 'when the session does not exist'
|
||||
it 'should return a new Session'
|
||||
var result
|
||||
memory.fetch('1', function(error,session){
|
||||
result= session
|
||||
})
|
||||
result.should.have_property 'lastAccess'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the session does exist'
|
||||
it 'should return the previous session'
|
||||
var result
|
||||
memory.commit(new Base('1'))
|
||||
memory.fetch('1', function(error, session){
|
||||
result = session
|
||||
})
|
||||
result.id.should.eql '1'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clear()'
|
||||
it 'should remove all sessions'
|
||||
memory.commit({ id: '1' })
|
||||
memory.commit({ id: '2' })
|
||||
memory.clear()
|
||||
memory.should.not.have_property '1'
|
||||
memory.should.not.have_property '2'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#length()'
|
||||
it 'should return the number of session'
|
||||
var length
|
||||
memory.commit({ id: '1' })
|
||||
memory.commit({ id: '2' })
|
||||
memory.length(function(error, len) {
|
||||
length = len
|
||||
})
|
||||
length.should.eql 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy()'
|
||||
it 'should destroy a single session'
|
||||
memory.commit({ id: '1' })
|
||||
memory.commit({ id: '2' })
|
||||
memory.destroy('1')
|
||||
memory.store.should.not.have_property '1'
|
||||
memory.store.should.have_property '2'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reap()'
|
||||
it 'should destroy sessions older than the given age in milliseconds'
|
||||
memory.commit({ id: '1', lastAccess: Number(new Date) - 300 })
|
||||
memory.commit({ id: '2', lastAccess: Number(new Date) - 250 })
|
||||
memory.commit({ id: '3', lastAccess: Number(new Date) - 100 })
|
||||
memory.commit({ id: '4', lastAccess: Number(new Date) })
|
||||
memory.reap(200)
|
||||
memory.store.should.not.have_property '1'
|
||||
memory.store.should.not.have_property '2'
|
||||
memory.store.should.have_property '3'
|
||||
memory.store.should.have_property '4'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,104 @@
|
||||
|
||||
describe 'Express'
|
||||
describe 'StaticFile'
|
||||
before
|
||||
StaticFile = require('express/plugins/static').File
|
||||
use(require('express/plugins/static').Static, { path: 'spec/fixtures' })
|
||||
end
|
||||
|
||||
describe '#constructor'
|
||||
it 'should accept and assign #path'
|
||||
(new StaticFile('/foo/bar')).path.should.eql '/foo/bar'
|
||||
end
|
||||
|
||||
it 'should throw an InvalidPathError when .. is found'
|
||||
// TODO: use throw_error when fixed...
|
||||
try { new StaticFile('/../foobar') }
|
||||
catch (e) {
|
||||
e.name.should.eql 'InvalidPathError'
|
||||
e.message.should.eql "`/../foobar' is not a valid path"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /public/*'
|
||||
it 'should transfer static files'
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sendfile()'
|
||||
describe 'when the file exists'
|
||||
it 'should transfer the file'
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
end
|
||||
|
||||
it 'should automatically set the content type based on extension'
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when "cache static files" is enabled'
|
||||
it 'should transfer the file'
|
||||
enable('cache static files')
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/cached', function(){
|
||||
var self = this
|
||||
this.cache.get('static:spec/fixtures/user.json', function(val){
|
||||
self.halt(200, val ? 'yes' : 'no')
|
||||
})
|
||||
})
|
||||
get('/cached').body.should.eql 'no'
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
get('/cached').body.should.eql 'yes'
|
||||
get('/cached').body.should.eql 'yes'
|
||||
end
|
||||
|
||||
it 'should automatically set the content type based on extension'
|
||||
enable('cache static files')
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#download()'
|
||||
describe 'when the file exists'
|
||||
it 'should set attachment filename'
|
||||
get('/report', function(){
|
||||
this.download('report.pdf')
|
||||
return 'foo'
|
||||
})
|
||||
get('/report').headers['content-disposition'].should.eql 'attachment; filename="report.pdf"'
|
||||
end
|
||||
|
||||
it 'should transfer the file'
|
||||
get('/public/*', function(file){
|
||||
this.download('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
end
|
||||
|
||||
it 'should automatically set the content type based on extension'
|
||||
get('/public/*', function(file){
|
||||
this.download('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -11,5 +11,194 @@ describe 'Express'
|
||||
set('views').should.eql 'spec/views'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'set("partials")'
|
||||
it 'should default to <views>/partials'
|
||||
set('root', 'spec')
|
||||
set('partials').should.eql 'spec/views/partials'
|
||||
set('views', 'magicland')
|
||||
set('partials').should.eql 'magicland/partials'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#partial()'
|
||||
before_each
|
||||
set('views', 'spec/fixtures')
|
||||
set('partials', 'spec/fixtures/partials')
|
||||
end
|
||||
|
||||
describe 'given a valid view name'
|
||||
it 'should render a partial'
|
||||
get('/', function(){
|
||||
this.render('list.html.haml', { locals: { items: ['foo', 'bar'] }})
|
||||
})
|
||||
get('/').body.should.include '<ul>'
|
||||
get('/').body.should.include '<li>foo'
|
||||
get('/').body.should.include '<li>bar'
|
||||
end
|
||||
|
||||
it 'should render collections'
|
||||
get('/', function(){
|
||||
return this.partial('item.html.haml', {
|
||||
collection: ['foo', 'bar']
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li>foo'
|
||||
get('/').body.should.include '<li>bar'
|
||||
end
|
||||
|
||||
it 'should render collections with a given object name'
|
||||
get('/', function(){
|
||||
return this.partial('video.html.haml', {
|
||||
collection: ['im a movie', 'im another movie'],
|
||||
as: 'vid'
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li>im a movie'
|
||||
get('/').body.should.include '<li>im another movie'
|
||||
end
|
||||
|
||||
it 'should pass __isFirst__, __isLast__, and __index__ to partials as locals'
|
||||
get('/', function(){
|
||||
return this.partial('article.html.haml', {
|
||||
collection: ['a', 'b', 'c']
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li class="first">a'
|
||||
get('/').body.should.include '<li class="1">b'
|
||||
get('/').body.should.include '<li class="last">c'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#render()'
|
||||
before_each
|
||||
set('views', 'spec/fixtures')
|
||||
end
|
||||
|
||||
describe 'given a callback'
|
||||
it 'should be passed the rendered content'
|
||||
get('/', function(){
|
||||
this.render('hello.html.haml', {}, function(err, content){
|
||||
if (err) this.error(err)
|
||||
else this.halt(203, content)
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<html>\n<body>'
|
||||
get('/').status.should.eql 203
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given a valid view name'
|
||||
describe 'and layout of the same type exists'
|
||||
it 'should render the layout and view'
|
||||
get('/', function(){
|
||||
this.render('hello.html.haml')
|
||||
})
|
||||
get('/').body.should.include '<html>\n<body>'
|
||||
get('/').body.should.include '<h2>Hello'
|
||||
end
|
||||
|
||||
it 'should default context to the current request'
|
||||
get('/', function(){
|
||||
this.title = 'Welcome'
|
||||
this.render('page.html.haml', { layout: false })
|
||||
})
|
||||
get('/').body.should.include '<title>Welcome'
|
||||
end
|
||||
|
||||
it 'should set the content type based on the last path segment'
|
||||
get('/', function(){
|
||||
this.render('hello.html.haml')
|
||||
})
|
||||
get('/').headers['content-type'].should.eql 'text/html'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and layout of the same type does not exist'
|
||||
it 'should throw an error'
|
||||
get('/', function(){
|
||||
this.render('hello.html.haml', { layout: 'front' })
|
||||
})
|
||||
-{ get('/') }.should.throw_error 'No such file or directory'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given a custom layout name'
|
||||
it 'should render the layout and view'
|
||||
get('/', function(){
|
||||
this.title = 'Express'
|
||||
this.render('hello.html.haml', { layout: 'page' })
|
||||
})
|
||||
get('/').body.should.include '<title>Express'
|
||||
get('/').body.should.include '<h2>Hello'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when layout: false'
|
||||
it 'should render the view only'
|
||||
get('/', function(){
|
||||
this.render('hello.html.haml', { layout: false })
|
||||
})
|
||||
get('/').body.should.not.include '<body>'
|
||||
get('/').body.should.include '<h2>Hello'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when engine cannot be found'
|
||||
it 'should throw an error'
|
||||
get('/', function(){
|
||||
this.render('user.html.invalid')
|
||||
})
|
||||
-{ get('/') }.should.throw_error "Cannot find module 'invalid'"
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when locals are passed'
|
||||
it 'should have direct access to locals'
|
||||
get('/user', function(){
|
||||
this.render('user.html.haml', {
|
||||
locals: {
|
||||
name: 'tj',
|
||||
email: 'tj@vision-media.ca'
|
||||
}
|
||||
})
|
||||
})
|
||||
get('/user').body.should.include '<h1>tj'
|
||||
get('/user').body.should.include '<p>tj@vision-media.ca'
|
||||
end
|
||||
|
||||
it 'should have direct access to locals within the layout'
|
||||
get('/user', function(){
|
||||
this.render('user.html.haml', {
|
||||
layout: 'layout.user',
|
||||
locals: {
|
||||
name: 'tj',
|
||||
email: 'tj@vision-media.ca'
|
||||
}
|
||||
})
|
||||
})
|
||||
get('/user').body.should.include '<title>Viewing tj'
|
||||
get('/user').body.should.include '<h1>tj'
|
||||
get('/user').body.should.include '<p>tj@vision-media.ca'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when context is passed'
|
||||
it 'should evaluate in context to that object'
|
||||
get('/article', function(){
|
||||
this.render('article.html.haml', {
|
||||
context: {
|
||||
name: 'Writing a Node.js Web Application'
|
||||
}
|
||||
})
|
||||
})
|
||||
get('/article').body.should.include '<h2>Writing a Node.js Web Application'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
+192
-15
@@ -39,6 +39,23 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#isXHR'
|
||||
it 'should return false unless X-Requested-With is "XMLHttpRequest"'
|
||||
get('/', function(){ return this.isXHR ? 'yay' : 'nope' })
|
||||
get('/').body.should.eql 'nope'
|
||||
end
|
||||
|
||||
it 'should return true when X-Requested-With is "XMLHttpRequest"'
|
||||
get('/', function(){ return this.isXHR ? 'yay' : 'nope' })
|
||||
get('/', { headers: { 'x-requested-with': 'XMLHttpRequest' }}).body.should.eql 'yay'
|
||||
end
|
||||
|
||||
it 'should be case insensitive'
|
||||
get('/', function(){ return this.isXHR ? 'yay' : 'nope' })
|
||||
get('/', { headers: { 'x-requested-with': 'xmlhttprequest' }}).body.should.eql 'yay'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#accepts()'
|
||||
describe 'when the Accept header is present'
|
||||
it 'should return true if the mime type is acceptable'
|
||||
@@ -55,7 +72,30 @@ describe 'Express'
|
||||
describe 'when the Accept header is not present'
|
||||
it 'should return true'
|
||||
get('/user', function(){ return this.accepts('jpeg').toString() })
|
||||
get('/user', { headers: { accept: null }}).body.should.eql 'true'
|
||||
get('/user', { headers: { accept: '' }}).body.should.eql 'true'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'should allow multiple arguments'
|
||||
it 'should return true if any mime type is present'
|
||||
get('/user', function(){ return this.accepts('jpeg', 'png').toString() })
|
||||
get('/user', { headers: { accept: 'image/gif,image/png' }}).body.should.eql 'true'
|
||||
end
|
||||
|
||||
it 'should return false if none of the mime types are present'
|
||||
get('/user', function(){ return this.accepts('jpeg', 'png').toString() })
|
||||
get('/user', { headers: { accept: 'text/plain,text/html' }}).body.should.eql 'false'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when a media type range was sent'
|
||||
it 'should return true if the group media type matches'
|
||||
get('/user', function(){ return this.accepts('html').toString() })
|
||||
get('/user', { headers: { accept: 'text/plain,text/*' }}).body.should.eql 'true'
|
||||
end
|
||||
it 'should return false if the group media type does not match'
|
||||
get('/user', function(){ return this.accepts('ogg').toString() })
|
||||
get('/user', { headers: { accept: 'text/plain,text/*' }}).body.should.eql 'false'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -84,18 +124,6 @@ describe 'Express'
|
||||
get('/user').body.should.include('Oh noes!')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given an invalid status code'
|
||||
it 'should throw an InvalidStatusCode exception'
|
||||
// TODO: use throw_error when fixed...
|
||||
get('/user', function(){ this.halt(123123) })
|
||||
try { get('/user') }
|
||||
catch (e) {
|
||||
e.should.be_an_instance_of ExpressError
|
||||
e.should.be_an_instance_of InvalidStatusCode
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#contentType()'
|
||||
@@ -174,9 +202,11 @@ describe 'Express'
|
||||
|
||||
it 'should work with a query string'
|
||||
get('/user', function(){
|
||||
return this.param('page') || 'First page'
|
||||
var page = this.param('page')
|
||||
return page === undefined ? 'First page' : String(page)
|
||||
})
|
||||
get('/user').body.should.eql 'First page'
|
||||
get('/user?page=0').body.should.eql '0'
|
||||
get('/user?page=2').body.should.eql '2'
|
||||
get('/user?foo[]=bar&page=5').body.should.eql '5'
|
||||
end
|
||||
@@ -187,6 +217,153 @@ describe 'Express'
|
||||
})
|
||||
get('/public/app.js').body.should.eql 'public, app, js'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#pass()'
|
||||
it 'should pass control to the next matching route'
|
||||
get('/user', function(){
|
||||
this.pass()
|
||||
})
|
||||
get('/user', function(){
|
||||
this.pass()
|
||||
return 'nodejs'
|
||||
})
|
||||
get('/user', function(){ return 'success'})
|
||||
get('/user').body.should.eql 'success'
|
||||
end
|
||||
|
||||
describe 'given a string'
|
||||
it 'should pass to the given route'
|
||||
get('/user', function(){
|
||||
this.pass('/user/1')
|
||||
})
|
||||
get('/user/:id', function(){
|
||||
return 'Supa doopa usa'
|
||||
})
|
||||
get('/user').body.should.eql 'Supa doopa usa'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#error()'
|
||||
describe 'when an error route is defined'
|
||||
it 'should be called'
|
||||
var err
|
||||
disable('throw exceptions')
|
||||
global.error(function(e){
|
||||
err = e
|
||||
this.halt(500, 'FAIL!')
|
||||
})
|
||||
get('/', function(){ this.error(new Error('whoop')) })
|
||||
get('/').body.should.eql 'FAIL!'
|
||||
err.message.should.eql 'whoop'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when accepting "html"'
|
||||
describe 'with "show exceptions" enabled'
|
||||
it 'should render the show-exceptions page'
|
||||
disable('throw exceptions')
|
||||
enable('show exceptions')
|
||||
get('/', function(){
|
||||
this.error(new Error('fail!'))
|
||||
})
|
||||
get('/').body.should.include '<em>500</em> Error: fail!'
|
||||
get('/').status.should.eql 500
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when not accepting "html"'
|
||||
it 'should render the default 500 status body'
|
||||
var headers = { headers: { accept: 'text/plain' }}
|
||||
disable('throw exceptions')
|
||||
enable('show exceptions')
|
||||
get('/', function(){
|
||||
this.error(new Error('fail!'))
|
||||
})
|
||||
get('/', headers).body.should.eql 'Internal Server Error'
|
||||
get('/', headers).status.should.eql 500
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with "throw exceptions" enabled'
|
||||
it 'should re-throw the exception'
|
||||
enable('show exceptions')
|
||||
enable('throw exceptions')
|
||||
get('/', function(){
|
||||
this.error(new Error('fail!'))
|
||||
})
|
||||
-{ get('/') }.should.throw_error Error, 'fail!'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with "show exceptions" disabled'
|
||||
it 'should render the default 500 status body'
|
||||
disable('throw exceptions')
|
||||
get('/', function(){
|
||||
this.error(new Error('fail!'))
|
||||
})
|
||||
get('/').body.should.eql 'Internal Server Error'
|
||||
get('/').status.should.eql 500
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with "throw exceptions" enabled'
|
||||
it 'should re-throw the exception'
|
||||
enable('throw exceptions')
|
||||
get('/', function(){
|
||||
this.error(new Error('fail!'))
|
||||
})
|
||||
-{ get('/') }.should.throw_error Error, 'fail!'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notFound()'
|
||||
describe 'when a notFound route is defined'
|
||||
it 'should be called'
|
||||
notFound(function(){
|
||||
this.halt(404, 'Sorry your page was not found')
|
||||
})
|
||||
get('/', function(){ this.notFound() })
|
||||
get('/').body.should.eql 'Sorry your page was not found'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when accepting "html"'
|
||||
describe 'with "helpful 404" enabled'
|
||||
it 'should render the not-found page'
|
||||
enable('helpful 404')
|
||||
get('/', function(){ this.notFound() })
|
||||
get('/').body.should.include '<em>404</em> Not Found'
|
||||
get('/').status.should.eql 404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when not accepting "html"'
|
||||
describe 'with "helpful 404" enabled'
|
||||
it 'should render defaulat 404 status body'
|
||||
var headers = { headers: { accept: 'text/plain' }}
|
||||
enable('helpful 404')
|
||||
get('/', function(){ this.notFound() })
|
||||
get('/', headers).body.should.eql 'Not Found'
|
||||
get('/', headers).status.should.eql 404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when "helpful 404" is disabled'
|
||||
it 'should render defaulat 404 status body'
|
||||
var headers = { headers: { accept: 'text/plain' }}
|
||||
enable('helpful 404')
|
||||
get('/', function(){ this.notFound() })
|
||||
get('/', headers).body.should.eql 'Not Found'
|
||||
get('/', headers).status.should.eql 404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
+73
-13
@@ -4,6 +4,70 @@ describe 'Express'
|
||||
reset()
|
||||
end
|
||||
|
||||
describe 'param()'
|
||||
describe 'given no args'
|
||||
it 'should throw an error'
|
||||
-{ param() }.should.throw_error TypeError
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given a non-string as the first argument'
|
||||
it 'should throw an error'
|
||||
-{ param(12) }.should.throw_error TypeError
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given no callback function'
|
||||
it 'should throw an error'
|
||||
-{ param('foo') }.should.throw_error TypeError
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given a non-function as the second argument'
|
||||
it 'should throw an error'
|
||||
-{ param('foo', 2) }.should.throw_error TypeError
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with key / callback function'
|
||||
it 'should pre-process a value before it is passed to a route'
|
||||
param('user_id', function(val){
|
||||
val = parseInt(val)
|
||||
return isNaN(val) ? false : val
|
||||
})
|
||||
get('/user/:user_id', function(id){
|
||||
return String(typeof id === 'number')
|
||||
})
|
||||
get('/user/99').body.should.eql 'true'
|
||||
end
|
||||
|
||||
it 'should pass when false is explictly returned'
|
||||
param('user_id', function(val){
|
||||
val = parseInt(val)
|
||||
return isNaN(val) ? false : val
|
||||
})
|
||||
get('/user/:user_id', function(id){
|
||||
return String(typeof id === 'number')
|
||||
})
|
||||
get('/user/:name', function(name){
|
||||
return name
|
||||
})
|
||||
get('/user/99').body.should.eql 'true'
|
||||
get('/user/tj').body.should.eql 'tj'
|
||||
end
|
||||
|
||||
it 'should evaluate in context to the request'
|
||||
param('user_id', function(val){
|
||||
this.pass()
|
||||
})
|
||||
get('/user/:user_id', function(id){
|
||||
return String(typeof id === 'number')
|
||||
})
|
||||
get('/user/99').status.should.eql 404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'route'
|
||||
describe 'with callback function'
|
||||
it 'should respond with a body string'
|
||||
@@ -84,19 +148,6 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with no response body'
|
||||
it 'should throw a InvalidResponseBody'
|
||||
// TODO: use throw_error when fixed...
|
||||
get('/user', function(){
|
||||
this.respond()
|
||||
})
|
||||
try { get('/user') }
|
||||
catch (e) {
|
||||
e.should.be_an_instance_of InvalidResponseBody
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with regular expression'
|
||||
it 'should match'
|
||||
get(/^\/user\/(\d+)\/(\w+)/, function(id, operation){
|
||||
@@ -187,6 +238,15 @@ describe 'Express'
|
||||
get('/report.pdf').body.should.eql 'yay'
|
||||
end
|
||||
|
||||
it 'should match when mid-segment'
|
||||
get('/user-:name-:id', function(name, id){
|
||||
return name + ' ' + id
|
||||
})
|
||||
get('/user-tj-1').body.should.eql 'tj 1'
|
||||
get('/user--1').status.should.eql 404
|
||||
get('/user-tj-').status.should.eql 404
|
||||
end
|
||||
|
||||
it 'should not match without value'
|
||||
get('/report.:format', function(){
|
||||
return 'yay'
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
describe 'Express'
|
||||
describe 'StaticFile'
|
||||
before
|
||||
StaticFile = require('express/static').File
|
||||
end
|
||||
|
||||
describe '#init'
|
||||
it 'should accept and assign #path'
|
||||
(new StaticFile('/foo/bar')).path.should.eql '/foo/bar'
|
||||
end
|
||||
|
||||
it 'should throw an InvalidPathError when .. is found'
|
||||
// TODO: use throw_error when fixed...
|
||||
try { new StaticFile('/../foobar') }
|
||||
catch (e) {
|
||||
e.should.be_an_instance_of InvalidPathError
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,104 @@
|
||||
|
||||
describe 'Express'
|
||||
before
|
||||
utils = require('express/utils')
|
||||
end
|
||||
|
||||
describe 'escape()'
|
||||
it 'should escape html'
|
||||
utils.escape('<p>this & that').should.eql '<p>this & that'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'uid()'
|
||||
it 'should return a string of random characters'
|
||||
utils.uid().should.not.eql utils.uid()
|
||||
utils.uid().length.should.be_greater_than 20
|
||||
end
|
||||
end
|
||||
|
||||
describe 'mergeParam()'
|
||||
describe 'with empty params'
|
||||
it 'should merge the given key and value'
|
||||
params = {}
|
||||
utils.mergeParam('user[names][firstName]', 'tj', params)
|
||||
params.user.names.firstName.should.eql 'tj'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with populated params'
|
||||
it 'should merge not overwrite'
|
||||
params = { user: { name: 'tj' }}
|
||||
utils.mergeParam('user[email]', 'tj@vision-media.ca', params)
|
||||
params.user.name.should.eql 'tj'
|
||||
params.user.email.should.eql 'tj@vision-media.ca'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with an object as value'
|
||||
it 'should preserve it'
|
||||
params = {}
|
||||
utils.mergeParam('images[]', { name: 1 }, params)
|
||||
utils.mergeParam('images[]', { name: 2 }, params)
|
||||
params.images.should.eql [{ name: 1}, { name: 2 }]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'key[number]'
|
||||
it 'should merge correctly'
|
||||
params = { images: { one: 'foo.png' }}
|
||||
utils.mergeParam('images[0]', 'bar.png', params)
|
||||
params.images.one.should.eql 'foo.png'
|
||||
params.images[0].should.eql 'bar.png'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'key[]'
|
||||
describe 'with a single value'
|
||||
it 'should still be an array'
|
||||
params = {}
|
||||
utils.mergeParam('images[]', '1', params)
|
||||
params.images.should.eql ['1']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with empty params'
|
||||
it 'should merge correctly'
|
||||
params = {}
|
||||
utils.mergeParam('images[]', '1', params)
|
||||
utils.mergeParam('images[]', '2', params)
|
||||
params.images.should.eql ['1', '2']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with populated params'
|
||||
it 'should convert to an array'
|
||||
params = { images: 'foo.png'}
|
||||
utils.mergeParam('images[]', '1', params)
|
||||
params.images.should.eql ['foo.png', '1']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with several merges'
|
||||
it 'should push values'
|
||||
params = {}
|
||||
utils.mergeParam('images[]', '1', params)
|
||||
utils.mergeParam('images[]', '2', params)
|
||||
utils.mergeParam('images[]', '3', params)
|
||||
params.images.should.eql ['1', '2', '3']
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when nested'
|
||||
it 'should marge correctly'
|
||||
params = {}
|
||||
utils.mergeParam('user[tj][images][]', '1', params)
|
||||
utils.mergeParam('user[tj][images][]', '2', params)
|
||||
utils.mergeParam('user[tj][images][]', '3', params)
|
||||
params.user.should.eql { tj: { images: ['1', '2', '3'] }}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
Submodule spec/support/libxmljs deleted from 584f3aa303
Referência em uma Nova Issue
Bloquear um usuário