Comparar commits
349 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 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 | |||
| cbc3163496 | |||
| 8edc213517 | |||
| b9f1589079 | |||
| d7fa7864ce | |||
| 725e2ec99a | |||
| 8953450e9a | |||
| dda12561d3 | |||
| 9a2178d34d | |||
| b9278d4698 | |||
| 23d35c5828 | |||
| 389594dab7 | |||
| e125ae2c48 | |||
| 4f4b4f0de5 | |||
| 0469a41d0b | |||
| a09c773f1b | |||
| 765c8ae0b3 | |||
| a1eba23ee8 | |||
| 25b34263e2 | |||
| 30d23a8699 | |||
| 9d096378f3 | |||
| 3f9cbbbffb | |||
| 080e4290ea | |||
| 39aea80fbb | |||
| 8ac1eaa233 | |||
| 7df254be64 | |||
| 596a39ba19 | |||
| a36b13006b | |||
| e8771336ba | |||
| 6f7141a266 | |||
| 6f995d0d49 | |||
| 694572bd8a | |||
| ddd6f27080 | |||
| 60ed9bd49f | |||
| 54a8a7472a | |||
| 19672e7c40 | |||
| 99e3130f3c | |||
| 17c41689e3 | |||
| 22d4b18c0e | |||
| 4e85cace68 | |||
| da390ff43d | |||
| 774f96fcb9 | |||
| 17ab8e81aa | |||
| b02b384f14 | |||
| 3ec970bb67 | |||
| 22e338fbd4 |
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
+9
-6
@@ -1,9 +1,12 @@
|
||||
[submodule "support/haml"]
|
||||
path = support/haml
|
||||
url = git://github.com/creationix/haml-js.git
|
||||
[submodule "lib/support/oo"]
|
||||
path = lib/support/oo
|
||||
url = git://github.com/visionmedia/js-oo.git
|
||||
[submodule "lib/support/ext"]
|
||||
path = lib/support/ext
|
||||
url = git://github.com/visionmedia/ext.js.git
|
||||
[submodule "lib/support/sass"]
|
||||
path = lib/support/sass
|
||||
url = git://github.com/visionmedia/sass.js.git
|
||||
[submodule "lib/support/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
|
||||
|
||||
+127
-1
@@ -1,5 +1,131 @@
|
||||
|
||||
0.0.1 / YYYY-MM-DD
|
||||
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
|
||||
==================
|
||||
|
||||
* Added view benchmarks; currently haml vs ejs
|
||||
* Added Request#attachment() specs. Closes #116
|
||||
* Added use of node's parseQuery() util. Closes #123
|
||||
* Added `make init` for submodules
|
||||
* Updated Haml
|
||||
* Updated sample chat app to show messages on load
|
||||
* Updated libxmljs parseString -> parseHtmlString
|
||||
* Fixed `make init` to work with older versions of git
|
||||
* Fixed specs can now run independant specs for those who cant build deps. Closes #127
|
||||
* Fixed issues introduced by the node url module changes. Closes 126.
|
||||
* Fixed two assertions failing due to Collection#keys() returning strings
|
||||
* Fixed faulty Collection#toArray() spec due to keys() returning strings
|
||||
* Fixed `make test` now builds libxmljs.node before testing
|
||||
|
||||
0.0.1 / 2010-01-03
|
||||
==================
|
||||
|
||||
* Initial release
|
||||
|
||||
+10
-6
@@ -1,13 +1,17 @@
|
||||
|
||||
NODE = node
|
||||
|
||||
all: test
|
||||
|
||||
test:
|
||||
@$(NODE) spec/spec.node.js
|
||||
@$(NODE) spec/node.js all
|
||||
|
||||
app:
|
||||
@$(NODE) examples/app.js
|
||||
app: app-chat
|
||||
|
||||
benchmark:
|
||||
@$(NODE) benchmarks/collection.js
|
||||
app-chat:
|
||||
@$(NODE) examples/chat/app.js
|
||||
|
||||
.PHONY: test benchmark app
|
||||
app-upload:
|
||||
@$(NODE) examples/upload/app.js
|
||||
|
||||
.PHONY: test app
|
||||
+36
-47
@@ -11,100 +11,89 @@
|
||||
|
||||
* Sexy DSL with robust sinatra-like routing
|
||||
* High performance
|
||||
* Session support
|
||||
* 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)
|
||||
* View support (ejs, haml, sass, etc)
|
||||
* Full test coverage
|
||||
* Extremely readable specs
|
||||
|
||||
## 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://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 it-self 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 froozen
|
||||
to spec/lib and does not require seperate installation.
|
||||
|
||||
To run all specifications:
|
||||
framework to write and run elegant spec suites. JSpec is frozen
|
||||
to spec/lib and **does not** require separate installation.
|
||||
|
||||
$ make test
|
||||
|
||||
Run individual suites:
|
||||
|
||||
$ node spec/spec.node.js core
|
||||
$ node spec/spec.node.js mime
|
||||
$ node spec/spec.node.js routing
|
||||
$ node spec/node.js core
|
||||
$ node spec/node.js mime
|
||||
$ node spec/node.js routing
|
||||
...
|
||||
|
||||
|
||||
Express is currently being developed with node --version:
|
||||
v0.1.21-66-g59a78d6
|
||||
v0.1.32
|
||||
|
||||
## More Information
|
||||
|
||||
* [JavaScript Extensions & Utilities](http://github.com/visionmedia/ext.js)
|
||||
* [JavaScript Sass](http://github.com/visionmedia/sass.js)
|
||||
* 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>
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -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]', 1000, 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,58 +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!')
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
var messages = [],
|
||||
utils = require('express/utils'),
|
||||
kiwi = require('kiwi')
|
||||
|
||||
configure(function(){
|
||||
kiwi.seed('haml')
|
||||
kiwi.seed('sass')
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(Cookie)
|
||||
use(Cache, { lifetime: (5).minutes, reapInterval: (1).minute })
|
||||
use(Session, { lifetime: (15).minutes, reapInterval: (1).minute })
|
||||
use(Logger)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.pass('/chat')
|
||||
})
|
||||
|
||||
get('/chat', function(){
|
||||
this.render('chat.haml.html', {
|
||||
locals: {
|
||||
title: 'Chat',
|
||||
messages: messages,
|
||||
name: this.session.name,
|
||||
usersOnline: Session.store.length()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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('/public/*', function(file){
|
||||
this.sendfile(__dirname + '/public/' + file)
|
||||
})
|
||||
|
||||
get('/*.css', function(file){
|
||||
this.render(file + '.sass.css', { 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,8 +1,10 @@
|
||||
%h1 Chat
|
||||
%img.bubble{ src: '/public/images/bubble.png' }
|
||||
%ul#messages
|
||||
:each msg in messages
|
||||
%li= msg
|
||||
%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' }
|
||||
@@ -1,8 +1,11 @@
|
||||
%html
|
||||
%head
|
||||
%title Chat
|
||||
%title= title
|
||||
%script{ src: '/public/javascripts/jquery.js' }
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/public/stylesheets/style.css' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper= body
|
||||
#online
|
||||
Online:
|
||||
%strong= usersOnline
|
||||
@@ -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
|
||||
@@ -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,48 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
var kiwi = require('kiwi')
|
||||
|
||||
configure(function(){
|
||||
kiwi.seed('haml')
|
||||
kiwi.seed('sass')
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(Cookie)
|
||||
use(Session)
|
||||
use(Flash)
|
||||
use(Logger)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.redirect('/upload')
|
||||
})
|
||||
|
||||
get('/upload', function(){
|
||||
this.render('upload.haml.html', {
|
||||
locals: {
|
||||
flashes: this.flash('info')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
post('/upload', function(){
|
||||
this.param('images').each(function(image){
|
||||
puts(image.filename + ' -> ' + image.tempfile)
|
||||
this.flash('info', 'Uploaded ' + image.filename)
|
||||
}, this)
|
||||
this.redirect('/upload')
|
||||
})
|
||||
|
||||
get('/public/*', function(file){
|
||||
this.sendfile(__dirname + '/public/' + file)
|
||||
})
|
||||
|
||||
get('/*.css', function(file){
|
||||
this.render(file + '.sass.css', { layout: false })
|
||||
})
|
||||
|
||||
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]' }
|
||||
.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
|
||||
%h2 Multiple
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[]', multiple: 'multiple' }
|
||||
.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
+6
-2
@@ -1,3 +1,7 @@
|
||||
|
||||
require('support/js-oo/lib/oo')
|
||||
require('express/core')
|
||||
require.paths.unshift(__dirname + '/support/ext/lib')
|
||||
require('ext')
|
||||
require('support/oo/lib/oo')
|
||||
require('support/haml/lib/haml')
|
||||
require('support/sass/lib/sass')
|
||||
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)
|
||||
}
|
||||
+127
-59
@@ -1,15 +1,20 @@
|
||||
|
||||
// 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 multipart = require('multipart'),
|
||||
utils = require('express/utils'),
|
||||
events = require('events'),
|
||||
fs = require('fs')
|
||||
|
||||
global.merge(require('sys'))
|
||||
global.merge(require('express/event'))
|
||||
global.merge(require('express/request'))
|
||||
global.merge(require('express/plugin'))
|
||||
global.merge(require('express/dsl'))
|
||||
|
||||
// --- Route
|
||||
|
||||
@@ -37,19 +42,6 @@ Route = Class({
|
||||
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)
|
||||
},
|
||||
|
||||
/**
|
||||
* Normalize _path_. When a RegExp it is simply returned,
|
||||
* otherwise a string is converted to a regular expression
|
||||
@@ -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)
|
||||
@@ -95,7 +87,7 @@ Route = Class({
|
||||
Router = Class({
|
||||
|
||||
/**
|
||||
* Initialize with _request_.
|
||||
* Initialize with _request_ and parse url.
|
||||
*
|
||||
* @param {Request} request
|
||||
* @api private
|
||||
@@ -112,10 +104,19 @@ Router = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
route: function(){
|
||||
var route = this.matchingRoute()
|
||||
if (route)
|
||||
return route.run(this.request.captures.slice(1), this.request)
|
||||
route: function() {
|
||||
var body,
|
||||
route = this.matchingRoute()
|
||||
if (route) {
|
||||
body = route.fn.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 if (this.request.accepts('html') && set('helpful 404'))
|
||||
this.request.halt(404, require('express/pages/not-found').render(this.request))
|
||||
else
|
||||
@@ -129,11 +130,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
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -147,7 +150,7 @@ Router = Class({
|
||||
|
||||
match: function(route) {
|
||||
if (this.request.method.toLowerCase() == route.method)
|
||||
if (this.request.captures = this.request.uri.path.match(route.path)) {
|
||||
if (this.request.captures = this.request.url.pathname.match(route.path)) {
|
||||
this.mapParams(route)
|
||||
return true
|
||||
}
|
||||
@@ -155,17 +158,16 @@ 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){
|
||||
this.request.params.path[key] = this.request.captures[++i]
|
||||
}, this)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -207,15 +209,58 @@ Server = Class({
|
||||
|
||||
run: function(port, host, backlog){
|
||||
var self = this
|
||||
this.running = true
|
||||
if (host !== undefined) this.host = host
|
||||
if (port !== undefined) this.port = port
|
||||
if (backlog !== undefined) this.backlog = backlog
|
||||
require('http')
|
||||
.createServer(function(request, response){
|
||||
request.body = ''
|
||||
request.setBodyEncoding('utf8')
|
||||
request.addListener('body', function(chunk){ request.body += chunk })
|
||||
request.addListener('complete', function(){ self.route(request, response) })
|
||||
request.setBodyEncoding('binary')
|
||||
function callback(e, result) {
|
||||
if (e)
|
||||
self.error(e, request, response)
|
||||
else if (!pendingFiles)
|
||||
self.route(request, response)
|
||||
}
|
||||
if (request.headers['content-type'] &&
|
||||
request.headers['content-type'].includes('multipart/form-data')) {
|
||||
var stream = multipart.parse(request),
|
||||
pendingFiles = 0
|
||||
request.params = { post: {}}
|
||||
stream
|
||||
.addListener('partBegin', function(part) {
|
||||
if (part.filename)
|
||||
++pendingFiles,
|
||||
part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(),
|
||||
part.fileStream = fs.createWriteStream(part.tempfile)
|
||||
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.close(function(){
|
||||
--pendingFiles
|
||||
callback()
|
||||
}),
|
||||
utils.mergeParam(part.name, { filename: part.filename, tempfile: part.tempfile }, request.params.post)
|
||||
else
|
||||
utils.mergeParam(part.name, part.buf, request.params.post)
|
||||
})
|
||||
.addListener('error', callback)
|
||||
.addListener('complete', callback)
|
||||
}
|
||||
else
|
||||
request
|
||||
.addListener('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')
|
||||
@@ -230,29 +275,46 @@ Server = Class({
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
var self = this,
|
||||
request = new Request(request, response)
|
||||
request.trigger('request', function(e) {
|
||||
try {
|
||||
if (e) throw e
|
||||
if (request.response.finished) return
|
||||
if (typeof (body = (new Router(request)).route()) === 'string')
|
||||
request.halt(200, body)
|
||||
} catch (e) {
|
||||
self.error(e, request)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle errors.
|
||||
*
|
||||
* @param {Error} e
|
||||
* @param {object} request
|
||||
* @param {object} response
|
||||
* @api private
|
||||
*/
|
||||
|
||||
error: function (e, request, response) {
|
||||
if (!(request instanceof Request))
|
||||
request = new Request(request, response),
|
||||
request.trigger('request')
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
// --- Express
|
||||
|
||||
Express = {
|
||||
version: '0.0.1',
|
||||
version: '0.7.2',
|
||||
config: [],
|
||||
routes: [],
|
||||
plugins: [],
|
||||
@@ -264,6 +326,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 +339,8 @@ configure('development', function(){
|
||||
configure('test', function(){
|
||||
enable('throw exceptions')
|
||||
})
|
||||
|
||||
configure('production', function(){
|
||||
enable('cache view contents')
|
||||
enable('cache static files')
|
||||
})
|
||||
|
||||
+28
-20
@@ -13,7 +13,10 @@ function route(method) {
|
||||
return function(path, options, fn){
|
||||
if (options instanceof Function)
|
||||
fn = options, options = {}
|
||||
Express.routes.push(new Route(method, path, fn, options))
|
||||
if (!Express.server.running)
|
||||
Express.routes.push(new Route(method, path, fn, options))
|
||||
else
|
||||
throw new Error('cannot create route ' + method.toUpperCase() + " `" + path + "' at runtime")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +31,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 +71,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 _fn_.
|
||||
*
|
||||
* Global configuration, disregards which
|
||||
* environment is active:
|
||||
@@ -92,22 +99,23 @@ exports.run = function() {
|
||||
*
|
||||
* configure('development')
|
||||
*
|
||||
* @param {string, function} environment
|
||||
* @param {string, function} env
|
||||
* @param {function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.configure = function(environment, fn) {
|
||||
if (environment instanceof Function)
|
||||
fn = environment, environment = 'all'
|
||||
exports.configure = function(env, fn) {
|
||||
if (env instanceof Function)
|
||||
fn = env, env = '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)
|
||||
return Express.config.push([env, fn])
|
||||
if (typeof env !== 'string')
|
||||
throw new Error('environment required')
|
||||
Express.config.each(function(conf){
|
||||
if (conf[0] === env ||
|
||||
conf[0] === 'all')
|
||||
conf[1].call(Express)
|
||||
})
|
||||
}
|
||||
|
||||
// --- Routing API
|
||||
@@ -115,4 +123,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('support/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.parseString(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)
|
||||
}
|
||||
@@ -13,7 +13,7 @@ exports.Event = Class({
|
||||
|
||||
init: 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,151 +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')
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode values in _params_.
|
||||
*
|
||||
* @param {hash} params
|
||||
* @see parseNestedParams()
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function decode(params) {
|
||||
for (var key in params)
|
||||
params[key] = decodeURIComponent(params[key]).replace(/\+/g, ' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse nested _params_.
|
||||
*
|
||||
* @param {hash} params
|
||||
* @return {hash}
|
||||
* @see parseParams()
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.parseNestedParams = function(params) {
|
||||
var parts, key
|
||||
decode(params)
|
||||
for (key in params)
|
||||
if (parts = key.split('['))
|
||||
if (parts.length > 1)
|
||||
for (var i = 0, prop = params, len = parts.length; i < len; ++i) {
|
||||
var name = parts[i].replace(']', '')
|
||||
if (i == len - 1)
|
||||
prop[name] = params[key],
|
||||
prop = params,
|
||||
delete params[key]
|
||||
else
|
||||
prop = prop[name] = prop[name] || {}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse params _string_ into a nested hash.
|
||||
*
|
||||
* @param {string} string
|
||||
* @return {hash}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.parseParams = function(string) {
|
||||
return exports.parseNestedParams($(string.split('&')).reduce({}, function(params, pair){
|
||||
pair = pair.split('=')
|
||||
params[pair[0]] = pair[1]
|
||||
return params
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
// Express - HTTP - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http'),
|
||||
utils = require('express/utils'),
|
||||
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} fn
|
||||
* @param {number} redirects
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function request(method, url, data, headers, fn, 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)
|
||||
fn(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, fn, redirects)
|
||||
else
|
||||
fn(new Error('maximum number of redirects reached'))
|
||||
else {
|
||||
res.setBodyEncoding('utf8')
|
||||
res
|
||||
.addListener('data', function(chunk){ buf += chunk })
|
||||
.addListener('end', function(){ fn(null, buf, res) })
|
||||
}
|
||||
})
|
||||
req.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(),
|
||||
fn = args.pop(),
|
||||
data = args.shift(),
|
||||
headers = args.shift()
|
||||
if (typeof fn !== 'function')
|
||||
throw new TypeError('http client requires a callback function')
|
||||
return request(method.toUpperCase(), url, data, headers, fn)
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ var style = require('express/pages/style').style
|
||||
exports.render = function(request) {
|
||||
request.contentType('html')
|
||||
var method = request.method.toLowerCase(),
|
||||
path = request.uri.path || '/'
|
||||
path = request.url.pathname || '/'
|
||||
return '<html> \n\
|
||||
<head> \n\
|
||||
<title>Express -- Not Found</title> \n\
|
||||
|
||||
@@ -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.uri.params) + ' \n\
|
||||
' + hash(request.params.get) + ' \n\
|
||||
</table> \n\
|
||||
<h3>POST</h3> \n\
|
||||
<table id="post-params"> \n\
|
||||
' + hash(request.uri.post) + ' \n\
|
||||
' + hash(request.params.post) + ' \n\
|
||||
</table> \n\
|
||||
</div> \n\
|
||||
</body> \n\
|
||||
|
||||
+17
-4
@@ -1,8 +1,16 @@
|
||||
|
||||
// Express - Plugin - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils');
|
||||
|
||||
/**
|
||||
* 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 +18,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
|
||||
@@ -30,7 +43,7 @@ exports.Plugin = Class({
|
||||
|
||||
init: function(options) {
|
||||
if (options)
|
||||
process.mixin(this, options)
|
||||
utils.mixin(this, options)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -40,9 +53,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,11 @@
|
||||
|
||||
// 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/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,6 +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: {
|
||||
|
||||
@@ -13,8 +21,8 @@ exports.BodyDecoder = Plugin.extend({
|
||||
request: function(event) {
|
||||
var request = event.request
|
||||
if (request.header('content-type') &&
|
||||
request.header('content-type').indexOf('application/x-www-form-urlencoded') > -1)
|
||||
request.params.post = parseParams(request.body)
|
||||
request.header('content-type').includes('application/x-www-form-urlencoded'))
|
||||
request.params.post = queryString.parseQuery(request.body)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,220 @@
|
||||
|
||||
// Express - Cache - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
// --- Cache
|
||||
|
||||
var Cache = Class({
|
||||
|
||||
/**
|
||||
* Initialize cache with _key_ and _val_.
|
||||
*/
|
||||
|
||||
init: function(key, val) {
|
||||
this.key = key
|
||||
this.val = val
|
||||
this.created = Number(new Date)
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store
|
||||
|
||||
exports.Store = Class({
|
||||
|
||||
/**
|
||||
* Ensure that the given _key_ is a string.
|
||||
* Override in subclass to provide data-store specific functionality.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set: function(key, val) {
|
||||
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
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get: function(key) {
|
||||
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.
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
this.data = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the given _key_ to _val_, returning _val_.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set: function(key, val) {
|
||||
this.__super__(key, val)
|
||||
return this.data[key] = new Cache(key, val), val
|
||||
},
|
||||
|
||||
/**
|
||||
* Get data found matching the given _key_.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* cache.get('page:front')
|
||||
* // => '<html>...</html>'
|
||||
*
|
||||
* cache.get('page:*')
|
||||
* // => { 'page:front': '<html>...</html>',
|
||||
* 'page:users': '<html>...</html>',
|
||||
* ... }
|
||||
*
|
||||
* @param {string} key
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get: function(key) {
|
||||
this.__super__(key)
|
||||
if (key.indexOf('*') === -1)
|
||||
return this.data[key] instanceof Cache ?
|
||||
this.data[key].val :
|
||||
null
|
||||
var regexp = this.normalize(key)
|
||||
return this.data.reduce(function(vals, cache){
|
||||
if (regexp.test(cache.key))
|
||||
vals[cache.key] = cache.val
|
||||
return vals
|
||||
}, {})
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear data matching the given _key_.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* cache.clear('page:front')
|
||||
* cache.clear('page:*')
|
||||
*
|
||||
* @param {string} key
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clear: function(key) {
|
||||
if (key.indexOf('*') === -1)
|
||||
return delete this.data[key]
|
||||
var regexp = this.normalize(key)
|
||||
for (var key in this.data)
|
||||
if (this.data.hasOwnProperty(key))
|
||||
if (regexp.test(key))
|
||||
delete this.data[key]
|
||||
},
|
||||
|
||||
/**
|
||||
* Reap caches older than _ms_.
|
||||
*
|
||||
* @param {int} ms
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms) {
|
||||
var self = this,
|
||||
threshold = Number(new Date(Number(new Date) - ms))
|
||||
this.data.each(function(cache){
|
||||
if (cache.created < threshold)
|
||||
self.clear(cache.key)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
startReaper: function() {
|
||||
var self = this,
|
||||
oneDay = 86400000,
|
||||
oneHour = 3600000
|
||||
setInterval(function(){
|
||||
self.store.reap(self.lifetime || oneDay)
|
||||
}, self.reapInterval || self.reapEvery || oneHour)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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.uri.path || '/') +
|
||||
' HTTP/' + event.request.httpVersion + '"',
|
||||
event.request.response.status,
|
||||
event.request.response.headers['content-length'] || 0].join(' '))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -10,11 +10,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 +29,14 @@ 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')
|
||||
return val === true ? key : key + '=' + val
|
||||
}).toArray().join('; ')
|
||||
}).join('; ')
|
||||
}
|
||||
|
||||
// --- Cookie
|
||||
@@ -56,12 +56,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 +73,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 +88,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)
|
||||
: {}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
// Express - Flash - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
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.
|
||||
*
|
||||
* 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 {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
flash: function(key, val) {
|
||||
if (!this.session.flash) this.session.flash = {}
|
||||
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,8 +1,11 @@
|
||||
|
||||
// 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.
|
||||
@@ -12,7 +15,7 @@ var before = [],
|
||||
*/
|
||||
|
||||
exports.before = function(fn) {
|
||||
before.push(fn)
|
||||
exports.callbacks.before.push(fn)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +26,7 @@ exports.before = function(fn) {
|
||||
*/
|
||||
|
||||
exports.after = function(fn) {
|
||||
after.push(fn)
|
||||
exports.callbacks.after.push(fn)
|
||||
}
|
||||
|
||||
// --- 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(fn){
|
||||
fn.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(fn){
|
||||
fn.call(event.request, event.request)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
// 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" %s %d %0.3f',
|
||||
event.request.connection.remoteAddress,
|
||||
(new Date).format('%d/%b/%Y %H:%M:%S'),
|
||||
event.request.method.uppercase,
|
||||
event.request.url.pathname || '/',
|
||||
event.request.httpVersion,
|
||||
event.request.response.status,
|
||||
event.request.response.headers['content-length'] || 0,
|
||||
(Number(new Date) - 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(Number(new Date) - 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 = Number(new Date)
|
||||
},
|
||||
|
||||
/**
|
||||
* Output log data.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
formats[exports.Logger.format || 'common'](event, this.start)
|
||||
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.uri.path + ': ' +
|
||||
(Number(new Date) - this.start) + ' ms')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -12,7 +12,7 @@ exports.Redirect = Plugin.extend({
|
||||
Request.include({
|
||||
|
||||
/**
|
||||
* Redirect to _uri_ with optional status _code_,
|
||||
* Redirect to _url_ with optional status _code_,
|
||||
* defaulting to 303 "See Other".
|
||||
*
|
||||
* When using redirect('home') the resolution
|
||||
@@ -26,14 +26,14 @@ exports.Redirect = Plugin.extend({
|
||||
* header is used. Commonly misspelled as 'referer'
|
||||
* which is supported as well.
|
||||
*
|
||||
* @param {string} uri
|
||||
* @param {string} url
|
||||
* @settings 'home', 'basepath'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
redirect: function(uri, code) {
|
||||
if (uri == 'back' || uri == 'home') uri = this[uri]
|
||||
this.header('location', uri)
|
||||
redirect: function(url, code) {
|
||||
if (url === 'back' || url === 'home') url = this[url]
|
||||
this.header('location', url)
|
||||
this.halt(code || 303)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
// Express - Session - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils')
|
||||
|
||||
// --- Session
|
||||
|
||||
var Session = Class({
|
||||
|
||||
/**
|
||||
* Initialize session _sid_.
|
||||
*/
|
||||
|
||||
init: function(sid) {
|
||||
this.id = sid
|
||||
this.touch()
|
||||
},
|
||||
|
||||
/**
|
||||
* Update last access time.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
touch: function() {
|
||||
this.lastAccess = Number(new Date)
|
||||
}
|
||||
})
|
||||
|
||||
// --- Store
|
||||
|
||||
exports.Store = 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.
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
this.store = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch session with the given _sid_ or
|
||||
* a new Session is returned.
|
||||
*
|
||||
* @param {int} sid
|
||||
* @return {Session}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
fetch: function(sid) {
|
||||
return this.store[sid] || new Session(sid)
|
||||
},
|
||||
|
||||
/**
|
||||
* Commit _session_ data.
|
||||
*
|
||||
* @param {Session} session
|
||||
* @api private
|
||||
*/
|
||||
|
||||
commit: function(session) {
|
||||
return this.store[session.id] = session
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all sessions.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clear: function() {
|
||||
this.store = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy session using the given _sid_.
|
||||
*
|
||||
* @param {int} sid
|
||||
* @api public
|
||||
*/
|
||||
|
||||
destroy: function(sid) {
|
||||
delete this.store[sid]
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the number of sessions currently stored.
|
||||
*
|
||||
* @return {int}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
length: function() {
|
||||
return this.store.values.length
|
||||
},
|
||||
|
||||
/**
|
||||
* Reap sessions older than _ms_.
|
||||
*
|
||||
* @param {int} ms
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms) {
|
||||
var threshold = Number(new Date(Number(new Date) - ms))
|
||||
this.store.each(function(session, sid){
|
||||
if (session.lastAccess < threshold)
|
||||
this.destroy(sid)
|
||||
}, this)
|
||||
}
|
||||
})
|
||||
|
||||
// --- 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
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
this.merge(options || {})
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
this.startReaper()
|
||||
},
|
||||
|
||||
/**
|
||||
* Start reaper.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
startReaper: function() {
|
||||
setInterval(function(){
|
||||
this.store.reap(this.lifetime || (1).day)
|
||||
}, this.reapInterval || this.reapEvery || (1).hour, this)
|
||||
}
|
||||
},
|
||||
|
||||
// --- Events
|
||||
|
||||
on: {
|
||||
|
||||
/**
|
||||
* Create session id when not found; delegate to store.
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
var sid
|
||||
if (!(sid = event.request.cookie('sid')))
|
||||
event.request.cookie('sid', sid = utils.uid(), set('session cookie'))
|
||||
event.request.session = exports.Session.store.fetch(sid)
|
||||
event.request.session.touch()
|
||||
},
|
||||
|
||||
/**
|
||||
* Delegate to store, allowing it to save sessions changes.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
exports.Session.store.commit(event.request.session)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,22 +1,19 @@
|
||||
|
||||
// Express - View - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var posix = require('posix')
|
||||
|
||||
/**
|
||||
* Template content cache.
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var cache = {}
|
||||
var utils = require('express/utils'),
|
||||
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/haml')
|
||||
}
|
||||
var engines = {}
|
||||
|
||||
// --- View
|
||||
|
||||
@@ -51,13 +48,14 @@ exports.View = Plugin.extend({
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @param {string} view
|
||||
* @param {hash} options
|
||||
* @settings 'views', 'cache views'
|
||||
* @settings 'views', 'cache view contents'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -67,12 +65,13 @@ exports.View = Plugin.extend({
|
||||
path = set('views') + '/' + view,
|
||||
type = path.split('.').slice(-2)[0],
|
||||
ext = extname(path),
|
||||
layout = options.layout === undefined ? true : options.layout
|
||||
layout = options.layout === undefined ? 'layout' : options.layout
|
||||
options.context = options.context || this
|
||||
self.contentType(ext)
|
||||
function render(content) {
|
||||
content = engine[type].render(content, options)
|
||||
content = (engines[type] = engines[type] || require(type)).render(content, options)
|
||||
if (layout)
|
||||
self.render('layout.' + type + '.' + ext, process.mixin(options, {
|
||||
self.render(layout + '.' + type + ext, utils.mixin(true, options, {
|
||||
layout: false,
|
||||
locals: {
|
||||
body: content
|
||||
@@ -81,13 +80,14 @@ exports.View = Plugin.extend({
|
||||
else
|
||||
self.halt(200, content)
|
||||
}
|
||||
if (set('cache views') && cache[view])
|
||||
render(cache[view])
|
||||
if (set('cache view contents') && self.cache.get(path))
|
||||
render(self.cache.get(path))
|
||||
else
|
||||
posix.cat(path).addCallback(function(content){
|
||||
render(cache[view] = content)
|
||||
}).addErrback(function(e){
|
||||
throw e
|
||||
fs.readFile(path, function(e, content){
|
||||
if (e) throw e
|
||||
set('cache view contents')
|
||||
? render(self.cache.set(path, content))
|
||||
: render(content)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
+106
-79
@@ -2,34 +2,15 @@
|
||||
// Express - Request - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* StaticFile for Request#sendfile()
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var StaticFile = require('express/static').File
|
||||
|
||||
/**
|
||||
* Default response code bodies.
|
||||
*/
|
||||
|
||||
var statusBodies = require('http').STATUS_CODES
|
||||
|
||||
// --- 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.uri.path) + ' did not respond with a body string'
|
||||
}
|
||||
})
|
||||
var StaticFile = require('express/static').File,
|
||||
statusBodies = require('http').STATUS_CODES,
|
||||
queryString = require('querystring'),
|
||||
mime = require('express/mime'),
|
||||
utils = require('express/utils'),
|
||||
url = require('url')
|
||||
|
||||
// --- Helpers
|
||||
|
||||
@@ -53,22 +34,28 @@ exports.Request = Class({
|
||||
/**
|
||||
* Initialize with node's _request_ and _response_ objects.
|
||||
*
|
||||
* - Defaults headers to {}
|
||||
* - Parses request url
|
||||
* - Normalizes url pathname
|
||||
* - Parses GET params when available
|
||||
* - Initializes plugins
|
||||
*
|
||||
* @param {object} request
|
||||
* @param {object} response
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(request, response) {
|
||||
process.mixin(true, this, request)
|
||||
utils.mixin(true, this, request)
|
||||
response.headers = {}
|
||||
this.response = response
|
||||
this.uri.path = exports.normalizePath(this.uri.path)
|
||||
this.params = {
|
||||
get: parseNestedParams(this.uri.params),
|
||||
post: {},
|
||||
path: {}
|
||||
}
|
||||
this.plugins = $(Express.plugins).map(function(plugin){
|
||||
this.url = url.parse(this.url)
|
||||
this.url.pathname = exports.normalizePath(this.url.pathname)
|
||||
this.params = 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)
|
||||
})
|
||||
},
|
||||
@@ -84,9 +71,9 @@ 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.toLowerCase()]
|
||||
: this.response.headers[key.toLowerCase()] = val
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -109,21 +96,32 @@ exports.Request = Class({
|
||||
|
||||
/**
|
||||
* 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')
|
||||
return accept
|
||||
? arguments.any(function(path){
|
||||
var type = mime.type(path)
|
||||
return accept.indexOf(type) !== -1 ||
|
||||
accept.indexOf(type.split('/')[0]+'/*') !== -1
|
||||
})
|
||||
: true
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -145,9 +143,6 @@ exports.Request = Class({
|
||||
* default to the default body associated with the response
|
||||
* _code_.
|
||||
*
|
||||
* When an invalid status _code_ is passed, InvalidStatusCode
|
||||
* will be thrown.
|
||||
*
|
||||
* @param {int} code
|
||||
* @param {string} body
|
||||
* @param {string} encoding
|
||||
@@ -155,11 +150,10 @@ exports.Request = Class({
|
||||
* @api public
|
||||
*/
|
||||
|
||||
halt: function(code, body, encoding) {
|
||||
halt: function(code, body, encoding, callback) {
|
||||
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)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -171,48 +165,81 @@ 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(e) {
|
||||
if (e)
|
||||
if (callback !== undefined) callback(e)
|
||||
else throw e
|
||||
self.response.writeHeader(self.response.status, self.response.headers)
|
||||
self.response.write(self.response.body, encoding)
|
||||
self.response.close()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Content-Type header to the mime type
|
||||
* for the given _path_, which calls mime().
|
||||
* Pass control to the next matching route, or
|
||||
* the given _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {Request}
|
||||
* @see mime()
|
||||
* NOTE: _path_ may be the request pathname only,
|
||||
* and may not contain a query string etc.
|
||||
*
|
||||
* @param {string} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
contentType: function(path) {
|
||||
this.header('content-type', mime(path))
|
||||
|
||||
pass: function(path) {
|
||||
this.passed = path || true
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger even _name_ with optional _data_.
|
||||
* Set Content-Type header to the mime type
|
||||
* for the given _path_, which calls mime.type().
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {hash} data
|
||||
* @param {string} path
|
||||
* @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))
|
||||
})
|
||||
contentType: function(path) {
|
||||
this.header('content-type', mime.type(path))
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger event _name_ with optional _data_ and _callback_ function.
|
||||
* The _callback_ function may be the second or third argument.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {object} data
|
||||
* @param {function} callback
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
trigger: function(name, data, callback) {
|
||||
if (data instanceof Function)
|
||||
callback = data,
|
||||
data = null
|
||||
data = data || {}
|
||||
data.merge({ request: this, response: this.response })
|
||||
var self = this,
|
||||
complete = 0,
|
||||
total = this.plugins.length
|
||||
;(function next(e) {
|
||||
if (e || complete === total)
|
||||
callback(e)
|
||||
else {
|
||||
try {
|
||||
if (self.plugins.at(complete++).trigger(new Event(name, data), next) !== true)
|
||||
next()
|
||||
} catch(e) {
|
||||
next(e)
|
||||
}
|
||||
}
|
||||
})()
|
||||
return this
|
||||
},
|
||||
|
||||
@@ -239,9 +266,9 @@ 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
|
||||
},
|
||||
|
||||
|
||||
+11
-11
@@ -1,6 +1,12 @@
|
||||
|
||||
// Express - MockRequest - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils')
|
||||
|
||||
// --- MockRequest
|
||||
|
||||
var MockRequest = Class({
|
||||
@@ -22,16 +28,10 @@ var MockRequest = Class({
|
||||
|
||||
init: function(method, path, options) {
|
||||
this.method = method
|
||||
this.url = path
|
||||
this.connection = {
|
||||
remoteAddress: '127.0.0.1'
|
||||
}
|
||||
this.uri = {
|
||||
queryString: '',
|
||||
fragment: '',
|
||||
params: {},
|
||||
path: path,
|
||||
full: path
|
||||
}
|
||||
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',
|
||||
@@ -39,7 +39,7 @@ var MockRequest = Class({
|
||||
'accept-language': 'en-us',
|
||||
'connection': 'keep-alive'
|
||||
}
|
||||
process.mixin(true, this, options)
|
||||
utils.mixin(true, this, options)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ var MockResponse = Class({
|
||||
* Store _code_ and _headers_.
|
||||
*/
|
||||
|
||||
sendHeader: function(code, headers) {
|
||||
writeHeader: function(code, headers) {
|
||||
this.status = code
|
||||
this.headers = headers
|
||||
},
|
||||
@@ -60,7 +60,7 @@ var MockResponse = Class({
|
||||
* Store _body_.
|
||||
*/
|
||||
|
||||
sendBody: function(body) {
|
||||
write: function(body) {
|
||||
this.body = body
|
||||
},
|
||||
|
||||
@@ -68,7 +68,7 @@ var MockResponse = Class({
|
||||
* Flag response as finished.
|
||||
*/
|
||||
|
||||
finish: function() {
|
||||
close: function() {
|
||||
this.finished = true
|
||||
}
|
||||
})
|
||||
|
||||
+22
-17
@@ -1,18 +1,13 @@
|
||||
|
||||
// Express - Static - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var path = require('path'),
|
||||
posix = require('posix')
|
||||
|
||||
// --- InvalidPathError
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
InvalidPathError = ExpressError.extend({
|
||||
name: 'InvalidPathError',
|
||||
init: function(path) {
|
||||
this.message = "`" + path + "' is not a valid path"
|
||||
}
|
||||
})
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs')
|
||||
|
||||
// --- File
|
||||
|
||||
exports.File = Class({
|
||||
@@ -26,7 +21,8 @@ exports.File = Class({
|
||||
|
||||
init: function(path) {
|
||||
this.path = path
|
||||
if (path.indexOf('..') != -1) throw new InvalidPathError(path)
|
||||
if (path.indexOf('..') != -1)
|
||||
Error.raise('InvalidPathError', "`" + path + "' is not a valid path")
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -35,19 +31,28 @@ exports.File = Class({
|
||||
* - Ensures the file exists
|
||||
* - Ensures the file is a regular file (not FIFO, Socket, etc)
|
||||
* - Automatically assigns content type
|
||||
* - Halts with 404 when failing
|
||||
*
|
||||
* @param {Request} request
|
||||
* @settings 'cache static files'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
send: function(request) {
|
||||
var file = this.path
|
||||
var cache, file = this.path
|
||||
if (set('cache static files') && (cache = request.cache.get(file)))
|
||||
return request.contentType(cache.type),
|
||||
request.halt(200, cache.content, 'binary')
|
||||
path.exists(file, function(exists){
|
||||
if (!exists) request.halt()
|
||||
posix.stat(file).addCallback(function(stats){
|
||||
if (!stats.isFile()) request.halt()
|
||||
posix.cat(file, 'binary').addCallback(function(content){
|
||||
if (!exists) return request.halt()
|
||||
fs.stat(file, function(e, stats){
|
||||
if (e) throw e
|
||||
if (!stats.isFile()) return request.halt()
|
||||
fs.readFile(file, 'binary', function(e, content){
|
||||
if (e) throw e
|
||||
request.contentType(file)
|
||||
if (set('cache static files'))
|
||||
request.cache.set(file, { type: file, content: content })
|
||||
request.halt(200, content, 'binary')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a clone of _obj_.
|
||||
*
|
||||
* @param {mixed} obj
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Clone() {}
|
||||
exports.clone = function(obj) {
|
||||
Clone.prototype = obj
|
||||
return new Clone
|
||||
}
|
||||
|
||||
// From jQuery.extend in the jQuery JavaScript Library v1.3.2
|
||||
// Copyright (c) 2009 John Resig
|
||||
// Dual licensed under the MIT and GPL licenses.
|
||||
// http://docs.jquery.com/License
|
||||
// Modified for node.js (formely for copying properties correctly)
|
||||
exports.mixin = function() {
|
||||
// copy reference to target object
|
||||
var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, source;
|
||||
|
||||
// Handle a deep copy situation
|
||||
if ( typeof target === "boolean" ) {
|
||||
deep = target;
|
||||
target = arguments[1] || {};
|
||||
// skip the boolean and the target
|
||||
i = 2;
|
||||
}
|
||||
|
||||
// Handle case when target is a string or something (possible in deep copy)
|
||||
if ( typeof target !== "object" && !(typeof target === 'function') )
|
||||
target = {};
|
||||
|
||||
// mixin process itself if only one argument is passed
|
||||
if ( length == i ) {
|
||||
target = GLOBAL;
|
||||
--i;
|
||||
}
|
||||
|
||||
for ( ; i < length; i++ ) {
|
||||
// Only deal with non-null/undefined values
|
||||
if ( (source = arguments[i]) != null ) {
|
||||
// Extend the base object
|
||||
Object.getOwnPropertyNames(source).forEach(function(k){
|
||||
var d = Object.getOwnPropertyDescriptor(source, k) || {value: source[k]};
|
||||
if (d.get) {
|
||||
target.__defineGetter__(k, d.get);
|
||||
if (d.set) {
|
||||
target.__defineSetter__(k, d.set);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Prevent never-ending loop
|
||||
if (target !== d.value) {
|
||||
|
||||
if (deep && d.value && typeof d.value === "object") {
|
||||
target[k] = exports.mixin(deep,
|
||||
// Never move original objects, clone them
|
||||
target[k] || (d.value.length != null ? [] : {})
|
||||
, d.value);
|
||||
}
|
||||
else {
|
||||
target[k] = d.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Return the modified object
|
||||
return target;
|
||||
};
|
||||
@@ -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 967039b7d6
+1
-1
Submodule lib/support/haml updated: bb25260227...389c33c6e4
Submódulo
+1
Submodule lib/support/sass added at 2a648b3766
@@ -0,0 +1,4 @@
|
||||
---
|
||||
name: Express
|
||||
description: Sinatra inspired web development framework
|
||||
version: 0.7.2
|
||||
Arquivo binário não exibido.
|
Antes Largura: | Altura: | Tamanho: 8.4 KiB Depois Largura: | Altura: | Tamanho: 3.5 KiB |
@@ -114,12 +114,12 @@ body.jspec {
|
||||
background: url(images/sprites.png) 3px -7px no-repeat;
|
||||
}
|
||||
#jspec-report td.fail {
|
||||
background: url(images/sprites.png) 3px -47px no-repeat;
|
||||
background: url(images/sprites.png) 3px -158px no-repeat;
|
||||
font-weight: bold;
|
||||
color: #FC0D0D;
|
||||
}
|
||||
#jspec-report td.requires-implementation {
|
||||
background: url(images/sprites.png) 3px -87px no-repeat;
|
||||
background: url(images/sprites.png) 3px -333px no-repeat;
|
||||
}
|
||||
#jspec-report tr.description td {
|
||||
margin-top: 25px;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
|
||||
// JSpec - Growl - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
;(function(){
|
||||
|
||||
Growl = {
|
||||
|
||||
// --- Version
|
||||
|
||||
version: '1.0.0',
|
||||
|
||||
/**
|
||||
* Execute the given _cmd_, returning an array of lines from stdout.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Growl.exec('growlnotify', '-m', msg)
|
||||
*
|
||||
* @param {string ...} cmd
|
||||
* @return {array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exec: function(cmd) {
|
||||
var lines = [], line
|
||||
with (JavaImporter(java.lang, java.io)) {
|
||||
var proccess = Runtime.getRuntime().exec(Array.prototype.slice.call(arguments))
|
||||
var stream = new DataInputStream(proccess.getInputStream())
|
||||
while (line = stream.readLine())
|
||||
lines.push(line + '')
|
||||
stream.close()
|
||||
}
|
||||
return lines
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the extension of the given _path_ or null.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
extname: function(path) {
|
||||
return path.lastIndexOf('.') != -1 ?
|
||||
path.slice(path.lastIndexOf('.') + 1, path.length) :
|
||||
null
|
||||
},
|
||||
|
||||
/**
|
||||
* Version of the 'growlnotify' binary.
|
||||
*
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
binVersion: function() {
|
||||
try { return this.exec('growlnotify', '-v')[0].split(' ')[1] } catch (e) {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Send growl notification _msg_ with _options_.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - title Notification title
|
||||
* - sticky Make the notification stick (defaults to false)
|
||||
* - name Application name (defaults to growlnotify)
|
||||
* - image
|
||||
* - path to an icon sets --iconpath
|
||||
* - path to an image sets --image
|
||||
* - capitalized word sets --appIcon
|
||||
* - filename uses extname as --icon
|
||||
* - otherwise treated as --icon
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Growl.notify('New email')
|
||||
* Growl.notify('5 new emails', { title: 'Thunderbird' })
|
||||
*
|
||||
* @param {string} msg
|
||||
* @param {options} hash
|
||||
* @api public
|
||||
*/
|
||||
|
||||
notify: function(msg, options) {
|
||||
options = options || {}
|
||||
var args = ['growlnotify', '-m', msg]
|
||||
if (!this.binVersion()) throw new Error('growlnotify executable is required')
|
||||
if (image = options.image) {
|
||||
var flag, ext = this.extname(image)
|
||||
flag = flag || ext == 'icns' && 'iconpath'
|
||||
flag = flag || /^[A-Z]/.test(image) && 'appIcon'
|
||||
flag = flag || /^png|gif|jpe?g$/.test(ext) && 'image'
|
||||
flag = flag || ext && (image = ext) && 'icon'
|
||||
flag = flag || 'icon'
|
||||
args.push('--' + flag, image)
|
||||
}
|
||||
if (options.sticky) args.push('--sticky')
|
||||
if (options.name) args.push('--name', options.name)
|
||||
if (options.title) args.push(options.title)
|
||||
this.exec.apply(this, args)
|
||||
}
|
||||
}
|
||||
|
||||
JSpec.include({
|
||||
name: 'Growl',
|
||||
reporting: function(options){
|
||||
var stats = JSpec.stats
|
||||
if (stats.failures) Growl.notify('failed ' + stats.failures + ' assertions', { title: 'JSpec'})
|
||||
else Growl.notify('passed ' + stats.passes + ' assertions', { title: 'JSpec' })
|
||||
}
|
||||
})
|
||||
|
||||
})()
|
||||
@@ -47,7 +47,7 @@ JSpec
|
||||
},
|
||||
|
||||
have_classes : function(actual) {
|
||||
return !JSpec.any(JSpec.argumentsToArray(arguments, 1), function(arg){
|
||||
return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){
|
||||
return !JSpec.does(actual, 'have_class', arg)
|
||||
})
|
||||
},
|
||||
|
||||
+102
-105
@@ -1,20 +1,20 @@
|
||||
|
||||
// JSpec - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
(function(){
|
||||
;(function(){
|
||||
|
||||
JSpec = {
|
||||
|
||||
version : '2.11.13',
|
||||
version : '3.2.1',
|
||||
assert : true,
|
||||
cache : {},
|
||||
suites : [],
|
||||
modules : [],
|
||||
allSuites : [],
|
||||
matchers : {},
|
||||
stubbed : [],
|
||||
options : {},
|
||||
request : 'XMLHttpRequest' in this ? XMLHttpRequest : null,
|
||||
stats : { specs: 0, assertions: 0, failures: 0, passes: 0, specsFinished: 0, suitesFinished: 0 },
|
||||
options : { profile: false },
|
||||
|
||||
/**
|
||||
* Default context in which bodies are evaluated.
|
||||
@@ -51,15 +51,12 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Load fixture at _path_. This utility function
|
||||
* supplies the means to resolve, and cache fixture contents
|
||||
* via the DOM or Rhino.
|
||||
* Load fixture at _path_.
|
||||
*
|
||||
* Fixtures are resolved as:
|
||||
*
|
||||
* - <path>
|
||||
* - fixtures/<path>
|
||||
* - fixtures/<path>.html
|
||||
* - <path>.html
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
@@ -69,18 +66,14 @@
|
||||
fixture : function(path) {
|
||||
if (JSpec.cache[path]) return JSpec.cache[path]
|
||||
return JSpec.cache[path] =
|
||||
JSpec.tryLoading(path) ||
|
||||
JSpec.tryLoading('fixtures/' + path) ||
|
||||
JSpec.tryLoading('fixtures/' + path + '.html') ||
|
||||
JSpec.tryLoading('spec/' + path) ||
|
||||
JSpec.tryLoading('spec/fixtures/' + path) ||
|
||||
JSpec.tryLoading('spec/fixtures/' + path + '.html')
|
||||
JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) ||
|
||||
JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.html')
|
||||
}
|
||||
},
|
||||
|
||||
// --- Objects
|
||||
|
||||
formatters : {
|
||||
reporters : {
|
||||
|
||||
/**
|
||||
* Report to server.
|
||||
@@ -123,7 +116,7 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Default formatter, outputting to the DOM.
|
||||
* Default reporter, outputting to the DOM.
|
||||
*
|
||||
* Options:
|
||||
* - reportToId id of element to output reports to, defaults to 'jspec'
|
||||
@@ -149,6 +142,7 @@
|
||||
report.innerHTML = '<div id="jspec-report" class="' + classes + '"><div class="heading"> \
|
||||
<span class="passes">Passes: <em>' + results.stats.passes + '</em></span> \
|
||||
<span class="failures">Failures: <em>' + results.stats.failures + '</em></span> \
|
||||
<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())
|
||||
@@ -170,7 +164,7 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Terminal formatter.
|
||||
* Terminal reporter.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
@@ -178,7 +172,8 @@
|
||||
Terminal : function(results, options) {
|
||||
failuresOnly = option('failuresOnly')
|
||||
print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
|
||||
color(" Failures: ", 'bold') + color(results.stats.failures, 'red') + "\n")
|
||||
color(" Failures: ", 'bold') + color(results.stats.failures, 'red') +
|
||||
color(" Duration: ", 'bold') + color(results.duration, 'green') + " ms \n")
|
||||
|
||||
function indent(string) {
|
||||
return string.replace(/^(.)/gm, ' $1')
|
||||
@@ -208,7 +203,7 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Console formatter.
|
||||
* Console reporter.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
@@ -246,7 +241,8 @@
|
||||
// Report assertion results
|
||||
|
||||
report : function() {
|
||||
this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
|
||||
if (JSpec.assert)
|
||||
this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
|
||||
return this
|
||||
},
|
||||
|
||||
@@ -270,7 +266,7 @@
|
||||
// Proxy
|
||||
|
||||
object[method] = function(){
|
||||
args = argumentsToArray(arguments)
|
||||
args = toArray(arguments)
|
||||
result = old.apply(object, args)
|
||||
self.calls.push({ args : args, result : result })
|
||||
return result
|
||||
@@ -302,7 +298,7 @@
|
||||
// Proxy arguments passed
|
||||
|
||||
with_args : function() {
|
||||
this.expectedArgs = argumentsToArray(arguments)
|
||||
this.expectedArgs = toArray(arguments)
|
||||
return this
|
||||
},
|
||||
|
||||
@@ -312,7 +308,7 @@
|
||||
return any(this.calls, function(call){
|
||||
return self.expectedResult.an_instance_of ?
|
||||
call.result.constructor != self.expectedResult.an_instance_of:
|
||||
hash(self.expectedResult) != hash(call.result)
|
||||
!equal(self.expectedResult, call.result)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -322,7 +318,7 @@
|
||||
return any(this.calls, function(call){
|
||||
return self.expectedResult.an_instance_of ?
|
||||
call.result.constructor == self.expectedResult.an_instance_of:
|
||||
hash(self.expectedResult) == hash(call.result)
|
||||
equal(self.expectedResult, call.result)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -346,7 +342,7 @@
|
||||
if (arg == null) return call.args[i] == null
|
||||
return arg.an_instance_of ?
|
||||
call.args[i].constructor != arg.an_instance_of:
|
||||
hash(arg) != hash(call.args[i])
|
||||
!equal(arg, call.args[i])
|
||||
|
||||
})
|
||||
})
|
||||
@@ -359,7 +355,7 @@
|
||||
return any(self.expectedArgs, function(i, arg){
|
||||
return arg.an_instance_of ?
|
||||
call.args[i].constructor == arg.an_instance_of:
|
||||
hash(arg) == hash(call.args[i])
|
||||
equal(arg, call.args[i])
|
||||
|
||||
})
|
||||
})
|
||||
@@ -380,7 +376,8 @@
|
||||
// Report assertion results
|
||||
|
||||
report : function() {
|
||||
this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures
|
||||
if (JSpec.assert)
|
||||
this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures
|
||||
return this
|
||||
},
|
||||
|
||||
@@ -506,14 +503,14 @@
|
||||
|
||||
pass : function(message) {
|
||||
this.assertions.push({ passed: true, message: message })
|
||||
++JSpec.stats.passes
|
||||
if (JSpec.assert) ++JSpec.stats.passes
|
||||
},
|
||||
|
||||
// Add failing assertion
|
||||
|
||||
fail : function(message) {
|
||||
this.assertions.push({ passed: false, message: message })
|
||||
++JSpec.stats.failures
|
||||
if (JSpec.assert) ++JSpec.stats.failures
|
||||
},
|
||||
|
||||
// Run deferred assertions
|
||||
@@ -679,7 +676,7 @@
|
||||
/**
|
||||
* Include _object_ which may be a hash or Module instance.
|
||||
*
|
||||
* @param {has, Module} object
|
||||
* @param {hash, Module} object
|
||||
* @return {JSpec}
|
||||
* @api public
|
||||
*/
|
||||
@@ -690,7 +687,7 @@
|
||||
if ('init' in module) module.init()
|
||||
if ('utilities' in module) extend(this.defaultContext, module.utilities)
|
||||
if ('matchers' in module) this.addMatchers(module.matchers)
|
||||
if ('formatters' in module) extend(this.formatters, module.formatters)
|
||||
if ('reporters' in module) extend(this.reporters, module.reporters)
|
||||
if ('DSLs' in module)
|
||||
each(module.DSLs, function(name, methods){
|
||||
JSpec.DSLs[name] = JSpec.DSLs[name] || {}
|
||||
@@ -711,7 +708,7 @@
|
||||
*/
|
||||
|
||||
hook : function(name, args) {
|
||||
args = argumentsToArray(arguments, 1)
|
||||
args = toArray(arguments, 1)
|
||||
return inject(JSpec.modules, [], function(results, module){
|
||||
if (typeof module[name] == 'function')
|
||||
results.push(JSpec.evalHook(module, name, args))
|
||||
@@ -805,7 +802,7 @@
|
||||
* @api public
|
||||
*/
|
||||
|
||||
argumentsToArray : function(arguments, offset) {
|
||||
toArray : function(arguments, offset) {
|
||||
return Array.prototype.slice.call(arguments, offset || 0)
|
||||
},
|
||||
|
||||
@@ -842,7 +839,9 @@
|
||||
return 'expected ' + puts(actual) + ' to ' +
|
||||
(negate ? 'not ' : '') +
|
||||
name.replace(/_/g, ' ') +
|
||||
' ' + puts.apply(this, expected.slice(1))
|
||||
' ' + (expected.length > 1 ?
|
||||
puts.apply(this, expected.slice(1)) :
|
||||
'')
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -907,34 +906,35 @@
|
||||
return (value = query(key)) !== null ? value :
|
||||
JSpec.options[key] || null
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates a hash of the object passed.
|
||||
*
|
||||
* @param {object} object
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
hash : function(object) {
|
||||
if (object == null) return 'null'
|
||||
if (object == undefined) return 'undefined'
|
||||
function serialize(prefix) {
|
||||
return inject(object, prefix + ':', function(buffer, key, value){
|
||||
return buffer += hash(value)
|
||||
})
|
||||
}
|
||||
switch (object.constructor) {
|
||||
case Array : return serialize('a')
|
||||
case RegExp: return 'r:' + object.toString()
|
||||
case Number: return 'n:' + object.toString()
|
||||
case String: return 's:' + object.toString()
|
||||
case Object: return 'o:' + inject(object, [], function(array, key, value){
|
||||
array.push([key, hash(value)])
|
||||
}).sort()
|
||||
default: return object.toString()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if object _a_, is equal to object _b_.
|
||||
*
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @return {bool}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
equal: function(a, b) {
|
||||
if (typeof a != typeof b) return
|
||||
if (a === b) return true
|
||||
if (a instanceof RegExp)
|
||||
return a.toString() === b.toString()
|
||||
if (a instanceof Date)
|
||||
return Number(a) === Number(b)
|
||||
if (typeof a != 'object') return
|
||||
if (a.length !== undefined)
|
||||
if (a.length !== b.length) return
|
||||
else
|
||||
for (var i = 0, len = a.length; i < len; ++i)
|
||||
if (!equal(a[i], b[i]))
|
||||
return
|
||||
for (var key in a)
|
||||
if (!equal(a[key], b[key]))
|
||||
return
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* Return last element of an array.
|
||||
@@ -957,30 +957,35 @@
|
||||
*/
|
||||
|
||||
puts : function(object) {
|
||||
if (arguments.length > 1) {
|
||||
return map(argumentsToArray(arguments), function(arg){
|
||||
if (arguments.length > 1)
|
||||
return map(toArray(arguments), function(arg){
|
||||
return puts(arg)
|
||||
}).join(', ')
|
||||
}
|
||||
if (object === undefined) return ''
|
||||
if (object === undefined) return 'undefined'
|
||||
if (object === null) return 'null'
|
||||
if (object === true) return 'true'
|
||||
if (object === false) return 'false'
|
||||
if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name
|
||||
if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector) + ''
|
||||
if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector)
|
||||
if (object.jquery) return object.get(0).outerHTML
|
||||
if (object.nodeName) return object.outerHTML
|
||||
switch (object.constructor) {
|
||||
case String: return "'" + object + "'"
|
||||
case Number: return object
|
||||
case Function: return object.name || object
|
||||
case String:
|
||||
return '"' + object
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\t/g, '\\t')
|
||||
+ '"'
|
||||
case Array:
|
||||
return inject(object, '[', function(b, v){
|
||||
return b + ', ' + puts(v)
|
||||
}).replace('[,', '[') + ' ]'
|
||||
case Object:
|
||||
object.__hit__ = true
|
||||
return inject(object, '{', function(b, k, v) {
|
||||
return b + ', ' + puts(k) + ' : ' + puts(v)
|
||||
if (k == '__hit__') return b
|
||||
return b + ', ' + k + ': ' + (v && v.__hit__ ? '<circular reference>' : puts(v))
|
||||
}).replace('{,', '{') + ' }'
|
||||
default:
|
||||
return object.toString()
|
||||
@@ -1024,7 +1029,7 @@
|
||||
*/
|
||||
|
||||
does : function(actual, matcher, expected) {
|
||||
var assertion = new JSpec.Assertion(JSpec.matchers[matcher], actual, argumentsToArray(arguments, 2))
|
||||
var assertion = new JSpec.Assertion(JSpec.matchers[matcher], actual, toArray(arguments, 2))
|
||||
return assertion.run().result
|
||||
},
|
||||
|
||||
@@ -1041,8 +1046,8 @@
|
||||
*/
|
||||
|
||||
expect : function(actual) {
|
||||
assert = function(matcher, args, negate) {
|
||||
var expected = argumentsToArray(args, 1)
|
||||
function assert(matcher, args, negate) {
|
||||
var expected = toArray(args, 1)
|
||||
matcher.negate = negate
|
||||
assertion = new JSpec.Assertion(matcher, actual, expected, negate)
|
||||
hook('beforeAssertion', assertion)
|
||||
@@ -1051,11 +1056,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)
|
||||
}
|
||||
|
||||
@@ -1114,17 +1119,20 @@
|
||||
/**
|
||||
* Iterate an object, invoking the given callback.
|
||||
*
|
||||
* @param {hash, array, string} object
|
||||
* @param {hash, array} object
|
||||
* @param {function} callback
|
||||
* @return {JSpec}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
each : function(object, callback) {
|
||||
if (typeof object == 'string') object = object.split(' ')
|
||||
for (key in object)
|
||||
if (object.hasOwnProperty(key))
|
||||
callIterator(callback, key, object[key])
|
||||
if (object.constructor == Array)
|
||||
for (var i = 0, len = object.length; i < len; ++i)
|
||||
callIterator(callback, i, object[i])
|
||||
else
|
||||
for (var key in object)
|
||||
if (object.hasOwnProperty(key))
|
||||
callIterator(callback, key, object[key])
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1326,7 +1334,7 @@
|
||||
var context = this.context || this.defaultContext
|
||||
var contents = this.contentsOf(body)
|
||||
hook('evaluatingBody', dsl, matchers, context, contents)
|
||||
try { eval('with (dsl){ with (context) { with (matchers) { ' + contents + ' }}}') }
|
||||
try { with (dsl){ with (context) { with (matchers) { eval(contents) }}} }
|
||||
catch(e) { error(errorMessage, e) }
|
||||
},
|
||||
|
||||
@@ -1344,7 +1352,7 @@
|
||||
return input.
|
||||
replace(/\t/g, ' ').
|
||||
replace(/\r\n|\n|\r/g, '\n').
|
||||
replace(/__END__[^]*/, '').
|
||||
split('__END__')[0].
|
||||
replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)').
|
||||
replace(/describe\s+(.*?)$/gm, 'describe($1, function(){').
|
||||
replace(/^\s+it\s+(.*?)$/gm, ' it($1, function(){').
|
||||
@@ -1381,8 +1389,9 @@
|
||||
*/
|
||||
|
||||
report : function() {
|
||||
this.duration = Number(new Date) - this.start
|
||||
hook('reporting', JSpec.options)
|
||||
new (JSpec.options.formatter || JSpec.formatters.DOM)(JSpec, JSpec.options)
|
||||
new (JSpec.options.reporter || JSpec.reporters.DOM)(JSpec, JSpec.options)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1397,9 +1406,8 @@
|
||||
run : function(options) {
|
||||
if (any(hook('running'), haveStopped)) return this
|
||||
if (options) extend(this.options, options)
|
||||
if (option('profile')) console.group('Profile')
|
||||
this.start = Number(new Date)
|
||||
each(this.suites, function(suite) { JSpec.runSuite(suite) })
|
||||
if (option('profile')) console.groupEnd()
|
||||
return this
|
||||
},
|
||||
|
||||
@@ -1462,10 +1470,8 @@
|
||||
|
||||
runSpec : function(spec) {
|
||||
this.currentSpec = spec
|
||||
if (option('profile')) console.time(spec.description)
|
||||
try { this.evalBody(spec.body) }
|
||||
catch (e) { fail(e) }
|
||||
if (option('profile')) console.timeEnd(spec.description)
|
||||
spec.runDeferredAssertions()
|
||||
destub()
|
||||
this.stats.specsFinished++
|
||||
@@ -1588,8 +1594,7 @@
|
||||
*/
|
||||
|
||||
tryLoading : function(file) {
|
||||
try { return JSpec.load(file) }
|
||||
catch (e) {}
|
||||
try { return JSpec.load(file) } catch (e) {}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1638,14 +1643,15 @@
|
||||
var main = this
|
||||
var find = JSpec.any
|
||||
var utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \
|
||||
error escape extend puts hash query strip color does addMatchers callIterator argumentsToArray'.split(/\s+/)
|
||||
while (utils.length) util = utils.shift(), eval('var ' + util + ' = JSpec.' + util)
|
||||
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() }
|
||||
|
||||
// --- Matchers
|
||||
|
||||
addMatchers({
|
||||
equal : "===",
|
||||
eql : "equal(actual, expected)",
|
||||
be : "alias equal",
|
||||
be_greater_than : ">",
|
||||
be_less_than : "<",
|
||||
@@ -1664,13 +1670,6 @@
|
||||
have_length : "actual.length == expected",
|
||||
be_within : "actual >= expected[0] && actual <= last(expected)",
|
||||
have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)",
|
||||
|
||||
eql : function(actual, expected) {
|
||||
return actual.constructor == Array ||
|
||||
actual instanceof Object ?
|
||||
hash(actual) == hash(expected):
|
||||
actual == expected
|
||||
},
|
||||
|
||||
receive : { defer : true, match : function(actual, method, times) {
|
||||
proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
|
||||
@@ -1693,7 +1692,7 @@
|
||||
case Number:
|
||||
case RegExp:
|
||||
case Function:
|
||||
state = actual.toString().match(arg.toString())
|
||||
state = actual.toString().indexOf(arg) !== -1
|
||||
break
|
||||
|
||||
case Object:
|
||||
@@ -1701,7 +1700,7 @@
|
||||
break
|
||||
|
||||
case Array:
|
||||
state = any(actual, function(value){ return hash(value) == hash(arg) })
|
||||
state = any(actual, function(value){ return equal(value, arg) })
|
||||
break
|
||||
}
|
||||
if (!state) return false
|
||||
@@ -1715,9 +1714,9 @@
|
||||
this.e = e
|
||||
var assert = function(arg) {
|
||||
switch (arg.constructor) {
|
||||
case RegExp : return arg.test(e)
|
||||
case RegExp : return arg.test(e.message || e.toString())
|
||||
case String : return arg == (e.message || e.toString())
|
||||
case Function : return (e.name || 'Error') == arg.name
|
||||
case Function : return e instanceof arg || e.name == arg.name
|
||||
}
|
||||
}
|
||||
return message ? assert(expected) && assert(message) :
|
||||
@@ -1771,6 +1770,4 @@
|
||||
}
|
||||
})
|
||||
|
||||
if ('exports' in main) exports.JSpec = JSpec
|
||||
|
||||
})()
|
||||
@@ -15,7 +15,8 @@
|
||||
|
||||
commands: {
|
||||
quit: ['Terminate the shell', function(){ _quit() }],
|
||||
exit: ['Terminate the shell', function(){ _quit() }]
|
||||
exit: ['Terminate the shell', function(){ _quit() }],
|
||||
p: ['Inspect an object', function(o){ return o.toSource() }]
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -27,7 +28,9 @@
|
||||
start : function() {
|
||||
for (var name in this.commands)
|
||||
if (this.commands.hasOwnProperty(name))
|
||||
this.main.__defineGetter__(name, this.commands[name][1])
|
||||
this.commands[name][1].length ?
|
||||
this.main[name] = this.commands[name][1] :
|
||||
this.main.__defineGetter__(name, this.commands[name][1])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
// Mock Timers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
// JSpec - Mock Timers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
;(function(){
|
||||
|
||||
|
||||
+12
-2
@@ -8,7 +8,10 @@
|
||||
var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
|
||||
XMLHttpRequest :
|
||||
function(){}
|
||||
|
||||
var OriginalActiveXObject = 'ActiveXObject' in this ?
|
||||
ActiveXObject :
|
||||
undefined
|
||||
|
||||
// --- MockXMLHttpRequest
|
||||
|
||||
var MockXMLHttpRequest = function() {
|
||||
@@ -66,11 +69,15 @@
|
||||
*/
|
||||
|
||||
send : function(data) {
|
||||
var self = this
|
||||
this.data = data
|
||||
this.readyState = 4
|
||||
if (this.method == 'HEAD') this.responseText = null
|
||||
this.responseHeaders['content-length'] = (this.responseText || '').length
|
||||
if(this.async) this.onreadystatechange()
|
||||
lastRequest = function(){
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +139,7 @@
|
||||
function mockRequest() {
|
||||
return { and_return : function(body, type, status, headers) {
|
||||
XMLHttpRequest = MockXMLHttpRequest
|
||||
ActiveXObject = false
|
||||
status = status || 200
|
||||
headers = headers || {}
|
||||
headers['content-type'] = type
|
||||
@@ -152,6 +160,7 @@
|
||||
|
||||
function unmockRequest() {
|
||||
XMLHttpRequest = OriginalXMLHttpRequest
|
||||
ActiveXObject = OriginalActiveXObject
|
||||
}
|
||||
|
||||
JSpec.include({
|
||||
@@ -175,7 +184,8 @@
|
||||
DSLs : {
|
||||
snake : {
|
||||
mock_request: mockRequest,
|
||||
unmock_request: unmockRequest
|
||||
unmock_request: unmockRequest,
|
||||
last_request: function(){ return lastRequest() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
require.paths.unshift('spec', 'lib', 'spec/lib')
|
||||
require("jspec")
|
||||
require("express")
|
||||
require("express/spec")
|
||||
|
||||
print = puts
|
||||
quit = process.exit
|
||||
readFile = require('fs').readFileSync
|
||||
|
||||
function run(specs) {
|
||||
specs.forEach(function(spec){
|
||||
JSpec.exec('spec/spec.' + spec + '.js')
|
||||
})
|
||||
}
|
||||
|
||||
specs = {
|
||||
independant: [
|
||||
'core',
|
||||
'routing',
|
||||
'utils',
|
||||
'request',
|
||||
'mime',
|
||||
'static',
|
||||
'plugins',
|
||||
'plugins.cache',
|
||||
'plugins.view',
|
||||
'plugins.content-length',
|
||||
'plugins.method-override',
|
||||
'plugins.body-decoder',
|
||||
'plugins.redirect',
|
||||
'plugins.hooks',
|
||||
'plugins.cookie',
|
||||
'plugins.session',
|
||||
'plugins.flash',
|
||||
]
|
||||
}
|
||||
|
||||
switch (process.ARGV[2]) {
|
||||
case 'all':
|
||||
run(specs.independant)
|
||||
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,75 +0,0 @@
|
||||
|
||||
describe 'Express'
|
||||
describe 'parseParams()'
|
||||
it 'should parse simple query string key / value pairs'
|
||||
parseParams('foo=bar').should.eql { foo: 'bar' }
|
||||
parseParams('foo=bar&baz=1').should.eql { foo: 'bar', baz: '1' }
|
||||
end
|
||||
|
||||
it 'should parse named nested params'
|
||||
var user = { user: { name: 'tj', email: 'tj@vision-media.ca' }}
|
||||
parseParams('user[name]=tj&user[email]=tj@vision-media.ca').should.eql user
|
||||
end
|
||||
|
||||
it 'should parse several levels of nesting'
|
||||
var user = { user: { name: 'tj', email: { primary: 'tj@vision-media.ca' }}}
|
||||
parseParams('user[name]=tj&user[email][primary]=tj@vision-media.ca').should.eql user
|
||||
end
|
||||
|
||||
it 'should convert + to literal space'
|
||||
parseParams('foo=bar+baz++that').should.eql { foo: 'bar baz that' }
|
||||
parseParams('user[name]=tj+holowaychuk').should.eql { user: { name: 'tj holowaychuk' }}
|
||||
end
|
||||
|
||||
it 'should decode hex literals'
|
||||
parseParams('foo=bar%20baz%20%20that').should.eql { foo: 'bar baz that' }
|
||||
parseParams('user[name]=tj%20holowaychuk').should.eql { user: { name: 'tj holowaychuk' }}
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
require.paths.unshift('spec', 'spec/lib', 'lib')
|
||||
require("jspec")
|
||||
require("express")
|
||||
require("express/spec")
|
||||
|
||||
quit = process.exit
|
||||
print = puts
|
||||
|
||||
readFile = function(path) {
|
||||
var promise = require('posix').cat(path, "utf8")
|
||||
var result = ''
|
||||
promise.addErrback(function(){ throw "failed to read file `" + path + "'" })
|
||||
promise.addCallback(function(contents){
|
||||
result = contents
|
||||
})
|
||||
promise.wait()
|
||||
return result
|
||||
}
|
||||
|
||||
if (process.ARGV[2])
|
||||
JSpec.exec('spec/spec.' + process.ARGV[2] + '.js')
|
||||
else
|
||||
JSpec
|
||||
.exec('spec/spec.core.js')
|
||||
.exec('spec/spec.routing.js')
|
||||
.exec('spec/spec.helpers.js')
|
||||
.exec('spec/spec.request.js')
|
||||
.exec('spec/spec.mime.js')
|
||||
.exec('spec/spec.static.js')
|
||||
.exec('spec/spec.collection.js')
|
||||
.exec('spec/spec.element-collection.js')
|
||||
.exec('spec/spec.plugins.js')
|
||||
.exec('spec/spec.plugins.view.js')
|
||||
.exec('spec/spec.plugins.common-logger.js')
|
||||
.exec('spec/spec.plugins.content-length.js')
|
||||
.exec('spec/spec.plugins.method-override.js')
|
||||
.exec('spec/spec.plugins.body-decoder.js')
|
||||
.exec('spec/spec.plugins.redirect.js')
|
||||
.exec('spec/spec.plugins.hooks.js')
|
||||
.exec('spec/spec.plugins.cookie.js')
|
||||
JSpec.run({ formatter: JSpec.formatters.Terminal, failuresOnly: true })
|
||||
JSpec.report()
|
||||
@@ -0,0 +1,119 @@
|
||||
|
||||
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'
|
||||
store.set('foo', 'bar')
|
||||
store.get('foo').should.eql 'bar'
|
||||
end
|
||||
|
||||
it 'should override existing data'
|
||||
store.set('foo', 'bar')
|
||||
store.set('foo', 'baz')
|
||||
store.get('foo').should.eql 'baz'
|
||||
end
|
||||
|
||||
it 'should return data'
|
||||
store.set('foo', 'bar').should.eql 'bar'
|
||||
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'
|
||||
store.set('user', { name: 'tj' }).should.eql { name: 'tj' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get()'
|
||||
describe 'given a key'
|
||||
it 'should return cached value'
|
||||
store.set('foo', 'bar')
|
||||
store.get('foo').should.eql 'bar'
|
||||
end
|
||||
|
||||
it 'should unserialize JSON data'
|
||||
store.set('user', { name: 'tj' })
|
||||
store.get('user').should.eql { name: 'tj' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should return a set of caches'
|
||||
store.set('user:1', 'a')
|
||||
store.set('user:2', 'b')
|
||||
store.set('foo', 'bar')
|
||||
store.get('user:*').should.eql { 'user:1': 'a', 'user:2': 'b' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clear()'
|
||||
describe 'given a key'
|
||||
it 'should delete previous data'
|
||||
store.set('foo', 'bar')
|
||||
store.clear('foo')
|
||||
store.get('foo').should.be_null
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should clear a set of caches'
|
||||
store.set('user:one', '1')
|
||||
store.set('user:two', '2')
|
||||
store.clear('user:*')
|
||||
store.get('user:one').should.be_null
|
||||
store.get('user:two').should.be_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(new Date) - 300
|
||||
store.set('user:two', '2')
|
||||
store.data['user:two'].created = Number(new Date) - 100
|
||||
store.reap(200)
|
||||
store.get('user:one').should.be_null
|
||||
store.get('user:two').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
|
||||
@@ -82,7 +82,7 @@ describe 'Express'
|
||||
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, foo=bar; path=/'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
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()
|
||||
end
|
||||
|
||||
describe 'Flash'
|
||||
describe 'flash()'
|
||||
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', 'email received') })
|
||||
get('/info', function(){ return this.flash('info').join(', ') })
|
||||
get('/messages', function(){ return this.flash('info') || 'empty' })
|
||||
|
||||
post('/', headers).body.should.eql 'email sent'
|
||||
get('/', headers).body.should.eql 'email received'
|
||||
get('/info', headers).body.should.eql 'email sent, email received'
|
||||
get('/messages').body.should.eql 'empty'
|
||||
// TODO: seperate once segfault is fixed...
|
||||
end
|
||||
|
||||
it 'should return the message pushed'
|
||||
get('/', function(){ return this.flash('info', 'email sent') })
|
||||
get('/').body.should.eql 'email sent'
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ describe 'Express'
|
||||
put('/user', function(){
|
||||
return 'updated user'
|
||||
})
|
||||
post('/user', { uri: { params: { _method: 'put' }}}).body.should.eql 'updated user'
|
||||
post('/user', { body: '_method=put', headers: { 'content-type': 'application/x-www-form-urlencoded' }}).body.should.eql 'updated user'
|
||||
end
|
||||
|
||||
it 'should force _method to lowercase to conform to internal uses'
|
||||
put('/user', function(){
|
||||
return 'updated user'
|
||||
})
|
||||
post('/user', { uri: { params: { _method: 'PUT' }}}).body.should.eql 'updated user'
|
||||
post('/user', { body: '_method=PUT', headers: { 'content-type': 'application/x-www-form-urlencoded' }}).body.should.eql 'updated user'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/cookie').Cookie)
|
||||
use(Session = require('express/plugins/session').Session)
|
||||
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 sid cookie is present'
|
||||
it 'should not set sid'
|
||||
get('/login', function(){ return '' })
|
||||
get('/login', { headers: { cookie: 'sid=123' }}).headers.should.not.have_property 'set-cookie'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'session Store.Memory'
|
||||
before_each
|
||||
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
|
||||
})
|
||||
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'
|
||||
memory.fetch('1').should.have_property 'lastAccess'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the session does exist'
|
||||
it 'should return the previous session'
|
||||
memory.commit({ id: '1', same: true })
|
||||
memory.fetch('1').should.have_property 'same', true
|
||||
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'
|
||||
memory.commit({ id: '1' })
|
||||
memory.commit({ id: '2' })
|
||||
memory.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
|
||||
+73
-17
@@ -58,6 +58,29 @@ describe 'Express'
|
||||
get('/user', { headers: { accept: null }}).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
|
||||
|
||||
describe '#halt()'
|
||||
@@ -84,19 +107,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: throw_error(InvalidStatusCode, ...) when jspec is fixed
|
||||
get('/user', function(){ this.halt(123123) })
|
||||
-{ get('/user') }.should.throw_error(/InvalidStatusCode: 123123 is an invalid HTTP response code/)
|
||||
try { get('/user') }
|
||||
catch (e) {
|
||||
e.should.be_an_instance_of ExpressError
|
||||
e.should.be_an_instance_of InvalidStatusCode
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#contentType()'
|
||||
@@ -108,6 +118,24 @@ describe 'Express'
|
||||
get('/style.css').headers['content-type'].should.eql 'text/css'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#attachment()'
|
||||
it 'should set Content-Disposition to attachment'
|
||||
get('/report', function(){
|
||||
this.attachment()
|
||||
return 'foo'
|
||||
})
|
||||
get('/report').headers['content-disposition'].should.eql 'attachment'
|
||||
end
|
||||
|
||||
it 'should set attachment filename'
|
||||
get('/report', function(){
|
||||
this.attachment('report.pdf')
|
||||
return 'foo'
|
||||
})
|
||||
get('/report').headers['content-disposition'].should.eql 'attachment; filename="report.pdf"'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#param()'
|
||||
it 'should return a route placeholder value'
|
||||
@@ -155,12 +183,13 @@ describe 'Express'
|
||||
get('/product/ipod').body.should.eql 'ipod'
|
||||
end
|
||||
|
||||
it 'should access request.uri.params'
|
||||
it 'should work with a query string'
|
||||
get('/user', function(){
|
||||
return this.param('page') || 'First page'
|
||||
return String(this.param('page') || 'First page')
|
||||
})
|
||||
get('/user').body.should.eql 'First page'
|
||||
get('/user', { uri: { params: { page: '2' }}}).body.should.eql '2'
|
||||
get('/user?page=2').body.should.eql '2'
|
||||
get('/user?foo[]=bar&page=5').body.should.eql '5'
|
||||
end
|
||||
|
||||
it 'should work with splats'
|
||||
@@ -169,6 +198,33 @@ 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
|
||||
|
||||
end
|
||||
end
|
||||
@@ -84,15 +84,6 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with no response body'
|
||||
it 'should throw a InvalidResponseBody'
|
||||
get('/user', function(){
|
||||
this.respond()
|
||||
})
|
||||
-{ get('/user') }.should.throw_error(/InvalidResponseBody: get "\/user" did not respond with a body string/)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with regular expression'
|
||||
it 'should match'
|
||||
get(/^\/user\/(\d+)\/(\w+)/, function(id, operation){
|
||||
|
||||
@@ -11,7 +11,12 @@ describe 'Express'
|
||||
end
|
||||
|
||||
it 'should throw an InvalidPathError when .. is found'
|
||||
-{ new StaticFile('/../foobar') }.should.throw_error(/InvalidPathError/)
|
||||
// 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
|
||||
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
|
||||
Arquivo binário não exibido.
Referência em uma Nova Issue
Bloquear um usuário