Comparar commits
241 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 3b49821e82 | |||
| febf443960 | |||
| f5da81e782 | |||
| 9cb23ac584 | |||
| 6ff3100c1f | |||
| e3ea3723bf | |||
| 33443d9b41 | |||
| e0ef61659f | |||
| 655ad77bce | |||
| c09e546240 | |||
| a08b14a89e | |||
| 12e90b0eef | |||
| 7606f1bcbd | |||
| cabb43b187 | |||
| b85cda3f6e | |||
| b92f1b7497 | |||
| 273a51a335 | |||
| 212dc88b89 | |||
| ce82a91d14 | |||
| 52a08401c7 | |||
| 706c0cb033 | |||
| 1567a613a6 | |||
| d767d80d20 | |||
| 1103a9d510 | |||
| c34af4b97a | |||
| 8dcee4d338 | |||
| fc1e69ea73 | |||
| db460117c1 | |||
| 1c514df0fd | |||
| 780ec5cbec | |||
| 3721873b62 | |||
| 3bdc77ca91 | |||
| b8f54d64a4 | |||
| 90d48b320a | |||
| a797342800 | |||
| 31e608e0ae | |||
| ebcfb0e577 | |||
| 161c9f9142 | |||
| 2a099da726 | |||
| 5118397004 | |||
| 546c340656 | |||
| 3034cc7c52 | |||
| d89381ff78 | |||
| 86cac58100 | |||
| 8fc062007e | |||
| 9fd92e3306 | |||
| a4cbbe5dd4 | |||
| da7ea9655f | |||
| 00a8c642dd | |||
| e1152f6e56 | |||
| 7cf9c6842a | |||
| d375535cd9 | |||
| c5ea868f47 | |||
| 884d333805 | |||
| 3b9921a15b | |||
| 17b753d95e | |||
| 6a8781b5e2 | |||
| 0dfaf01749 | |||
| 088aa83e22 | |||
| 934adb1e9f | |||
| 50e0593de6 | |||
| f2637c6421 | |||
| a52dacea68 | |||
| f56a33d22d | |||
| 2aa858b0fb | |||
| c3b8ba4b9a | |||
| ac96b8c0e1 | |||
| a329a9f4d8 | |||
| 63cc7eb44d | |||
| 6505f32221 | |||
| 22088260f4 | |||
| d893009a8d | |||
| b62e1741be | |||
| 903c2aa642 | |||
| d0a8bb550e | |||
| f96f1423e1 | |||
| 7bf17f2f61 | |||
| 670b6cfc15 | |||
| b6d2c8479c | |||
| 5aaa114271 | |||
| e99c2791bb | |||
| 656d7754cd | |||
| 4aaf10fbfc | |||
| 85e77b77aa | |||
| f23ef09247 | |||
| 440d956438 | |||
| acd2852cf3 | |||
| 4246f43bdf | |||
| 7d33769cd2 | |||
| 31fdba80d4 | |||
| 77f8e460d0 | |||
| 51e51db9f7 | |||
| 73c506f19c | |||
| 79143f3334 | |||
| 128ba9040e | |||
| 300cfe74ad | |||
| f008af05bd | |||
| 3aa870d6bd | |||
| 25e1a8c001 | |||
| 0ba3b114b0 | |||
| c429e88e8e | |||
| c7a2fe8440 | |||
| de237e760b | |||
| 927f5c9883 | |||
| 88f461baf2 | |||
| 3251ae26a0 | |||
| e84c81633e | |||
| a2ec966ac7 | |||
| 69660fbfda | |||
| dcedca1a80 | |||
| 6455e954fc | |||
| 646904688f | |||
| 62779fc972 | |||
| fdee4cde26 | |||
| 0c18ac5adc | |||
| 6da4a942ca | |||
| 565f68d2d7 | |||
| a7cee4c889 | |||
| 4f315f9b11 | |||
| 51febfec2d | |||
| df8bd96b2e | |||
| 9da9beb342 | |||
| f93af823df | |||
| 5e74723a92 | |||
| ec77e1acea | |||
| 1afad64972 | |||
| a3365dda07 | |||
| 4c246a4cd1 | |||
| 934ffd0731 | |||
| 790e2c233d | |||
| 9e9967381c | |||
| 891ed08827 | |||
| 55d13a6f08 | |||
| 5569ea4397 | |||
| 693e37459a | |||
| 90de1fa55d | |||
| 6d6e1557ce | |||
| 2d84f16dc0 | |||
| 2f98ef9f6d | |||
| e0c07d2385 | |||
| 0b2413d8c0 | |||
| e92b01f813 | |||
| 27ff13459f | |||
| 3e80915454 | |||
| 4063d2e2c4 | |||
| ce9416857b | |||
| 575d5e8e57 | |||
| 14ceb8c046 | |||
| c6c29ca505 | |||
| c551226504 | |||
| fcbc09543c | |||
| 6d83bb0026 | |||
| 0784a513c9 | |||
| ebcdec0860 | |||
| 6805e4f28f | |||
| 962f5fa412 | |||
| 1385988ab8 | |||
| 6cc80f8c54 | |||
| d6c843962d | |||
| 247e6ad9ab | |||
| 364c131a4e | |||
| b047325033 | |||
| 0894cfe058 | |||
| f4a9d7e70b | |||
| 593634fb5d | |||
| 2932bf5006 | |||
| 4b1c39fe41 | |||
| ee91e3a139 | |||
| e287b854a2 | |||
| 5458e5dd00 | |||
| ed30f37ca9 | |||
| 9e495f6bb8 | |||
| 21566c49f0 | |||
| 0a225c13a4 | |||
| 680c07a030 | |||
| 8ee66dca22 | |||
| 60393f07ef | |||
| 34418b03fb | |||
| ce2f161f08 | |||
| d5ca1ea152 | |||
| c3fec8225a | |||
| bfff00826e | |||
| 61cb50b2bd | |||
| c8d3bfabc7 | |||
| 1ce212d33d | |||
| 3682f4f06a | |||
| 0f74408b0e | |||
| f7d7c4a8e0 | |||
| 0cb8d12796 | |||
| 9586ecfd58 | |||
| 86217867f9 | |||
| 496de51a11 | |||
| 38591d06a0 | |||
| 91805f7da4 | |||
| 7bd8340a8b | |||
| 4817007097 | |||
| d5003feb39 | |||
| f03e460ca5 | |||
| 347c8847b0 | |||
| 9f48b32329 | |||
| 2f6dfbc165 | |||
| 49cb53d735 | |||
| 54f1a51a10 | |||
| c6a2674c2b | |||
| 282a10ec83 | |||
| 50276a06df | |||
| f452250f88 | |||
| 9d44e237a5 | |||
| 9f0e5899c2 | |||
| e351a02a06 | |||
| cd167ec777 | |||
| 3290412477 | |||
| acf0128fb4 | |||
| e91ee22a89 | |||
| 45ef08cf99 | |||
| baa7d12ed6 | |||
| 822de581b3 | |||
| 3863a76fc8 | |||
| 9727fac291 | |||
| 5cadbcbbd7 | |||
| 8ee0294672 | |||
| 99789c3182 | |||
| 406a7f4fc7 | |||
| 5a11f82e0e | |||
| 33eca37ec9 | |||
| fbd9cdd11e | |||
| 6ec6657512 | |||
| 8e91d2039a | |||
| 1879648be7 | |||
| 490770171d | |||
| 621063cc18 | |||
| 821defc11b | |||
| dbc1709e0e | |||
| 4d1bda0601 | |||
| 1a9a3674c2 | |||
| 99b7e74422 | |||
| add0a43c40 | |||
| 3dc7c6a254 | |||
| e645123fbd | |||
| 4b104db212 | |||
| e823e31550 |
@@ -1 +1,3 @@
|
||||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
+11
-8
@@ -1,12 +1,15 @@
|
||||
[submodule "spec/support/libxmljs"]
|
||||
path = spec/support/libxmljs
|
||||
url = git://github.com/sprsquish/libxmljs.git
|
||||
[submodule "lib/support/haml"]
|
||||
path = lib/support/haml
|
||||
url = git://github.com/creationix/haml-js.git
|
||||
[submodule "lib/support/js-oo"]
|
||||
path = lib/support/js-oo
|
||||
[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/class"]
|
||||
path = lib/support/class
|
||||
url = git://github.com/visionmedia/class.js.git
|
||||
|
||||
+120
@@ -1,4 +1,124 @@
|
||||
|
||||
0.8.0 / 2010-03-19
|
||||
==================
|
||||
|
||||
* Added coffeescript example app. Closes #242
|
||||
* Changed; cache api now async friendly. Closes #240
|
||||
* Removed deprecated 'express/static' support. Use 'express/plugins/static'
|
||||
|
||||
0.7.6 / 2010-03-19
|
||||
==================
|
||||
|
||||
* Added Request#isXHR. Closes #229
|
||||
* Added `make install` (for the executable)
|
||||
* Added `express` executable for setting up simple app templates
|
||||
* Added "GET /public/*" to Static plugin, defaulting to <root>/public
|
||||
* Added Static plugin
|
||||
* Fixed; Request#render() only calls cache.get() once
|
||||
* Fixed; Namespacing View caches with "view:"
|
||||
* Fixed; Namespacing Static caches with "static:"
|
||||
* Fixed; Both example apps now use the Static plugin
|
||||
* Fixed set("views"). Closes #239
|
||||
* Fixed missing space for combined log format
|
||||
* Deprecated Request#sendfile() and 'express/static'
|
||||
* Removed Server#running
|
||||
|
||||
0.7.5 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Added Request#flash() support without args, now returns all flashes
|
||||
* Updated ext submodule
|
||||
|
||||
0.7.4 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Fixed session reaper
|
||||
* Changed; class.js replacing js-oo Class implementation (quite a bit faster, no browser cruft)
|
||||
|
||||
0.7.3 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Added package.json
|
||||
* Fixed requiring of haml / sass due to kiwi removal
|
||||
|
||||
0.7.2 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Fixed GIT submodules (HAH!)
|
||||
|
||||
0.7.1 / 2010-03-16
|
||||
==================
|
||||
|
||||
* Changed; Express now using submodules again until a PM is adopted
|
||||
* Changed; chat example using millisecond conversions from ext
|
||||
|
||||
0.7.0 / 2010-03-15
|
||||
==================
|
||||
|
||||
* Added Request#pass() support (finds the next matching route, or the given path)
|
||||
* Added Logger plugin (default "common" format replaces CommonLogger)
|
||||
* Removed Profiler plugin
|
||||
* Removed CommonLogger plugin
|
||||
|
||||
0.6.0 / 2010-03-11
|
||||
==================
|
||||
|
||||
* Added seed.yml for kiwi package management support
|
||||
* Added HTTP client query string support when method is GET. Closes #205
|
||||
|
||||
* Added support for arbitrary view engines.
|
||||
For example "foo.engine.html" will now require('engine'),
|
||||
the exports from this module are cached after the first require().
|
||||
|
||||
* Added async plugin support
|
||||
|
||||
* Removed usage of RESTful route funcs as http client
|
||||
get() etc, use http.get() and friends
|
||||
|
||||
* Removed custom exceptions
|
||||
|
||||
0.5.0 / 2010-03-10
|
||||
==================
|
||||
|
||||
* Added ext dependency (library of js extensions)
|
||||
* Removed extname() / basename() utils. Use path module
|
||||
* Removed toArray() util. Use arguments.values
|
||||
* Removed escapeRegexp() util. Use RegExp.escape()
|
||||
* Removed process.mixin() dependency. Use utils.mixin()
|
||||
* Removed Collection
|
||||
* Removed ElementCollection
|
||||
* Shameless self promotion of ebook "Advanced JavaScript" (http://dev-mag.com) ;)
|
||||
|
||||
0.4.0 / 2010-02-11
|
||||
==================
|
||||
|
||||
* Added flash() example to sample upload app
|
||||
* Added high level restful http client module (express/http)
|
||||
* Changed; RESTful route functions double as HTTP clients. Closes #69
|
||||
* Changed; throwing error when routes are added at runtime
|
||||
* Changed; defaulting render() context to the current Request. Closes #197
|
||||
* Updated haml submodule
|
||||
|
||||
0.3.0 / 2010-02-11
|
||||
==================
|
||||
|
||||
* Updated haml / sass submodules. Closes #200
|
||||
* Added flash message support. Closes #64
|
||||
* Added accepts() now allows multiple args. fixes #117
|
||||
* Added support for plugins to halt. Closes #189
|
||||
* Added alternate layout support. Closes #119
|
||||
* Removed Route#run(). Closes #188
|
||||
* Fixed broken specs due to use(Cookie) missing
|
||||
|
||||
0.2.1 / 2010-02-05
|
||||
==================
|
||||
|
||||
* Added "plot" format option for Profiler (for gnuplot processing)
|
||||
* Added request number to Profiler plugin
|
||||
* Fixed binary encoding for multi-part file uploads, was previously defaulting to UTF8
|
||||
* Fixed issue with routes not firing when not files are present. Closes #184
|
||||
* Fixed process.Promise -> events.Promise
|
||||
|
||||
0.2.0 / 2010-02-03
|
||||
==================
|
||||
|
||||
|
||||
+10
-16
@@ -1,20 +1,15 @@
|
||||
|
||||
NODE = node
|
||||
COFFEE = coffee
|
||||
|
||||
all: test
|
||||
|
||||
install: bin/express
|
||||
install bin/express /usr/local/bin/express
|
||||
|
||||
init:
|
||||
@git submodule init && git submodule update
|
||||
|
||||
test: init spec/support/libxmljs/libxmljs.node
|
||||
test:
|
||||
@$(NODE) spec/node.js all
|
||||
|
||||
test-independant: init
|
||||
@$(NODE) spec/node.js independant
|
||||
|
||||
test-dependant: init spec/support/libxmljs/libxmljs.node
|
||||
@$(NODE) spec/node.js dependant
|
||||
|
||||
app: app-chat
|
||||
|
||||
app-chat:
|
||||
@@ -23,11 +18,10 @@ app-chat:
|
||||
app-upload:
|
||||
@$(NODE) examples/upload/app.js
|
||||
|
||||
benchmark:
|
||||
@$(NODE) benchmarks/collection.js
|
||||
@$(NODE) benchmarks/views.js
|
||||
app-coffee-upload: compile-coffee
|
||||
@$(NODE) examples/coffee-upload/app.js
|
||||
|
||||
spec/support/libxmljs/libxmljs.node:
|
||||
@scons -C spec/support/libxmljs libxmljs.node
|
||||
compile-coffee:
|
||||
@$(COFFEE) examples/coffee-upload/app.coffee
|
||||
|
||||
.PHONY: init test benchmark app
|
||||
.PHONY: install test app
|
||||
+34
-53
@@ -12,90 +12,71 @@
|
||||
* Sexy DSL with robust sinatra-like routing
|
||||
* High performance
|
||||
* Session support
|
||||
* Cache API
|
||||
* RESTful HTTP client
|
||||
* Mime helpers
|
||||
* Redirection helpers
|
||||
* Nested parameter parsing
|
||||
* Full test coverage
|
||||
* Extremely readable specs
|
||||
* Multipart file upload support
|
||||
* Test helpers (mock requests etc)
|
||||
* Environment based configuration
|
||||
* Light-weight JavaScript class implementation via js-oo
|
||||
* Collections and chainable iterators
|
||||
* ElementCollections / markup parsing via libxmljs and css selector traversal support via css2xpath
|
||||
* Light-weight JavaScript class implementation via [class.js](http://github.com/visionmedia/class.js/)
|
||||
* Persistent flash messages
|
||||
* Route passing
|
||||
* View support (ejs, haml, sass, etc)
|
||||
* Full test coverage
|
||||
* Logger plugin with several formats
|
||||
* 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 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
|
||||
|
||||
Below is a minimal app example when express is already within your load path.
|
||||
Below is a tiny Express application. View the [Wiki](http://wiki.github.com/visionmedia/express/) for detailed information.
|
||||
|
||||
require.paths.unshift('express/lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
set('root', __dirname)
|
||||
get('/user', function(){
|
||||
this.redirect('/user/' + this.currentUser.id)
|
||||
})
|
||||
|
||||
get('/hello', function(){
|
||||
this.contentType('html')
|
||||
return '<h1>World<h1>'
|
||||
})
|
||||
|
||||
get('/user/:id?', function(id) {
|
||||
get('/user/:id', function(id){
|
||||
this.render('user.haml.html', {
|
||||
locals: {
|
||||
name: id ? 'User ' + id : 'You'
|
||||
user: this.currentUser,
|
||||
usersOnline: Session.store.length()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
run()
|
||||
|
||||
|
||||
## Running Tests
|
||||
|
||||
Express uses the [JSpec](http://jspec.info) BDD JavaScript testing
|
||||
framework to write and run elegant spec suites. JSpec is frozen
|
||||
to spec/lib and does not require separate installation.
|
||||
|
||||
To run all specifications run the following command. This will ensure
|
||||
git submodules are initialized and updated, as well as building test
|
||||
related dependencies such as libxmljs.
|
||||
to spec/lib and **does not** require separate installation.
|
||||
|
||||
$ make test
|
||||
|
||||
To run independent specs (which do not require building of external apis etc) use:
|
||||
|
||||
$ make test-independant
|
||||
|
||||
To run dependent specs (which require building of external apis etc) use:
|
||||
|
||||
$ make test-dependant
|
||||
|
||||
Run individual suites:
|
||||
|
||||
$ node spec/node.js core
|
||||
@@ -104,20 +85,20 @@ Run individual suites:
|
||||
...
|
||||
|
||||
Express is currently being developed with node --version:
|
||||
v0.1.27
|
||||
v0.1.32
|
||||
|
||||
## More Information
|
||||
|
||||
* [JavaScript Extensions & Utilities](http://github.com/visionmedia/ext.js)
|
||||
* [JavaScript Sass](http://github.com/visionmedia/sass.js)
|
||||
* [Scons Build System](http://www.scons.org/) (some development dependencies rely on this, ex libxmljs)
|
||||
* Featured in [Advanced JavaScript e-book](http://www.dev-mag.com/2010/02/18/advanced-javascript/) for only $4
|
||||
|
||||
## Contributors
|
||||
|
||||
* TJ Holowaychuk (visionmedia) <tj@vision-media.ca>
|
||||
* Aaron Heckmann (aheckmann) <aaron.heckmann+github@gmail.com>
|
||||
* Ciaran Jessup (ciaranj) <ciaranj@gmail.com>
|
||||
* Gareth Jones (csausdev) <gareth.jones@sensis.com.au>
|
||||
* Aaron Heckmann (aheckmann) <aaron.heckmann+github@gmail.com>
|
||||
|
||||
## 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]', 100, function(){
|
||||
array = range(0, 10000)
|
||||
|
||||
benchmark('for', function(){
|
||||
for (var i = 0, len = array.length; i < len; ++i) ;
|
||||
})
|
||||
|
||||
benchmark('for uncached', function(){
|
||||
for (var i = 0; i < array.length; ++i) ;
|
||||
})
|
||||
|
||||
benchmark('forEach()', function(){
|
||||
array.forEach(function(){})
|
||||
})
|
||||
|
||||
benchmark('#each()', function(){
|
||||
$(array).each(function(){})
|
||||
})
|
||||
|
||||
benchmark('#map()', function(){
|
||||
$(array).map(function(n){ return n += 1 })
|
||||
})
|
||||
|
||||
benchmark('#map() with shorthand', function(){
|
||||
$(array).map('a += 1')
|
||||
})
|
||||
|
||||
benchmark('#find()', function(){
|
||||
$(array).find(function(n){ return n > 5000 })
|
||||
})
|
||||
|
||||
benchmark('#select()', function(){
|
||||
$(array).select(function(n){ return n % 2 })
|
||||
})
|
||||
|
||||
benchmark('#first()', function(){
|
||||
$(array).first(5000)
|
||||
})
|
||||
|
||||
benchmark('#slice()', function(){
|
||||
$(array).slice(100, 5000)
|
||||
})
|
||||
|
||||
benchmark('#drop()', function(){
|
||||
$(array).drop(5000)
|
||||
})
|
||||
|
||||
benchmark('#length()', function(){
|
||||
$(array).length()
|
||||
})
|
||||
|
||||
benchmark('#keys()', function(){
|
||||
$(array).keys()
|
||||
})
|
||||
|
||||
benchmark('#toArray()', function(){
|
||||
$(array).toArray()
|
||||
})
|
||||
|
||||
benchmark('#min()', function(){
|
||||
$(array).min()
|
||||
})
|
||||
|
||||
benchmark('#max()', function(){
|
||||
$(array).max()
|
||||
})
|
||||
|
||||
benchmark('#sum()', function(){
|
||||
$(array).sum()
|
||||
})
|
||||
|
||||
benchmark('#avg()', function(){
|
||||
$(array).avg()
|
||||
})
|
||||
|
||||
benchmark('#clone()', function(){
|
||||
$(array).clone()
|
||||
})
|
||||
|
||||
benchmark('#merge()', function(){
|
||||
$(array).merge({ foo: 'bar' })
|
||||
})
|
||||
|
||||
benchmark('#sample()', function(){
|
||||
$(array).sample()
|
||||
})
|
||||
|
||||
benchmark('#chunk()', function(){
|
||||
$(array).chunk(5)
|
||||
})
|
||||
|
||||
benchmark('#at()', function(){
|
||||
$(array).at(5000)
|
||||
})
|
||||
})
|
||||
@@ -1,56 +0,0 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require.paths.unshift('benchmarks')
|
||||
process.mixin(GLOBAL, require('sys'))
|
||||
process.mixin(GLOBAL, require('benchmark'))
|
||||
require('express')
|
||||
|
||||
print = puts
|
||||
|
||||
engine = {
|
||||
ejs: require('ejs'),
|
||||
haml: require('haml'),
|
||||
sass: require('sass')
|
||||
}
|
||||
|
||||
options = { locals: { article: { title: 'Foo', body: 'bar' }}}
|
||||
|
||||
ejs = ' \n\
|
||||
<div id="primary"> \n\
|
||||
<div class="block first"> \n\
|
||||
<h1><%= article.title %></h1> \n\
|
||||
<p><%= article.body %></p> \n\
|
||||
</div> \n\
|
||||
</div> \n\
|
||||
'
|
||||
|
||||
haml = ' \n\
|
||||
#primary \n\
|
||||
.block.first \n\
|
||||
%h1= article.title \n\
|
||||
%p= article.body \n\
|
||||
'
|
||||
|
||||
sass = ' \n\
|
||||
red: #ff0000 \n\
|
||||
body \n\
|
||||
ul \n\
|
||||
li \n\
|
||||
a \n\
|
||||
:color !red \n\
|
||||
:list-style none \n\
|
||||
'
|
||||
|
||||
suite('Template Engines', 1000, function(){
|
||||
benchmark('ejs', function(){
|
||||
engine.ejs.render(ejs, options)
|
||||
})
|
||||
|
||||
benchmark('haml', function(){
|
||||
engine.haml.render(haml, options)
|
||||
})
|
||||
|
||||
benchmark('sass', function(){
|
||||
engine.sass.render(sass)
|
||||
})
|
||||
})
|
||||
Arquivo executável
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var arg,
|
||||
dir = '.',
|
||||
fs = require('fs'),
|
||||
sys = require('sys'),
|
||||
args = process.argv.slice(2)
|
||||
|
||||
// Parse arguments
|
||||
|
||||
while (arg = args.shift())
|
||||
switch (arg) {
|
||||
case '-h':
|
||||
case '--help':
|
||||
sys.print('Usage: express [options] [dir]\n')
|
||||
break
|
||||
default:
|
||||
dir = arg
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
function confirm(msg, fn) {
|
||||
sys.print(msg)
|
||||
process.stdio.open()
|
||||
process.stdio.addListener('data', function(chunk){
|
||||
switch (chunk.trim().toLowerCase()) {
|
||||
case 'yes':
|
||||
case 'y':
|
||||
return process.stdio.close(),
|
||||
fn(true)
|
||||
case 'no':
|
||||
case 'n':
|
||||
return process.stdio.close(),
|
||||
fn(false)
|
||||
default:
|
||||
sys.print(msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createTemplateIn(dir, mode) {
|
||||
fs.writeFile(dir + '/app.js', '// your app here')
|
||||
fs.mkdir(dir + '/views', mode)
|
||||
fs.mkdir(dir + '/public', mode, function(err){
|
||||
if (err) throw err
|
||||
fs.mkdir(dir + '/public/javascripts', mode)
|
||||
fs.mkdir(dir + '/public/stylesheets', mode)
|
||||
fs.mkdir(dir + '/public/images', mode)
|
||||
})
|
||||
}
|
||||
|
||||
// Setup template
|
||||
|
||||
fs.readdir(dir, function(err, files){
|
||||
if (err) throw err
|
||||
if (files.length)
|
||||
confirm(dir + ' is not empty, continue? ', function(ok){
|
||||
if (ok) createTemplateIn(dir, 0666)
|
||||
})
|
||||
else
|
||||
createTemplateIn(dir, 0666)
|
||||
})
|
||||
+24
-26
@@ -3,33 +3,35 @@ require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
var fiveMinutes = 300000,
|
||||
oneMinute = 60000
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(CommonLogger)
|
||||
use(Cookie)
|
||||
use(Cache, { lifetime: fiveMinutes, reapInterval: oneMinute })
|
||||
use(Session, { lifetime: fiveMinutes, reapInterval: oneMinute })
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
var messages = [],
|
||||
utils = require('express/utils')
|
||||
|
||||
get('/', function(){
|
||||
this.redirect('/chat')
|
||||
configure(function(){
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(Cookie)
|
||||
use(Cache, { lifetime: (5).minutes, reapInterval: (1).minute })
|
||||
use(Session, { lifetime: (15).minutes, reapInterval: (1).minute })
|
||||
use(Static)
|
||||
use(Logger)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/chat', function(){
|
||||
this.render('chat.haml.html', {
|
||||
locals: {
|
||||
title: 'Chat',
|
||||
messages: messages,
|
||||
name: this.session.name,
|
||||
usersOnline: Session.store.length()
|
||||
}
|
||||
get('/', function(){
|
||||
this.pass('/chat')
|
||||
})
|
||||
|
||||
get('/chat', function(){
|
||||
var self = this
|
||||
Session.store.length(function(err, len){
|
||||
self.render('chat.haml.html', {
|
||||
locals: {
|
||||
title: 'Chat',
|
||||
messages: messages,
|
||||
name: self.session.name,
|
||||
usersOnline: len
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,10 +56,6 @@ get('/chat/messages', function(){
|
||||
}, 100)
|
||||
})
|
||||
|
||||
get('/public/*', function(file){
|
||||
this.sendfile(__dirname + '/public/' + file)
|
||||
})
|
||||
|
||||
get('/*.css', function(file){
|
||||
this.render(file + '.sass.css', { layout: false })
|
||||
})
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
require.paths.unshift 'lib'
|
||||
require 'express'
|
||||
require 'express/plugins'
|
||||
|
||||
configure ->
|
||||
use MethodOverride
|
||||
use ContentLength
|
||||
use Cookie
|
||||
use Session
|
||||
use Flash
|
||||
use Logger
|
||||
use Static
|
||||
set 'root', __dirname
|
||||
|
||||
get '/', ->
|
||||
@redirect('/upload')
|
||||
|
||||
get '/upload', ->
|
||||
@render 'upload.haml.html', {
|
||||
locals: {
|
||||
flashes: @flash 'info'
|
||||
}
|
||||
}
|
||||
|
||||
post '/upload', ->
|
||||
@param('images').each (image) =>
|
||||
puts image.filename + ' -> ' + image.tempfile
|
||||
@flash 'info', 'Uploaded ' + image.filename
|
||||
@redirect '/upload'
|
||||
|
||||
get '/*.css', (file) ->
|
||||
@render file + '.sass.css', { layout: no }
|
||||
|
||||
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' }
|
||||
+15
-10
@@ -6,7 +6,11 @@ require('express/plugins')
|
||||
configure(function(){
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(CommonLogger)
|
||||
use(Cookie)
|
||||
use(Session)
|
||||
use(Flash)
|
||||
use(Logger)
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
@@ -15,18 +19,19 @@ get('/', function(){
|
||||
})
|
||||
|
||||
get('/upload', function(){
|
||||
this.render('upload.haml.html')
|
||||
})
|
||||
|
||||
post('/upload', function(){
|
||||
$(this.param('images')).each(function(image){
|
||||
puts('uploaded ' + image.filename + ' to ' + image.tempfile)
|
||||
this.render('upload.haml.html', {
|
||||
locals: {
|
||||
flashes: this.flash('info')
|
||||
}
|
||||
})
|
||||
this.redirect('/upload')
|
||||
})
|
||||
|
||||
get('/public/*', function(file){
|
||||
this.sendfile(__dirname + '/public/' + file)
|
||||
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('/*.css', function(file){
|
||||
|
||||
@@ -5,4 +5,10 @@
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper= body
|
||||
#wrapper
|
||||
%h1 Upload
|
||||
:if flashes
|
||||
%ul.messages.info
|
||||
:each msg in flashes
|
||||
%li= msg
|
||||
.body= body
|
||||
|
||||
@@ -56,4 +56,13 @@ form
|
||||
.panel
|
||||
:float left
|
||||
:width 100%
|
||||
:margin-bottom 15px
|
||||
: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
|
||||
@@ -1,4 +1,3 @@
|
||||
%h1 Upload
|
||||
:if typeof images !== 'undefined'
|
||||
.images
|
||||
:each img in images
|
||||
|
||||
+3
-4
@@ -1,8 +1,7 @@
|
||||
|
||||
var path = require('path')
|
||||
require.paths.unshift(__dirname + '/support/js-oo/lib')
|
||||
require.paths.unshift(__dirname + '/support/ejs/lib')
|
||||
require.paths.unshift(__dirname + '/support/ext/lib')
|
||||
require.paths.unshift(__dirname + '/support/haml/lib')
|
||||
require.paths.unshift(__dirname + '/support/sass/lib')
|
||||
require('oo')
|
||||
require('ext')
|
||||
Class = require('support/class/lib/class').Class
|
||||
require('express/core')
|
||||
@@ -1,543 +0,0 @@
|
||||
|
||||
// Express - Collection - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Throw $break in order to stop iteration.
|
||||
*/
|
||||
|
||||
$break = '__break__'
|
||||
|
||||
var property = /^\w+$/,
|
||||
method = /^\w+\(/
|
||||
|
||||
/**
|
||||
* Normalize callback _fn_. When a string is
|
||||
* passed convert the shorthand expr to a function.
|
||||
*
|
||||
* - Functions are passed through un-touched
|
||||
* - Strings with length of < 4 are considered operators between a and b
|
||||
* - Single words are considered properties on a
|
||||
* - Single functions are considered method calls on a
|
||||
* - Larger strings are considered return expressions
|
||||
*
|
||||
* @param {string, function} fn
|
||||
* @return {function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function callback(fn) {
|
||||
if (fn === undefined) return
|
||||
if (fn instanceof Function) return fn
|
||||
if (fn.length < 4) return Function('a, b, c', 'return a ' + fn + ' b')
|
||||
if (property.test(fn) || method.test(fn)) fn = 'a.' + fn
|
||||
return Function('a, b, c', 'return ' + fn)
|
||||
}
|
||||
|
||||
// --- Collection
|
||||
|
||||
Collection = Class({
|
||||
|
||||
/**
|
||||
* Initialize collection with an array-like object.
|
||||
*
|
||||
* @param {object} arr
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(arr) {
|
||||
this.arr = arr || []
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the value of _index_ or null.
|
||||
*
|
||||
* @param {int} index
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
at: function(index) {
|
||||
if ('length' in this.arr)
|
||||
return this.arr[index]
|
||||
var result, i = 0
|
||||
this.each(function(val){
|
||||
if (i++ == index) {
|
||||
result = val
|
||||
throw $break
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate collection using callback _fn_,
|
||||
* passing both the value and index.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
each: function(fn) {
|
||||
try {
|
||||
if (this.arr.forEach)
|
||||
this.arr.forEach(fn)
|
||||
else
|
||||
for (var key in this.arr)
|
||||
if (this.arr.hasOwnProperty(key))
|
||||
fn(this.arr[key], key)
|
||||
}
|
||||
catch (e) {
|
||||
if (e != $break) throw e
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Reverse a collection.
|
||||
*
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
reverse: function() {
|
||||
if (this.arr.reverse)
|
||||
return $(this.arr.reverse())
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Join a collection with the given _str_ or ''.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
join: function(str) {
|
||||
return this.toArray().join(str || '')
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort collection with optional _fn_.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @warn converts to array
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sort: function(fn) {
|
||||
fn = callback(fn)
|
||||
return $(this.toArray().sort(fn))
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate with _memo_ using callback _fn_.
|
||||
* The _memo_ object is passed as the first
|
||||
* argument, and the return value of _fn_ becomes
|
||||
* the value of _memo_ providing a functional
|
||||
* approach to reducing a collection.
|
||||
*
|
||||
* @param {mixed} memo
|
||||
* @param {function} fn
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
reduce: function(memo, fn) {
|
||||
fn = callback(fn)
|
||||
if (this.arr.reduce)
|
||||
return this.arr.reduce(fn, memo)
|
||||
this.each(function(val, key){
|
||||
memo = fn(memo, val, key)
|
||||
})
|
||||
return memo
|
||||
},
|
||||
|
||||
/**
|
||||
* Map using callback _fn_, returning a
|
||||
* new collection of return values.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
map: function(fn) {
|
||||
fn = callback(fn)
|
||||
if (this.arr.map)
|
||||
return $(this.arr.map(fn))
|
||||
return $(this.reduce([], function(array, val, key){
|
||||
array.push(fn(val, key))
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Return collection of values when _fn_ evaluates
|
||||
* to true.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
select: function(fn) {
|
||||
fn = callback(fn)
|
||||
return $(this.reduce([], function(array, val, key){
|
||||
if (fn(val, key))
|
||||
array.push(val)
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Return collection of values when _fn_ evaluates
|
||||
* to false.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
reject: function(fn) {
|
||||
fn = callback(fn)
|
||||
return $(this.reduce([], function(array, val, key){
|
||||
if (!fn(val, key))
|
||||
array.push(val)
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if all arguments passed are found within
|
||||
* the collection using the === operator.
|
||||
*
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
includes: function() {
|
||||
var self = this
|
||||
return $(arguments).all(function(arg){
|
||||
return self.any(function(val){
|
||||
return val === arg
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the first _n_ value(s), defaults to 1.
|
||||
*
|
||||
* @param {int} n
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
first: function(n) {
|
||||
return n ?
|
||||
this.slice(0, n) :
|
||||
this.at(0)
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the last _n_ value(s), defaults to -1.
|
||||
*
|
||||
* @param {int} n
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
last: function(n) {
|
||||
var len = this.length()
|
||||
return n ?
|
||||
this.slice(len - n, len) :
|
||||
this.at(--len)
|
||||
},
|
||||
|
||||
/**
|
||||
* Drop the first _n_ values, returning the others.
|
||||
*
|
||||
* @param {int} n
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
drop: function(n) {
|
||||
return this.slice(n, this.length())
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a slice of values from _start_ to _end_.
|
||||
*
|
||||
* @param {int} start
|
||||
* @param {int} end
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
slice: function(start, end) {
|
||||
if (this.arr.slice)
|
||||
return $(this.arr.slice(start, end))
|
||||
var i = 0
|
||||
return $(this.reduce([], function(array, val){
|
||||
if (i++ >= start)
|
||||
if (i <= end)
|
||||
array.push(val)
|
||||
else
|
||||
throw $break
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate until _fn_ evaluates to true, then
|
||||
* return the matching value.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
find: function(fn) {
|
||||
var result, fn = callback(fn)
|
||||
this.each(function(val, key){
|
||||
if (fn(val, key)) {
|
||||
result = val
|
||||
throw $break
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true if _fn_ ever evaluates to true, otherwise
|
||||
* returns false.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
any: function(fn) {
|
||||
fn = callback(fn)
|
||||
if (this.arr.some)
|
||||
return this.arr.some(fn)
|
||||
return !! this.find(fn)
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if _fn_ always evaluates to true, otherwise
|
||||
* returns false.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
all: function(fn) {
|
||||
fn = callback(fn)
|
||||
if (this.arr.every)
|
||||
return this.arr.every(fn)
|
||||
return this.reduce(true, function(state, val, key){
|
||||
if (!state) throw $break
|
||||
return !! fn(val, key)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Select values matching _pattern_
|
||||
*
|
||||
* @param {regexp} pattern
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
grep: function(pattern) {
|
||||
return this.select(function(val){
|
||||
return pattern.exec(val)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return keys as a collection.
|
||||
*
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
keys: function() {
|
||||
return $(Object.keys(this.arr))
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a collection to a true array.
|
||||
* Works recursively
|
||||
*
|
||||
* @return {array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toArray: function() {
|
||||
return this.reduce([], function(array, val){
|
||||
val instanceof Collection ?
|
||||
array.push(val.toArray()) :
|
||||
array.push(val)
|
||||
return array
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the lowest number in the collection.
|
||||
*
|
||||
* @return {number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
min: function() {
|
||||
return this.reduce(null, function(min, val){
|
||||
return min === null ? val :
|
||||
val < min ? val :
|
||||
min
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the largest number in the collection.
|
||||
*
|
||||
* @return {number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
max: function() {
|
||||
return this.reduce(null, function(max, val){
|
||||
return max === null ? val :
|
||||
val > max ? val :
|
||||
max
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Group collection into several collections grouped by _size_.
|
||||
*
|
||||
* @param {int} size
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
chunk: function(size) {
|
||||
var len = this.arr.length,
|
||||
chunks = [], chunk = [], i = 0
|
||||
this.each(function(val, key){
|
||||
chunk.push(val)
|
||||
if (i++ > 0 && (i % size == 0 || i == len))
|
||||
chunks.push($(chunk)),
|
||||
chunk = []
|
||||
})
|
||||
return $(chunks)
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the length of this collection.
|
||||
*
|
||||
* @return {int}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
length: function() {
|
||||
if ('length' in this.arr)
|
||||
return this.arr.length
|
||||
return this.reduce(0, function(len){
|
||||
return ++len
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the sum of numeric values in this collection.
|
||||
*
|
||||
* @return {number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sum: function() {
|
||||
return this.reduce(0, function(sum, n){
|
||||
return sum + n
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the average of numeric values in this collection.
|
||||
*
|
||||
* @return {number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
avg: function() {
|
||||
return this.sum() / this.length()
|
||||
},
|
||||
|
||||
/**
|
||||
* Merge _other_ with this collection.
|
||||
*
|
||||
* @param {mixed} other
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
merge: function(other) {
|
||||
if (this.arr instanceof Array)
|
||||
return $($(other).reduce(this.arr, function(array, val){
|
||||
array.push(val)
|
||||
return array
|
||||
}))
|
||||
else
|
||||
return $(process.mixin(this.arr, $(other).arr))
|
||||
},
|
||||
|
||||
/**
|
||||
* Clone the collection.
|
||||
*
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clone: function() {
|
||||
return this.merge({})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the value of a random index.
|
||||
*
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sample: function() {
|
||||
return this.at(Math.floor(Math.random() * this.length()))
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert collection to a string.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toString: function() {
|
||||
return '[Collection ' + this.arr + ']'
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Create a new collection from an array-like object.
|
||||
*
|
||||
* @param {object} arr
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var $ = exports.$ = function(arr) {
|
||||
if (arr instanceof Collection) return arr
|
||||
return new Collection(arr)
|
||||
}
|
||||
+111
-100
@@ -1,26 +1,24 @@
|
||||
|
||||
// Express - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
process.mixin(require('sys'))
|
||||
process.mixin(require('express/exceptions'))
|
||||
process.mixin(require('express/collection'))
|
||||
process.mixin(require('express/event'))
|
||||
process.mixin(require('express/request'))
|
||||
process.mixin(require('express/plugin'))
|
||||
process.mixin(require('express/dsl'))
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var multipart = require('multipart'),
|
||||
utils = require('express/utils'),
|
||||
events = require('events'),
|
||||
File = require('file').File,
|
||||
utils = require('express/utils')
|
||||
|
||||
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
|
||||
|
||||
Route = Class({
|
||||
Route = new Class({
|
||||
|
||||
/**
|
||||
* Initialize a route with the given _method_,
|
||||
@@ -37,26 +35,13 @@ Route = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(method, path, fn, options){
|
||||
constructor: function(method, path, fn, options){
|
||||
this.method = method
|
||||
this.originalPath = path
|
||||
this.path = this.normalize(path)
|
||||
this.fn = fn
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute this route's #fn with _args_,
|
||||
* against _context_ or GLOBAL.
|
||||
*
|
||||
* @param {array} args
|
||||
* @return {mixed}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
run: function(args, context) {
|
||||
return this.fn.apply(context || GLOBAL, args)
|
||||
},
|
||||
|
||||
/**
|
||||
* Normalize _path_. When a RegExp it is simply returned,
|
||||
* otherwise a string is converted to a regular expression
|
||||
@@ -84,7 +69,7 @@ Route = Class({
|
||||
var self = this
|
||||
this.keys = []
|
||||
if (path instanceof RegExp) return path
|
||||
return new RegExp('^' + utils.escapeRegexp(normalizePath(path), '.')
|
||||
return new RegExp('^' + RegExp.escape(normalizePath(path), '.')
|
||||
.replace(/\*/g, '(.+)')
|
||||
.replace(/(\/|\\\.):(\w+)\?/g, function(_, c, key){
|
||||
self.keys.push(key)
|
||||
@@ -99,7 +84,7 @@ Route = Class({
|
||||
|
||||
// --- Router
|
||||
|
||||
Router = Class({
|
||||
Router = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with _request_ and parse url.
|
||||
@@ -108,7 +93,7 @@ Router = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(request) {
|
||||
constructor: function(request) {
|
||||
this.request = request
|
||||
},
|
||||
|
||||
@@ -119,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
|
||||
@@ -136,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
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -162,23 +158,22 @@ 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)
|
||||
}
|
||||
})
|
||||
|
||||
// --- Server
|
||||
|
||||
Server = Class({
|
||||
Server = new Class({
|
||||
|
||||
/**
|
||||
* Default port number.
|
||||
@@ -221,51 +216,50 @@ Server = Class({
|
||||
.createServer(function(request, response){
|
||||
request.body = ''
|
||||
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'].indexOf('multipart/form-data') !== -1) {
|
||||
var stream = new multipart.Stream(request),
|
||||
promise = new events.Promise,
|
||||
request.headers['content-type'].includes('multipart/form-data')) {
|
||||
var stream = multipart.parse(request),
|
||||
pendingFiles = 0
|
||||
request.params = { post: {}}
|
||||
promise.timeout(set('upload timeout') || 5000)
|
||||
stream.addListener('part', function(part){
|
||||
if (part.filename) {
|
||||
var file = new File(part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(), 'w', { encoding: 'binary' })
|
||||
++pendingFiles
|
||||
part.pos = 0
|
||||
part.addListener('body', function(chunk){
|
||||
file.write(chunk, part.pos, 'binary').addErrback(function(){
|
||||
promise.emitError.apply(promise, arguments)
|
||||
})
|
||||
part.pos += chunk.length
|
||||
})
|
||||
.addListener('complete', function(){
|
||||
file.close().addCallback(function(){
|
||||
if (!--pendingFiles)
|
||||
promise.emitSuccess()
|
||||
}).addErrback(function(){
|
||||
promise.emitError.apply(promise, arguments)
|
||||
})
|
||||
utils.mergeParam(part.name, part, request.params.post)
|
||||
})
|
||||
}
|
||||
else
|
||||
part.buf = '',
|
||||
part
|
||||
.addListener('body', function(chunk){ part.buf += chunk })
|
||||
.addListener('complete', function(){
|
||||
if (part.buf.length)
|
||||
utils.mergeParam(part.name, part.buf, request.params.post)
|
||||
})
|
||||
}).addListener('complete', function(){
|
||||
if (!pendingFiles) promise.emitSuccess()
|
||||
promise.addCallback(function(){ self.route(request, response) })
|
||||
})
|
||||
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('body', function(chunk){ request.body += chunk })
|
||||
.addListener('complete', function(){ self.route(request, response) })
|
||||
.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')
|
||||
@@ -280,29 +274,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.2.0',
|
||||
version: '0.8.0',
|
||||
config: [],
|
||||
routes: [],
|
||||
plugins: [],
|
||||
|
||||
+20
-19
@@ -28,11 +28,11 @@ function route(method) {
|
||||
*/
|
||||
|
||||
exports.set = function(option, val) {
|
||||
return val === undefined ?
|
||||
Express.settings[option] instanceof Function ?
|
||||
Express.settings[option]() :
|
||||
Express.settings[option] :
|
||||
Express.settings[option] = val
|
||||
return val === undefined
|
||||
? Express.settings[option] instanceof Function
|
||||
? Express.settings[option]()
|
||||
: Express.settings[option]
|
||||
: Express.settings[option] = val
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,8 +68,8 @@ exports.disable = function(option) {
|
||||
*/
|
||||
|
||||
exports.run = function() {
|
||||
configure(Express.environment = process.ENV.EXPRESS_ENV || 'development')
|
||||
$(Express.plugins).each(function(plugin){
|
||||
configure(Express.environment = process.env.EXPRESS_ENV || 'development')
|
||||
Express.plugins.each(function(plugin){
|
||||
if ('init' in plugin.klass)
|
||||
plugin.klass.init(plugin.options)
|
||||
})
|
||||
@@ -77,7 +77,7 @@ exports.run = function() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure _environment_ with _fn_.
|
||||
* Configure _env_ with _fn_.
|
||||
*
|
||||
* Global configuration, disregards which
|
||||
* environment is active:
|
||||
@@ -96,22 +96,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')
|
||||
return Express.config.push([env, fn])
|
||||
if (typeof env !== 'string')
|
||||
throw new Error('environment required')
|
||||
for (var i = 0, len = Express.config.length; i < len; ++i)
|
||||
if (Express.config[i][0] == environment ||
|
||||
Express.config[i][0] == 'all')
|
||||
Express.config[i][1].call(Express)
|
||||
Express.config.each(function(conf){
|
||||
if (conf[0] === env ||
|
||||
conf[0] === 'all')
|
||||
conf[1].call(Express)
|
||||
})
|
||||
}
|
||||
|
||||
// --- Routing API
|
||||
@@ -119,4 +120,4 @@ exports.configure = function(environment, fn) {
|
||||
exports.get = exports.view = route('get')
|
||||
exports.post = exports.create = route('post')
|
||||
exports.del = exports.destroy = route('delete')
|
||||
exports.put = exports.update = route('put')
|
||||
exports.put = exports.update = route('put')
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
|
||||
// Express - ElementCollection - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var libxml = require('libxmljs')
|
||||
|
||||
// --- css2xpath
|
||||
|
||||
/** version 0.1 2009-04-30
|
||||
* @author Andrea Giammarchi
|
||||
* @license Mit Style License
|
||||
* @project http://code.google.com/p/css2xpath/
|
||||
*/
|
||||
var css2xpath=(function(){var b=[/\[([^\]~\$\*\^\|\!]+)(=[^\]]+)?\]/g,"[@$1$2]",/\s*,\s*/g,"|",/\s*(\+|~|>)\s*/g,"$1",/([a-zA-Z0-9\_\-\*])~([a-zA-Z0-9\_\-\*])/g,"$1/following-sibling::$2",/([a-zA-Z0-9\_\-\*])\+([a-zA-Z0-9\_\-\*])/g,"$1/following-sibling::*[1]/self::$2",/([a-zA-Z0-9\_\-\*])>([a-zA-Z0-9\_\-\*])/g,"$1/$2",/\[([^=]+)=([^'|"][^\]]*)\]/g,"[$1='$2']",/(^|[^a-zA-Z0-9\_\-\*])(#|\.)([a-zA-Z0-9\_\-]+)/g,"$1*$2$3",/([\>\+\|\~\,\s])([a-zA-Z\*]+)/g,"$1//$2",/\s+\/\//g,"//",/([a-zA-Z0-9\_\-\*]+):first-child/g,"*[1]/self::$1",/([a-zA-Z0-9\_\-\*]+):last-child/g,"$1[not(following-sibling::*)]",/([a-zA-Z0-9\_\-\*]+):only-child/g,"*[last()=1]/self::$1",/([a-zA-Z0-9\_\-\*]+):empty/g,"$1[not(*) and not(normalize-space())]",/([a-zA-Z0-9\_\-\*]+):not\(([^\)]*)\)/g,function(f,e,d){return e.concat("[not(",a(d).replace(/^[^\[]+\[([^\]]*)\].*$/g,"$1"),")]")},/([a-zA-Z0-9\_\-\*]+):nth-child\(([^\)]*)\)/g,function(f,e,d){switch(d){case"n":return e;case"even":return"*[position() mod 2=0 and position()>=0]/self::"+e;case"odd":return e+"[(count(preceding-sibling::*) + 1) mod 2=1]";default:d=(d||"0").replace(/^([0-9]*)n.*?([0-9]*)$/,"$1+$2").split("+");d[1]=d[1]||"0";return"*[(position()-".concat(d[1],") mod ",d[0],"=0 and position()>=",d[1],"]/self::",e)}},/:contains\(([^\)]*)\)/g,function(e,d){return"[contains(string(.),'"+d+"')]"},/\[([a-zA-Z0-9\_\-]+)\|=([^\]]+)\]/g,"[@$1=$2 or starts-with(@$1,concat($2,'-'))]",/\[([a-zA-Z0-9\_\-]+)\*=([^\]]+)\]/g,"[contains(@$1,$2)]",/\[([a-zA-Z0-9\_\-]+)~=([^\]]+)\]/g,"[contains(concat(' ',normalize-space(@$1),' '),concat(' ',$2,' '))]",/\[([a-zA-Z0-9\_\-]+)\^=([^\]]+)\]/g,"[starts-with(@$1,$2)]",/\[([a-zA-Z0-9\_\-]+)\$=([^\]]+)\]/g,function(f,e,d){return"[substring(@".concat(e,",string-length(@",e,")-",d.length-3,")=",d,"]")},/\[([a-zA-Z0-9\_\-]+)\!=([^\]]+)\]/g,"[not(@$1) or @$1!=$2]",/#([a-zA-Z0-9\_\-]+)/g,"[@id='$1']",/\.([a-zA-Z0-9\_\-]+)/g,"[contains(concat(' ',normalize-space(@class),' '),' $1 ')]",/\]\[([^\]]+)/g," and ($1)"],c=b.length;return function a(e){var d=0;while(d<c){e=e.replace(b[d++],b[d++])}return"//"+e}})();
|
||||
|
||||
// --- ElementCollection
|
||||
|
||||
ElementCollection = Collection.extend({
|
||||
|
||||
/**
|
||||
* Initialize with string of _markup_.
|
||||
*
|
||||
* @param {string} markup
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(markup) {
|
||||
if (typeof markup != 'string')
|
||||
return this.__super__(markup)
|
||||
if (!(/<html>/.test(markup)))
|
||||
markup = '<html><body>' + markup + '</body></html>'
|
||||
this.document = libxml.parseHtmlString(markup)
|
||||
this.arr = [this.document.root()]
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the first element's name.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
name: function() {
|
||||
return this.at(0).name()
|
||||
},
|
||||
|
||||
/**
|
||||
* Search child elements with the given _xpath_.
|
||||
*
|
||||
* @param {string} xpath
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
xpath: function(xpath) {
|
||||
// TODO: refactor with flatten()
|
||||
return $(this.reduce([], function(array, e){
|
||||
$(e.find(xpath)).each(function(child){
|
||||
array.push(child)
|
||||
})
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Search child elements with the given css _selector_
|
||||
*
|
||||
* @param {string} selector
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
search: function(selector) {
|
||||
return this.xpath(css2xpath(selector))
|
||||
},
|
||||
|
||||
/**
|
||||
* Return collection of children.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
children: function() {
|
||||
// TODO: refactor with flatten()
|
||||
return $(this.reduce([], function(array, e){
|
||||
$(e.children()).each(function(child){
|
||||
array.push(child)
|
||||
})
|
||||
return array
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Return collection of parents.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
parents: function() {
|
||||
return this.map(function(e){
|
||||
return e.parent()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the first element's parent.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
parent: function() {
|
||||
return $([this.at(0).parent()])
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the prev element.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
prev: function() {
|
||||
return $([this.at(0).prevSibling()])
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the next element.
|
||||
*
|
||||
* @return {ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
next: function() {
|
||||
return $([this.at(0).nextSibling()])
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the first element's text content.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
text: function() {
|
||||
return this.at(0).text()
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert collection to a string.
|
||||
*
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toString: function() {
|
||||
if (this.at(0) && this.at(0).doc)
|
||||
return '[Collection <elements>]'
|
||||
return this.__super__()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Add markup support to $().
|
||||
*
|
||||
* @param {object} arr
|
||||
* @return {Collection, ElementCollection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var $ = exports.$ = function(arr) {
|
||||
if (arr instanceof Collection) return arr
|
||||
return new ElementCollection(arr)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
// Express - Event - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
exports.Event = Class({
|
||||
exports.Event = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with event _name_ and optional _data_.
|
||||
@@ -11,9 +11,9 @@ exports.Event = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(name, data) {
|
||||
constructor: function(name, data) {
|
||||
this.name = name
|
||||
process.mixin(this, data)
|
||||
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
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,93 @@
|
||||
|
||||
// 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')
|
||||
@@ -5,7 +5,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils')
|
||||
var extname = require('path').extname
|
||||
|
||||
/**
|
||||
* Mime type lookup table.
|
||||
@@ -364,7 +364,7 @@ exports.types = {
|
||||
|
||||
exports.type = function(path) {
|
||||
return exports.types[path] ||
|
||||
exports.types[utils.extname(path)] ||
|
||||
exports.types[extname(path).substr(1)] ||
|
||||
set('default mime type') ||
|
||||
'application/octet-stream'
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ var style = require('express/pages/style').style
|
||||
|
||||
function stack(e) {
|
||||
if (e.stack)
|
||||
return $(e.stack.split('\n').slice(1)).map(function(val, i){
|
||||
return e.stack.split('\n').slice(1).map(function(val, i){
|
||||
if (!i)
|
||||
return '<li>' + val.replace(/^(.*?):/, '<span class="path">$1</span>:') + '</li>'
|
||||
return '<li>' + val
|
||||
@@ -32,8 +32,8 @@ function stack(e) {
|
||||
*/
|
||||
|
||||
function hash(hash) {
|
||||
if (!$(hash).length()) return '<tr><td class="empty" colspan="2">Empty</td></tr>'
|
||||
return $(hash).map(function(val, key){
|
||||
if (!hash || !hash.values.length) return '<tr><td class="empty" colspan="2">Empty</td></tr>'
|
||||
return hash.map(function(val, key){
|
||||
return '<tr><td>' + key + ':</td><td>' + JSON.encode(val) + '</td></tr>'
|
||||
}).join('\n')
|
||||
}
|
||||
@@ -62,15 +62,15 @@ exports.render = function(request, e) {
|
||||
</table> \n\
|
||||
<h3>Params</h3> \n\
|
||||
<table id="route-params"> \n\
|
||||
' + hash(request.params) + ' \n\
|
||||
' + hash(request.params.path) + ' \n\
|
||||
</table> \n\
|
||||
<h3>GET</h3> \n\
|
||||
<table id="get-params"> \n\
|
||||
' + hash(request.url.params) + ' \n\
|
||||
' + hash(request.params.get) + ' \n\
|
||||
</table> \n\
|
||||
<h3>POST</h3> \n\
|
||||
<table id="post-params"> \n\
|
||||
' + hash(request.url.post) + ' \n\
|
||||
' + hash(request.params.post) + ' \n\
|
||||
</table> \n\
|
||||
</div> \n\
|
||||
</body> \n\
|
||||
|
||||
+12
-6
@@ -1,6 +1,12 @@
|
||||
|
||||
// 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
|
||||
@@ -14,7 +20,7 @@
|
||||
exports.use = function(plugin, options) {
|
||||
if (Express.environment === 'test' && 'init' in plugin)
|
||||
plugin.init(options)
|
||||
$(Express.plugins).each(function(other, i){
|
||||
Express.plugins.each(function(other, i){
|
||||
if (other.klass === plugin)
|
||||
delete Express.plugins[i]
|
||||
})
|
||||
@@ -26,7 +32,7 @@ exports.use = function(plugin, options) {
|
||||
|
||||
// --- Plugin
|
||||
|
||||
exports.Plugin = Class({
|
||||
exports.Plugin = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with _options_.
|
||||
@@ -35,9 +41,9 @@ exports.Plugin = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
constructor: function(options) {
|
||||
if (options)
|
||||
process.mixin(this, options)
|
||||
utils.mixin(this, options)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -47,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,11 +1,12 @@
|
||||
|
||||
// Express - Plugins - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
process.mixin(require('express/plugins/hooks'))
|
||||
process.mixin(require('express/plugins/cache'))
|
||||
process.mixin(require('express/plugins/cookie'))
|
||||
process.mixin(require('express/plugins/session'))
|
||||
process.mixin(require('express/plugins/profiler'))
|
||||
process.mixin(require('express/plugins/common-logger'))
|
||||
process.mixin(require('express/plugins/content-length'))
|
||||
process.mixin(require('express/plugins/method-override'))
|
||||
global.merge(require('express/plugins/hooks'))
|
||||
global.merge(require('express/plugins/static'))
|
||||
global.merge(require('express/plugins/flash'))
|
||||
global.merge(require('express/plugins/cache'))
|
||||
global.merge(require('express/plugins/cookie'))
|
||||
global.merge(require('express/plugins/session'))
|
||||
global.merge(require('express/plugins/logger'))
|
||||
global.merge(require('express/plugins/content-length'))
|
||||
global.merge(require('express/plugins/method-override'))
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
|
||||
// Express - BodyDecoder - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var queryString = require('querystring')
|
||||
|
||||
// --- BodyDecoder
|
||||
|
||||
exports.BodyDecoder = Plugin.extend({
|
||||
on: {
|
||||
|
||||
@@ -15,7 +21,7 @@ exports.BodyDecoder = Plugin.extend({
|
||||
request: function(event) {
|
||||
var request = event.request
|
||||
if (request.header('content-type') &&
|
||||
request.header('content-type').indexOf('application/x-www-form-urlencoded') > -1)
|
||||
request.header('content-type').includes('application/x-www-form-urlencoded'))
|
||||
request.params.post = queryString.parseQuery(request.body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
// --- Cache
|
||||
|
||||
var Cache = Class({
|
||||
var Cache = new Class({
|
||||
|
||||
/**
|
||||
* Initialize cache with _key_ and _val_.
|
||||
*/
|
||||
|
||||
init: function(key, val) {
|
||||
constructor: function(key, val) {
|
||||
this.key = key
|
||||
this.val = val
|
||||
this.created = Number(new Date)
|
||||
@@ -18,7 +18,7 @@ var Cache = Class({
|
||||
|
||||
// --- Store
|
||||
|
||||
exports.Store = Class({
|
||||
exports.Store = new Class({
|
||||
|
||||
/**
|
||||
* Ensure that the given _key_ is a string.
|
||||
@@ -26,11 +26,13 @@ exports.Store = Class({
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @param {function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set: function(key, val) {
|
||||
if (typeof key !== 'string') throw new Error(this.name + ' store #set() key must be a string')
|
||||
set: function(key, val, fn) {
|
||||
if (typeof key !== 'string')
|
||||
throw new Error(this.name + ' store #set() key must be a string')
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -38,11 +40,13 @@ exports.Store = Class({
|
||||
* Override in subclass to provide data-store specific functionality.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get: function(key) {
|
||||
if (typeof key !== 'string') throw new Error(this.name + 'store #get() key must be a string')
|
||||
get: function(key, fn) {
|
||||
if (typeof key !== 'string')
|
||||
throw new Error(this.name + 'store #get() key must be a string')
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -71,22 +75,24 @@ exports.Store.Memory = exports.Store.extend({
|
||||
* Initialize data.
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
constructor: function() {
|
||||
this.data = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the given _key_ to _val_, returning _val_.
|
||||
* Set the given _key_ to _val_.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @param {function} callback
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set: function(key, val) {
|
||||
this.__super__(key, val)
|
||||
return this.data[key] = new Cache(key, val), val
|
||||
set: function(key, val, callback) {
|
||||
exports.Store.prototype.set.apply(this, arguments)
|
||||
this.data[key] = new Cache(key, val)
|
||||
if (callback) callback(val)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -94,31 +100,32 @@ exports.Store.Memory = exports.Store.extend({
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* cache.get('page:front')
|
||||
* cache.get('page:front', function(val){})
|
||||
* // => '<html>...</html>'
|
||||
*
|
||||
* cache.get('page:*')
|
||||
* cache.get('page:*', function(vals){})
|
||||
* // => { 'page:front': '<html>...</html>',
|
||||
* 'page:users': '<html>...</html>',
|
||||
* ... }
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} callback
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get: function(key) {
|
||||
this.__super__(key)
|
||||
get: function(key, callback) {
|
||||
exports.Store.prototype.get.apply(this, arguments)
|
||||
if (key.indexOf('*') === -1)
|
||||
return this.data[key] instanceof Cache ?
|
||||
this.data[key].val :
|
||||
null
|
||||
return callback(this.data[key] instanceof Cache
|
||||
? this.data[key].val
|
||||
: null)
|
||||
var regexp = this.normalize(key)
|
||||
return $(this.data).reduce({}, function(vals, cache){
|
||||
callback(this.data.reduce(function(vals, cache){
|
||||
if (regexp.test(cache.key))
|
||||
vals[cache.key] = cache.val
|
||||
return vals
|
||||
})
|
||||
}, {}))
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -126,21 +133,25 @@ exports.Store.Memory = exports.Store.extend({
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* cache.clear('page:front')
|
||||
* cache.clear('page:*')
|
||||
* cache.clear('page:front', function(){})
|
||||
* cache.clear('page:*', function(){})
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clear: function(key) {
|
||||
clear: function(key, callback) {
|
||||
if (key.indexOf('*') === -1)
|
||||
return delete this.data[key]
|
||||
var regexp = this.normalize(key)
|
||||
for (var key in this.data)
|
||||
if (this.data.hasOwnProperty(key))
|
||||
if (regexp.test(key))
|
||||
delete this.data[key]
|
||||
delete this.data[key]
|
||||
else {
|
||||
var regexp = this.normalize(key)
|
||||
for (var key in this.data)
|
||||
if (this.data.hasOwnProperty(key))
|
||||
if (regexp.test(key))
|
||||
delete this.data[key]
|
||||
}
|
||||
callback()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -150,13 +161,14 @@ exports.Store.Memory = exports.Store.extend({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms) {
|
||||
reap: function(ms, callback) {
|
||||
var self = this,
|
||||
threshold = Number(new Date(Number(new Date) - ms))
|
||||
$(this.data).each(function(cache){
|
||||
this.data.each(function(cache){
|
||||
if (cache.created < threshold)
|
||||
self.clear(cache.key)
|
||||
self.clear(cache.key, function(){})
|
||||
})
|
||||
if (callback) callback()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -194,7 +206,7 @@ exports.Cache = Plugin.extend({
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
process.mixin(this, options)
|
||||
this.merge(options || {})
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
Request.include({ cache: this.store })
|
||||
this.startReaper()
|
||||
@@ -203,16 +215,14 @@ exports.Cache = Plugin.extend({
|
||||
/**
|
||||
* Start reaper.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
startReaper: function() {
|
||||
var self = this,
|
||||
oneDay = 86400000,
|
||||
oneHour = 3600000
|
||||
setInterval(function(){
|
||||
self.store.reap(self.lifetime || oneDay)
|
||||
}, self.reapInterval || self.reapEvery || oneHour)
|
||||
startReaper: function(callbac) {
|
||||
setInterval(function(self){
|
||||
self.store.reap(self.lifetime || (1).day)
|
||||
}, this.reapInterval || this.reapEvery || (1).hour, this)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
// Express - CommonLogger - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Months.
|
||||
*/
|
||||
|
||||
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
|
||||
/**
|
||||
* Format _date_.
|
||||
*
|
||||
* @param {Date} date
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function format(date) {
|
||||
var d = date.getDate(),
|
||||
m = months[date.getMonth()],
|
||||
y = date.getFullYear(),
|
||||
h = date.getHours(),
|
||||
mi = date.getMinutes(),
|
||||
s = date.getSeconds()
|
||||
return (d < 10 ? '0' : '') + d + '/' + m + '/' + y + ' ' +
|
||||
(h < 10 ? '0' : '') + h + ':' + (mi < 10 ? '0' : '') +
|
||||
mi + ':' + (s < 10 ? '0' : '') + s
|
||||
}
|
||||
|
||||
// --- CommonLogger
|
||||
|
||||
exports.CommonLogger = Plugin.extend({
|
||||
on: {
|
||||
|
||||
/**
|
||||
* Output log data.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
puts([event.request.headers.host,
|
||||
'-',
|
||||
'-',
|
||||
'[' + format(new Date) + ']',
|
||||
'"' + event.request.method.toUpperCase() + ' ' + (event.request.url.pathname || '/') +
|
||||
' HTTP/' + event.request.httpVersion + '"',
|
||||
event.request.response.status,
|
||||
event.request.response.headers['content-length'] || 0].join(' '))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
@@ -93,9 +93,9 @@ exports.Cookie = Plugin.extend({
|
||||
|
||||
request: function(event) {
|
||||
event.request.response.cookies = []
|
||||
event.request.cookies = event.request.headers.cookie ?
|
||||
exports.parseCookie(event.request.headers.cookie) :
|
||||
{}
|
||||
event.request.cookies = event.request.headers.cookie
|
||||
? exports.parseCookie(event.request.headers.cookie)
|
||||
: {}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
// 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.
|
||||
*
|
||||
* When no arguments are given, all flashes are
|
||||
* returned keyed by their type.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* this.flash('info', 'email sent')
|
||||
* this.flash('info', 'email received')
|
||||
* this.flash('info')
|
||||
* // => ['email sent', 'email received']
|
||||
*
|
||||
* this.flash('info')
|
||||
* // => null
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @return {mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
flash: function(key, val) {
|
||||
if (!this.session.flash) this.session.flash = {}
|
||||
if (!key) {
|
||||
var flashes = this.session.flash
|
||||
this.session.flash = {}
|
||||
return flashes
|
||||
}
|
||||
if (!(key in this.session.flash)) this.session.flash[key] = []
|
||||
if (val)
|
||||
return this.session.flash[key].push(val), val
|
||||
else if (key) {
|
||||
var vals = this.session.flash[key]
|
||||
delete this.session.flash[key]
|
||||
if (vals.length) return vals
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,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,7 +55,7 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
$(before).each(function(fn){
|
||||
exports.callbacks.before.each(function(fn){
|
||||
fn.call(event.request, event.request)
|
||||
})
|
||||
},
|
||||
@@ -59,7 +65,7 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
$(after).each(function(fn){
|
||||
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,51 +0,0 @@
|
||||
|
||||
// Express - Profiler - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var n = 0
|
||||
|
||||
exports.Profiler = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize profiler options.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - format 'plot' outputs request duration in milliseconds only
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
process.mixin(this, options)
|
||||
}
|
||||
},
|
||||
|
||||
// --- Events
|
||||
|
||||
on: {
|
||||
|
||||
/**
|
||||
* Start timer.
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
this.start = Number(new Date)
|
||||
},
|
||||
|
||||
/**
|
||||
* Output duration.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
if (exports.Profiler.format === 'plot')
|
||||
puts(Number(new Date) - this.start)
|
||||
else
|
||||
puts(event.request.method + ' ' +
|
||||
event.request.url.pathname + ': ' +
|
||||
(Number(new Date) - this.start) + ' ms' +
|
||||
' #' + ++n)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -32,7 +32,7 @@ exports.Redirect = Plugin.extend({
|
||||
*/
|
||||
|
||||
redirect: function(url, code) {
|
||||
if (url == 'back' || url == 'home') url = this[url]
|
||||
if (url === 'back' || url === 'home') url = this[url]
|
||||
this.header('location', url)
|
||||
this.halt(code || 303)
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ var utils = require('express/utils')
|
||||
|
||||
// --- Session
|
||||
|
||||
var Session = Class({
|
||||
exports.Base = new Class({
|
||||
|
||||
/**
|
||||
* Initialize session _sid_.
|
||||
*/
|
||||
|
||||
init: function(sid) {
|
||||
constructor: function(sid) {
|
||||
this.id = sid
|
||||
this.touch()
|
||||
},
|
||||
@@ -33,7 +33,7 @@ var Session = Class({
|
||||
|
||||
// --- Store
|
||||
|
||||
exports.Store = Class({
|
||||
exports.Store = new Class({
|
||||
|
||||
/**
|
||||
* Convert to '[NAME Store]'.
|
||||
@@ -61,80 +61,101 @@ exports.Store.Memory = exports.Store.extend({
|
||||
* Initialize in-memory session store.
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
constructor: function() {
|
||||
this.store = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch session with the given _sid_ or
|
||||
* a new Session is returned.
|
||||
* a new Session is created.
|
||||
*
|
||||
* @param {int} sid
|
||||
* @return {Session}
|
||||
* @param {number} sid
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
fetch: function(sid) {
|
||||
return this.store[sid] || new Session(sid)
|
||||
fetch: function(sid, callback) {
|
||||
if (sid && this.store[sid])
|
||||
callback(null, this.store[sid])
|
||||
else
|
||||
this.generate(callback)
|
||||
},
|
||||
|
||||
/**
|
||||
* Commit _session_ data.
|
||||
*
|
||||
* @param {Session} session
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
commit: function(session) {
|
||||
return this.store[session.id] = session
|
||||
commit: function(session, callback) {
|
||||
this.store[session.id] = session
|
||||
if (callback) callback(null, session)
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all sessions.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clear: function() {
|
||||
clear: function(callback) {
|
||||
this.store = {}
|
||||
if (callback) callback()
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy session using the given _sid_.
|
||||
*
|
||||
* @param {int} sid
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
destroy: function(sid) {
|
||||
destroy: function(sid, callback) {
|
||||
delete this.store[sid]
|
||||
if (callback) callback(sid)
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the number of sessions currently stored.
|
||||
* Pass the number of sessions currently stored.
|
||||
*
|
||||
* @return {int}
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
length: function() {
|
||||
return $(this.store).length()
|
||||
length: function(callback) {
|
||||
callback(null, this.store.values.length)
|
||||
},
|
||||
|
||||
/**
|
||||
* Reap sessions older than _ms_.
|
||||
*
|
||||
* @param {int} ms
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms) {
|
||||
var self = this,
|
||||
threshold = Number(new Date(Number(new Date) - ms))
|
||||
$(this.store).each(function(session, sid){
|
||||
reap: function(ms, callback) {
|
||||
var threshold = Number(new Date(Number(new Date) - ms))
|
||||
this.store.each(function(session, sid){
|
||||
if (session.lastAccess < threshold)
|
||||
self.destroy(sid)
|
||||
})
|
||||
this.destroy(sid)
|
||||
}, this)
|
||||
if (callback) callback()
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and passes a shiny new session.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
generate: function(callback) {
|
||||
callback(null, new exports.Base(utils.uid()))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -157,7 +178,7 @@ exports.Session = Plugin.extend({
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
process.mixin(this, options)
|
||||
this.merge(options || {})
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
this.startReaper()
|
||||
},
|
||||
@@ -169,12 +190,9 @@ exports.Session = Plugin.extend({
|
||||
*/
|
||||
|
||||
startReaper: function() {
|
||||
var self = this,
|
||||
oneDay = 86400000,
|
||||
oneHour = 3600000
|
||||
setInterval(function(){
|
||||
self.store.reap(self.lifetime || oneDay)
|
||||
}, self.reapInterval || self.reapEvery || oneHour)
|
||||
setInterval(function(self) {
|
||||
self.store.reap(self.lifetime || (1).day)
|
||||
}, this.reapInterval || this.reapEvery || (1).hour, this)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -186,20 +204,30 @@ exports.Session = Plugin.extend({
|
||||
* Create session id when not found; delegate to store.
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
var sid
|
||||
if (!(sid = event.request.cookie('sid')))
|
||||
event.request.cookie('sid', sid = utils.uid(), set('session cookie'))
|
||||
event.request.session = exports.Session.store.fetch(sid)
|
||||
event.request.session.touch()
|
||||
request: function(event, next) {
|
||||
var sid= event.request.cookie('sid');
|
||||
exports.Session.store.fetch(sid, function(error, session) {
|
||||
if( error ) next(error);
|
||||
else {
|
||||
// If an invalid session id was passed then the id can change
|
||||
if( session.id != sid )
|
||||
event.request.cookie('sid',session.id, set('session cookie'))
|
||||
|
||||
event.request.session= session;
|
||||
event.request.session.touch();
|
||||
next();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Delegate to store, allowing it to save sessions changes.
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
exports.Session.store.commit(event.request.session)
|
||||
response: function(event, next) {
|
||||
exports.Session.store.commit(event.request.session, next)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,117 @@
|
||||
|
||||
// Express - Static - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs')
|
||||
|
||||
// --- File
|
||||
|
||||
exports.File = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with file _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
constructor: function(path) {
|
||||
this.path = path
|
||||
if (path.indexOf('..') != -1)
|
||||
Error.raise('InvalidPathError', "`" + path + "' is not a valid path")
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static file to the given _request_.
|
||||
*
|
||||
* - Ensures the file exists
|
||||
* - Ensures the file is a regular file (not FIFO, Socket, etc)
|
||||
* - Automatically assigns content type
|
||||
* - Halts with 404 when failing
|
||||
*
|
||||
* @param {Request} request
|
||||
* @settings 'cache static files'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sendTo: function(request) {
|
||||
var file = this.path
|
||||
function sendFromDisc() {
|
||||
path.exists(file, function(exists){
|
||||
if (!exists) return request.halt()
|
||||
fs.stat(file, function(err, stats){
|
||||
if (err) throw err
|
||||
if (!stats.isFile()) return request.halt()
|
||||
fs.readFile(file, 'binary', function(err, content){
|
||||
if (err) throw err
|
||||
request.contentType(file)
|
||||
if (set('cache static files'))
|
||||
request.cache.set('static:' + file, { type: file, content: content })
|
||||
request.halt(200, content, 'binary')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
if (set('cache static files'))
|
||||
request.cache.get('static:' + file, function(cache){
|
||||
if (cache)
|
||||
request.contentType(cache.type),
|
||||
request.halt(200, cache.content, 'binary')
|
||||
else
|
||||
sendFromDisc()
|
||||
})
|
||||
else
|
||||
sendFromDisc()
|
||||
}
|
||||
})
|
||||
|
||||
// --- Static
|
||||
|
||||
exports.Static = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
/**
|
||||
* Initialize routes and request extensions.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - path path from which to serve static files. Defaults to <root>/public
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
options = options || {}
|
||||
options.path = options.path || set('root') + '/public'
|
||||
|
||||
// Routes
|
||||
|
||||
get('/public/*', function(file){
|
||||
this.sendfile(options.path + '/' + file)
|
||||
})
|
||||
|
||||
// Request
|
||||
|
||||
Request.include({
|
||||
|
||||
/**
|
||||
* Transfer static file at the given _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sendfile: function(path) {
|
||||
(new exports.File(path)).sendTo(this)
|
||||
return this
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -5,18 +5,15 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var posix = require('posix'),
|
||||
utils = require('express/utils')
|
||||
var utils = require('express/utils'),
|
||||
extname = require('path').extname,
|
||||
fs = require('fs')
|
||||
|
||||
/**
|
||||
* Supported template engines.
|
||||
* Cache supported template engine exports.
|
||||
*/
|
||||
|
||||
var engine = {
|
||||
ejs: require('ejs'),
|
||||
haml: require('haml'),
|
||||
sass: require('sass')
|
||||
}
|
||||
var engines = {}
|
||||
|
||||
// --- View
|
||||
|
||||
@@ -31,7 +28,8 @@ exports.View = Plugin.extend({
|
||||
|
||||
// Settings
|
||||
|
||||
set('views', function(){ return set('root') + '/views' })
|
||||
if (!set('views'))
|
||||
set('views', function(){ return set('root') + '/views' })
|
||||
|
||||
// Request
|
||||
|
||||
@@ -51,9 +49,10 @@ 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
|
||||
@@ -66,13 +65,14 @@ exports.View = Plugin.extend({
|
||||
options = options || {},
|
||||
path = set('views') + '/' + view,
|
||||
type = path.split('.').slice(-2)[0],
|
||||
ext = utils.extname(path),
|
||||
layout = options.layout === undefined ? true : options.layout
|
||||
ext = extname(path),
|
||||
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(true, options, {
|
||||
self.render(layout + '.' + type + ext, utils.mixin(true, options, {
|
||||
layout: false,
|
||||
locals: {
|
||||
body: content
|
||||
@@ -81,16 +81,23 @@ exports.View = Plugin.extend({
|
||||
else
|
||||
self.halt(200, content)
|
||||
}
|
||||
if (set('cache view contents') && self.cache.get(path))
|
||||
render(self.cache.get(path))
|
||||
else
|
||||
posix.cat(path).addCallback(function(content){
|
||||
set('cache view contents') ?
|
||||
render(self.cache.set(path, content)) :
|
||||
render(content)
|
||||
}).addErrback(function(e){
|
||||
throw e
|
||||
function renderFromDisc() {
|
||||
fs.readFile(path, function(err, content){
|
||||
if (err) throw err
|
||||
set('cache view contents')
|
||||
? self.cache.set('view:' + path, content, function(cache){
|
||||
render(cache)
|
||||
})
|
||||
: render(content)
|
||||
})
|
||||
}
|
||||
if (set('cache view contents'))
|
||||
self.cache.get('view:' + path, function(cache){
|
||||
if (cache) render(cache)
|
||||
else renderFromDisc()
|
||||
})
|
||||
else
|
||||
renderFromDisc()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+91
-73
@@ -5,30 +5,12 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var StaticFile = require('express/static').File,
|
||||
statusBodies = require('http').STATUS_CODES,
|
||||
var statusBodies = require('http').STATUS_CODES,
|
||||
queryString = require('querystring'),
|
||||
mime = require('express/mime'),
|
||||
utils = require('express/utils'),
|
||||
url = require('url')
|
||||
|
||||
// --- InvalidStatusCode
|
||||
|
||||
InvalidStatusCode = ExpressError.extend({
|
||||
name: 'InvalidStatusCode',
|
||||
init: function(status) {
|
||||
this.message = status + ' is an invalid HTTP response code'
|
||||
}
|
||||
})
|
||||
|
||||
// --- InvalidResponseBody
|
||||
|
||||
InvalidResponseBody = ExpressError.extend({
|
||||
name: 'InvalidResponseBody',
|
||||
init: function(request) {
|
||||
this.message = request.method + ' ' + JSON.encode(request.url.pathname) + ' did not respond with a body string'
|
||||
}
|
||||
})
|
||||
|
||||
// --- Helpers
|
||||
|
||||
/**
|
||||
@@ -46,7 +28,7 @@ exports.normalizePath = function(path) {
|
||||
|
||||
// --- Request
|
||||
|
||||
exports.Request = Class({
|
||||
exports.Request = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with node's _request_ and _response_ objects.
|
||||
@@ -62,8 +44,8 @@ exports.Request = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(request, response) {
|
||||
process.mixin(true, this, request)
|
||||
constructor: function(request, response) {
|
||||
utils.mixin(true, this, request)
|
||||
response.headers = {}
|
||||
this.response = response
|
||||
this.url = url.parse(this.url)
|
||||
@@ -72,7 +54,7 @@ exports.Request = Class({
|
||||
this.params.path = {}
|
||||
this.params.get = this.url.query ? queryString.parseQuery(this.url.query) : {}
|
||||
this.params.post = this.params.post || {}
|
||||
this.plugins = $(Express.plugins).map(function(plugin){
|
||||
this.plugins = Express.plugins.map(function(plugin){
|
||||
return new plugin.klass(plugin.options)
|
||||
})
|
||||
},
|
||||
@@ -88,9 +70,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.lowercase]
|
||||
: this.response.headers[key.lowercase] = val
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -113,27 +95,50 @@ exports.Request = Class({
|
||||
|
||||
/**
|
||||
* Check if Accept header includes the mime type
|
||||
* for the given _path_, which calls mime.type().
|
||||
* 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}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
accepts: function(path) {
|
||||
return this.header('accept') ?
|
||||
this.header('accept').indexOf(mime.type(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
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the request was an xmlHttpRequest (ajax).
|
||||
*
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get isXHR() {
|
||||
return (this.header('x-requested-with') || '').lowercase === 'xmlhttprequest'
|
||||
},
|
||||
|
||||
/**
|
||||
* Set response status to _code_.
|
||||
*
|
||||
* @param {int} code
|
||||
* @return {Request)
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -148,9 +153,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
|
||||
@@ -158,11 +160,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)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -174,14 +175,33 @@ 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()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Pass control to the next matching route, or
|
||||
* the given _path_.
|
||||
*
|
||||
* NOTE: _path_ may be the request pathname only,
|
||||
* and may not contain a query string etc.
|
||||
*
|
||||
* @param {string} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
pass: function(path) {
|
||||
this.passed = path || true
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -199,22 +219,33 @@ exports.Request = Class({
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger even _name_ with optional _data_.
|
||||
* Trigger event _name_ with optional _data_ and _callback_ function.
|
||||
* The _callback_ function may be the second or third argument.
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {hash} data
|
||||
* @param {object} data
|
||||
* @param {function} callback
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
trigger: function(name, data) {
|
||||
data = process.mixin(data || {}, {
|
||||
request: this,
|
||||
response: this.response
|
||||
})
|
||||
this.plugins.each(function(plugin){
|
||||
plugin.trigger(new Event(name, data))
|
||||
})
|
||||
trigger: function(name, data, callback) {
|
||||
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 {
|
||||
if (self.plugins.at(complete++).trigger(new Event(name, data), next) !== true)
|
||||
next()
|
||||
}
|
||||
})()
|
||||
return this
|
||||
},
|
||||
|
||||
@@ -241,22 +272,9 @@ exports.Request = Class({
|
||||
*/
|
||||
|
||||
attachment: function(path) {
|
||||
this.header('content-disposition', path ?
|
||||
'attachment; filename="' + path + '"' :
|
||||
'attachment')
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static file at the given _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sendfile: function(path) {
|
||||
(new StaticFile(path)).send(this)
|
||||
this.header('content-disposition', path
|
||||
? 'attachment; filename="' + path + '"'
|
||||
: 'attachment')
|
||||
return this
|
||||
}
|
||||
})
|
||||
@@ -1,9 +1,57 @@
|
||||
|
||||
// Express - MockRequest - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils'),
|
||||
path = require('path'),
|
||||
fs = require('fs')
|
||||
|
||||
/**
|
||||
* Sync fs.readFile()
|
||||
*/
|
||||
|
||||
fs.readFile = function(path, encoding, fn) {
|
||||
if (encoding instanceof Function)
|
||||
fn = encoding,
|
||||
encoding = null
|
||||
try {
|
||||
fn(null, fs.readFileSync(path, encoding))
|
||||
} catch (e) {
|
||||
fn(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync path.exists()
|
||||
*/
|
||||
|
||||
path.exists = function(path, fn) {
|
||||
try {
|
||||
fs.statSync(path)
|
||||
fn(true)
|
||||
} catch (e) {
|
||||
fn(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync fs.stat()
|
||||
*/
|
||||
|
||||
fs.stat = function(path, fn) {
|
||||
try {
|
||||
fn(null, fs.statSync(path))
|
||||
} catch (e) {
|
||||
fn(e)
|
||||
}
|
||||
}
|
||||
|
||||
// --- MockRequest
|
||||
|
||||
var MockRequest = Class({
|
||||
var MockRequest = new Class({
|
||||
|
||||
/**
|
||||
* Default HTTP version.
|
||||
@@ -20,7 +68,7 @@ var MockRequest = Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(method, path, options) {
|
||||
constructor: function(method, path, options) {
|
||||
this.method = method
|
||||
this.url = path
|
||||
this.connection = {
|
||||
@@ -33,19 +81,19 @@ var MockRequest = Class({
|
||||
'accept-language': 'en-us',
|
||||
'connection': 'keep-alive'
|
||||
}
|
||||
process.mixin(true, this, options)
|
||||
utils.mixin(true, this, options)
|
||||
}
|
||||
})
|
||||
|
||||
// --- MockResponse
|
||||
|
||||
var MockResponse = Class({
|
||||
var MockResponse = new Class({
|
||||
|
||||
/**
|
||||
* Store _code_ and _headers_.
|
||||
*/
|
||||
|
||||
sendHeader: function(code, headers) {
|
||||
writeHeader: function(code, headers) {
|
||||
this.status = code
|
||||
this.headers = headers
|
||||
},
|
||||
@@ -54,7 +102,7 @@ var MockResponse = Class({
|
||||
* Store _body_.
|
||||
*/
|
||||
|
||||
sendBody: function(body) {
|
||||
write: function(body) {
|
||||
this.body = body
|
||||
},
|
||||
|
||||
@@ -62,7 +110,7 @@ var MockResponse = Class({
|
||||
* Flag response as finished.
|
||||
*/
|
||||
|
||||
finish: function() {
|
||||
close: function() {
|
||||
this.finished = true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
|
||||
// Express - Static - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var path = require('path'),
|
||||
posix = require('posix')
|
||||
|
||||
// --- InvalidPathError
|
||||
|
||||
InvalidPathError = ExpressError.extend({
|
||||
name: 'InvalidPathError',
|
||||
init: function(path) {
|
||||
this.message = "`" + path + "' is not a valid path"
|
||||
}
|
||||
})
|
||||
|
||||
// --- File
|
||||
|
||||
exports.File = Class({
|
||||
|
||||
/**
|
||||
* Initialize with file _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
init: function(path) {
|
||||
this.path = path
|
||||
if (path.indexOf('..') != -1) throw new InvalidPathError(path)
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static file to the given _request_.
|
||||
*
|
||||
* - Ensures the file exists
|
||||
* - Ensures the file is a regular file (not FIFO, Socket, etc)
|
||||
* - Automatically assigns content type
|
||||
* - Halts with 404 when failing
|
||||
*
|
||||
* @param {Request} request
|
||||
* @settings 'cache static files'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
send: function(request) {
|
||||
var cache, file = this.path
|
||||
if (set('cache static files') && (cache = request.cache.get(file)))
|
||||
return request.contentType(cache.type),
|
||||
request.halt(200, cache.content, 'binary')
|
||||
path.exists(file, function(exists){
|
||||
if (!exists) return request.halt()
|
||||
posix.stat(file).addCallback(function(stats){
|
||||
if (!stats.isFile()) return request.halt()
|
||||
posix.cat(file, 'binary').addCallback(function(content){
|
||||
request.contentType(file)
|
||||
if (set('cache static files'))
|
||||
request.cache.set(file, { type: file, content: content })
|
||||
request.halt(200, content, 'binary')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
+68
-65
@@ -28,32 +28,6 @@ exports.uid = function() {
|
||||
return uid
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the extension name of the given _path_,
|
||||
* or null when not present.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.extname = function(path) {
|
||||
if (path.lastIndexOf('.') < 0) return
|
||||
return path.slice(path.lastIndexOf('.') + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the basename of the given _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.basename = function(path) {
|
||||
return path.split('/').slice(-1)[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters in _html_.
|
||||
*
|
||||
@@ -63,45 +37,13 @@ exports.basename = function(path) {
|
||||
*/
|
||||
|
||||
exports.escape = function(html) {
|
||||
return html.toString()
|
||||
return String(html)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert native array-like objects into an
|
||||
* array with optional _offset_.
|
||||
*
|
||||
* @param {object} arr
|
||||
* @param {int} offset
|
||||
* @return {array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.toArray = function(arr, offset) {
|
||||
return Array.prototype.slice.call(arr, offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape RegExp _chars_ in _string_. Where _chars_
|
||||
* defaults to regular expression special characters.
|
||||
*
|
||||
* _chars_ should be a space delimited list of characters,
|
||||
* for example '[ ] ( )'.
|
||||
*
|
||||
* @param {string} string
|
||||
* @param {string} chars
|
||||
* @return {Type}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.escapeRegexp = function(string, chars) {
|
||||
var specials = (chars || '/ . * + ? | ( ) [ ] { } \\').split(' ').join('|\\')
|
||||
return string.replace(new RegExp('(\\' + specials + ')', 'g'), '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge param _key_ and _val_ into _params_. Key
|
||||
* should be a query string key such as 'user[name]',
|
||||
@@ -118,17 +60,78 @@ exports.mergeParam = function(key, val, params) {
|
||||
var orig = params,
|
||||
keys = key.trim().match(/\w+/g),
|
||||
array = /\[\]$/.test(key)
|
||||
$(keys).reduce(queryString.parseQuery(key), function(parts, key, i){
|
||||
keys.reduce(function(parts, key, i){
|
||||
if (i === keys.length - 1)
|
||||
if (key in params)
|
||||
params[key] instanceof Array ?
|
||||
params[key].push(val) :
|
||||
params[key] = [params[key], val]
|
||||
params[key] instanceof Array
|
||||
? params[key].push(val)
|
||||
: params[key] = [params[key], val]
|
||||
else
|
||||
params[key] = array ? [val] : val
|
||||
if (!(key in params)) params[key] = {}
|
||||
params = params[key]
|
||||
return parts[key]
|
||||
})
|
||||
}, queryString.parseQuery(key))
|
||||
return orig
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
};
|
||||
Submódulo
+1
Submodule lib/support/class added at 5ed0e4aaec
@@ -1,53 +0,0 @@
|
||||
/* Ejs template parser for CommonJS
|
||||
*
|
||||
* Copyright (c) 2009, Howard Rauscher
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* base on:
|
||||
* Simple JavaScript Templating (http://ejohn.org/blog/javascript-micro-templating/)
|
||||
* John Resig - http://ejohn.org/ - MIT Licensed
|
||||
*/
|
||||
|
||||
(function(){
|
||||
var cache = {};
|
||||
|
||||
var ejs = this.ejs = {};
|
||||
|
||||
ejs.parse = function tmpl(str, options) {
|
||||
options = options || {};
|
||||
options.context = options.context || {};
|
||||
options.locals = options.locals || {};
|
||||
|
||||
// Figure out if we're getting a template, or if we need to
|
||||
// load the template - and be sure to cache the result.
|
||||
var fn = cache[str] ||
|
||||
|
||||
// Generate a reusable function that will serve as a template
|
||||
// generator (and which will be cached).
|
||||
new Function("obj",
|
||||
"var p=[];" +
|
||||
|
||||
// Introduce the data as local variables using with(){}
|
||||
"with(obj){p.push('" +
|
||||
|
||||
// Convert the template into pure JavaScript
|
||||
str
|
||||
.replace(/\-%>(\n|\r)/g, "%>")
|
||||
.replace(/[\t\b\f]/g, " ")
|
||||
.replace(/[\n\r]/g, "\f")
|
||||
.split("<%").join("\t")
|
||||
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
||||
.replace(/\t=(.*?)%>/g, "',$1,'")
|
||||
.split("\t").join("');")
|
||||
.split("%>").join("p.push('")
|
||||
.split("\r").join("\\'").replace(/\f+/g, '\\n') +
|
||||
"');}return p.join('');");
|
||||
|
||||
cache[str] = fn;
|
||||
|
||||
// Provide some basic currying to the user
|
||||
return fn.call(options.context, options.locals);
|
||||
};
|
||||
})();
|
||||
|
||||
exports.render = ejs.parse;
|
||||
Submódulo
+1
Submodule lib/support/ext added at c29d6a0678
+1
-1
Submodule lib/support/haml updated: b1fa42226b...389c33c6e4
Submodule lib/support/js-oo deleted from 1f94bd8979
+1
-1
Submodule lib/support/sass updated: dfc1cd027f...2a648b3766
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "0.8.0",
|
||||
"keywords": ["framework", "sinatra", "web", "rest", "restful"],
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "git submodule update --init",
|
||||
"test": "make test"
|
||||
},
|
||||
"engines": { "node": ">= 0.1.30" }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
name: Express
|
||||
description: Sinatra inspired web development framework
|
||||
version: 0.8.0
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
%h2= this.name
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
%h2 Hello
|
||||
externo
+2
@@ -0,0 +1,2 @@
|
||||
%html
|
||||
%body= body
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
%title= "Viewing " + name
|
||||
%body= body
|
||||
externo
+2
@@ -0,0 +1,2 @@
|
||||
%title= this.title
|
||||
%body= body
|
||||
externo
+2
@@ -0,0 +1,2 @@
|
||||
%h1= name
|
||||
%p= email
|
||||
@@ -1521,6 +1521,8 @@
|
||||
*/
|
||||
|
||||
error : function(message, e) {
|
||||
if ('stack' in e)
|
||||
require('sys').puts(e.stack + '\n')
|
||||
throw (message ? message : '') + e.toString() +
|
||||
(e.line ? ' near line ' + e.line : '')
|
||||
},
|
||||
|
||||
+6
-26
@@ -1,21 +1,12 @@
|
||||
|
||||
require.paths.unshift('spec', 'lib', 'spec/lib', 'spec/support/libxmljs')
|
||||
require.paths.unshift('spec', 'lib', 'spec/lib')
|
||||
require("jspec")
|
||||
require("express")
|
||||
require("express/spec")
|
||||
|
||||
quit = process.exit
|
||||
print = puts
|
||||
|
||||
readFile = function(path) {
|
||||
var result
|
||||
require('posix')
|
||||
.cat(path, "utf8")
|
||||
.addCallback(function(contents){ result = contents })
|
||||
.addErrback(function(){ throw new Error("failed to read file `" + path + "'") })
|
||||
.wait()
|
||||
return result
|
||||
}
|
||||
quit = process.exit
|
||||
readFile = require('fs').readFileSync
|
||||
|
||||
function run(specs) {
|
||||
specs.forEach(function(spec){
|
||||
@@ -30,12 +21,9 @@ specs = {
|
||||
'utils',
|
||||
'request',
|
||||
'mime',
|
||||
'static',
|
||||
'collection',
|
||||
'plugins',
|
||||
'plugins.cache',
|
||||
'plugins.view',
|
||||
'plugins.common-logger',
|
||||
'plugins.content-length',
|
||||
'plugins.method-override',
|
||||
'plugins.body-decoder',
|
||||
@@ -43,22 +31,14 @@ specs = {
|
||||
'plugins.hooks',
|
||||
'plugins.cookie',
|
||||
'plugins.session',
|
||||
],
|
||||
dependant: [
|
||||
'element-collection'
|
||||
]
|
||||
'plugins.flash',
|
||||
'plugins.static',
|
||||
]
|
||||
}
|
||||
|
||||
switch (process.ARGV[2]) {
|
||||
case 'independant':
|
||||
run(specs.independant)
|
||||
break
|
||||
case 'dependant':
|
||||
run(specs.dependant)
|
||||
break
|
||||
case 'all':
|
||||
run(specs.independant)
|
||||
run(specs.dependant)
|
||||
break
|
||||
default:
|
||||
run([process.ARGV[2]])
|
||||
|
||||
@@ -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
|
||||
@@ -33,18 +33,25 @@ describe 'Express'
|
||||
describe '#set()'
|
||||
describe 'given a key and value'
|
||||
it 'should set the cache data'
|
||||
store.set('foo', 'bar')
|
||||
store.get('foo').should.eql 'bar'
|
||||
var result
|
||||
store.set('foo', 'bar', function(){
|
||||
store.get('foo', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
result.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'
|
||||
var result
|
||||
store.set('foo', 'bar', function(){
|
||||
store.set('foo', 'baz', function(){
|
||||
store.get('foo', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
})
|
||||
result.should.eql 'baz'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -56,30 +63,41 @@ describe 'Express'
|
||||
|
||||
describe 'given an abitrary value'
|
||||
it 'should serialize as JSON'
|
||||
store.set('user', { name: 'tj' }).should.eql { name: 'tj' }
|
||||
var result
|
||||
store.set('user', { name: 'tj' }, function(val){
|
||||
result = val
|
||||
})
|
||||
result.should.eql { name: 'tj' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get()'
|
||||
describe 'given a key'
|
||||
it 'should 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' }
|
||||
var result
|
||||
store.set('user', { name: 'tj' }, function(){
|
||||
store.get('user', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
result.should.eql { name: 'tj' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should 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' }
|
||||
it 'should pass a set of caches'
|
||||
var result
|
||||
store.set('user:1', 'a', function(){
|
||||
store.set('user:2', 'b', function(){
|
||||
store.set('foo', 'bar', function(){
|
||||
store.get('user:*', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
result.should.eql { 'user:1': 'a', 'user:2': 'b' }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -87,19 +105,34 @@ describe 'Express'
|
||||
describe '#clear()'
|
||||
describe 'given a key'
|
||||
it 'should delete previous data'
|
||||
store.set('foo', 'bar')
|
||||
store.clear('foo')
|
||||
store.get('foo').should.be_null
|
||||
var result
|
||||
store.set('foo', 'bar', function(){
|
||||
store.clear('foo', function(){
|
||||
store.get('foo', function(val){
|
||||
result = val
|
||||
})
|
||||
})
|
||||
})
|
||||
result.should.be_null
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given wildcards'
|
||||
it 'should clear a set of caches'
|
||||
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
|
||||
var results = []
|
||||
store.set('user:one', '1', function(){
|
||||
store.set('user:two', '2', function(){
|
||||
store.clear('user:*', function(){
|
||||
store.get('user:one', function(val){
|
||||
results.push(val)
|
||||
store.get('user:two', function(val){
|
||||
results.push(val)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
results.should.eql [null, null]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -107,12 +140,16 @@ describe 'Express'
|
||||
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.data['user:one'].created = Number((5).minutes.ago)
|
||||
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
|
||||
store.data['user:two'].created = Number((2).seconds.ago)
|
||||
store.reap((1).minute)
|
||||
store.get('user:one', function(val){
|
||||
val.should.be_null
|
||||
})
|
||||
store.get('user:two', function(val){
|
||||
val.should.not.be_null
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/cookie').Cookie)
|
||||
use(require('express/plugins/session').Session)
|
||||
use(require('express/plugins/flash').Flash)
|
||||
Session.store.clear()
|
||||
var sess = new Base(123)
|
||||
Session.store.commit(sess, function(){})
|
||||
end
|
||||
|
||||
describe 'Flash'
|
||||
describe '#flash()'
|
||||
describe 'given a type and msg'
|
||||
it 'should push a flash message'
|
||||
var headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/', function(){ return this.flash('info', 'email sent') })
|
||||
get('/', function(){ return this.flash('info').join(', ') })
|
||||
post('/', headers)
|
||||
post('/', headers)
|
||||
get('/', headers).body.should.eql 'email sent, email sent'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given a type'
|
||||
describe 'when no messages have been pushed'
|
||||
it 'should return null'
|
||||
var headers = { headers: { cookie: 'sid=123' }}
|
||||
get('/', function(){ return this.flash('info') || 'empty' })
|
||||
get('/', headers).body.should.eql 'empty'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when messages have been pushed'
|
||||
it 'should persist only until flushed'
|
||||
var headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/', function(){ return this.flash('info', 'email sent') })
|
||||
get('/', function(){ return (this.flash('info') || ['nope!']).join(', ') })
|
||||
post('/', headers)
|
||||
post('/', headers)
|
||||
get('/', headers).body.should.eql 'email sent, email sent'
|
||||
get('/', headers).body.should.eql 'nope!'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given no arguments'
|
||||
it 'should provide access to all types'
|
||||
var flash, headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/', function(){ return this.flash('info', 'email sent') })
|
||||
post('/error', function(){ return this.flash('error', 'email failed to send') })
|
||||
get('/', function(){ return flash = this.flash(), '' })
|
||||
post('/', headers)
|
||||
post('/', headers)
|
||||
post('/error', headers)
|
||||
get('/')
|
||||
flash.should.eql { info: ['email sent', 'email sent'], error: ['email failed to send'] }
|
||||
end
|
||||
|
||||
it 'should persist only until flushed'
|
||||
var flash, headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/', function(){ return this.flash('info', 'email sent') })
|
||||
post('/error', function(){ return this.flash('error', 'email failed to send') })
|
||||
get('/', function(){ return flash = this.flash(), '' })
|
||||
post('/', headers)
|
||||
post('/', headers)
|
||||
post('/error', headers)
|
||||
get('/', headers)
|
||||
flash.should.eql { info: ['email sent', 'email sent'], error: ['email failed to send'] }
|
||||
get('/', headers)
|
||||
flash.should.eql {}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,10 @@
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/hooks').Hooks)
|
||||
hooks = require('express/plugins/hooks')
|
||||
use(hooks.Hooks)
|
||||
hooks.callbacks.before = []
|
||||
hooks.callbacks.after = []
|
||||
end
|
||||
|
||||
describe 'Hooks'
|
||||
@@ -17,6 +20,15 @@ describe 'Express'
|
||||
get('/user', function(){})
|
||||
get('/user').body.should.eql 'foobar'
|
||||
end
|
||||
|
||||
it 'should be able to halt the request'
|
||||
GLOBAL.before(function(){
|
||||
this.halt(404, 'woo!')
|
||||
})
|
||||
get('/user', function(){ return 'fail' })
|
||||
get('/user').status.should.eql 404
|
||||
get('/user').body.should.eql 'woo!'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after()'
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
use(require('express/plugins/cookie').Cookie)
|
||||
use(Session = require('express/plugins/session').Session)
|
||||
Base = require('express/plugins/session').Base
|
||||
Session.store.clear()
|
||||
end
|
||||
|
||||
@@ -13,17 +15,28 @@ describe 'Express'
|
||||
get('/login').headers['set-cookie'].should.match(/^sid=(\w+);/)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when sid cookie is present'
|
||||
|
||||
describe 'when existing sid cookie is present'
|
||||
it 'should not set sid'
|
||||
Session.store.commit(new Base(123))
|
||||
get('/login', function(){ return '' })
|
||||
get('/login', { headers: { cookie: 'sid=123' }}).headers.should.not.have_property 'set-cookie'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe 'when unknown sid cookie is present'
|
||||
it 'should set new sid'
|
||||
get('/login', function(){ return '' })
|
||||
var headers= get('/login', { headers: { cookie: 'sid=123' }}).headers
|
||||
|
||||
headers.should.have_property 'set-cookie'
|
||||
headers['set-cookie'].should.not.be '123'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'session Store.Memory'
|
||||
before_each
|
||||
memory = new (require('express/plugins/session').Store.Memory)
|
||||
Session.store= memory = new (require('express/plugins/session').Store.Memory)
|
||||
end
|
||||
|
||||
it 'should persist'
|
||||
@@ -33,6 +46,7 @@ describe 'Express'
|
||||
get('/login', function(){
|
||||
return this.session.name
|
||||
})
|
||||
memory.commit(new Base(123))
|
||||
var headers = { headers: { cookie: 'sid=123' }}
|
||||
post('/login', headers)
|
||||
get('/login', headers).status.should.eql 200
|
||||
@@ -48,14 +62,22 @@ describe 'Express'
|
||||
describe '#fetch()'
|
||||
describe 'when the session does not exist'
|
||||
it 'should return a new Session'
|
||||
memory.fetch('1').should.have_property 'lastAccess'
|
||||
var result
|
||||
memory.fetch('1', function(error,session){
|
||||
result= session
|
||||
})
|
||||
result.should.have_property 'lastAccess'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the session does exist'
|
||||
it 'should return the previous session'
|
||||
memory.commit({ id: '1', same: true })
|
||||
memory.fetch('1').should.have_property 'same', true
|
||||
var result
|
||||
memory.commit(new Base('1'))
|
||||
memory.fetch('1', function(error, session){
|
||||
result = session
|
||||
})
|
||||
result.id.should.eql '1'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -72,9 +94,13 @@ describe 'Express'
|
||||
|
||||
describe '#length()'
|
||||
it 'should return the number of session'
|
||||
var length
|
||||
memory.commit({ id: '1' })
|
||||
memory.commit({ id: '2' })
|
||||
memory.length().should.eql 2
|
||||
memory.length(function(error, len) {
|
||||
length = len
|
||||
})
|
||||
length.should.eql 2
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
describe 'Express'
|
||||
describe 'StaticFile'
|
||||
before
|
||||
StaticFile = require('express/plugins/static').File
|
||||
use(require('express/plugins/static').Static, { path: 'spec/fixtures' })
|
||||
end
|
||||
|
||||
describe '#constructor'
|
||||
it 'should accept and assign #path'
|
||||
(new StaticFile('/foo/bar')).path.should.eql '/foo/bar'
|
||||
end
|
||||
|
||||
it 'should throw an InvalidPathError when .. is found'
|
||||
// TODO: use throw_error when fixed...
|
||||
try { new StaticFile('/../foobar') }
|
||||
catch (e) {
|
||||
e.name.should.eql 'InvalidPathError'
|
||||
e.message.should.eql "`/../foobar' is not a valid path"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /public/*'
|
||||
it 'should transfer static files'
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sendfile()'
|
||||
describe 'when the file exists'
|
||||
it 'should transfer the file'
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
end
|
||||
|
||||
it 'should automatically set the content type based on extension'
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when "cache static files" is enabled'
|
||||
it 'should transfer the file'
|
||||
enable('cache static files')
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/cached', function(){
|
||||
var self = this
|
||||
this.cache.get('static:spec/fixtures/user.json', function(val){
|
||||
self.halt(200, val ? 'yes' : 'no')
|
||||
})
|
||||
})
|
||||
get('/cached').body.should.eql 'no'
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
get('/cached').body.should.eql 'yes'
|
||||
get('/cached').body.should.eql 'yes'
|
||||
end
|
||||
|
||||
it 'should automatically set the content type based on extension'
|
||||
enable('cache static files')
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -11,5 +11,142 @@ describe 'Express'
|
||||
set('views').should.eql 'spec/views'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#render()'
|
||||
before_each
|
||||
set('views', 'spec/fixtures')
|
||||
end
|
||||
|
||||
describe 'given a valid view name'
|
||||
describe 'and layout of the same type exists'
|
||||
it 'should render the layout and view'
|
||||
get('/', function(){
|
||||
this.render('hello.haml.html')
|
||||
})
|
||||
get('/').body.should.include '<html><body>'
|
||||
get('/').body.should.include '<h2>Hello'
|
||||
end
|
||||
|
||||
it 'should default context to the current request'
|
||||
get('/', function(){
|
||||
this.title = 'Welcome'
|
||||
this.render('page.haml.html', { layout: false })
|
||||
})
|
||||
get('/').body.should.include '<title>Welcome'
|
||||
end
|
||||
|
||||
it 'should set the content type based on the last path segment'
|
||||
get('/', function(){
|
||||
this.render('hello.haml.html')
|
||||
})
|
||||
get('/').headers['content-type'].should.eql 'text/html'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and layout of the same type does not exist'
|
||||
it 'should throw an error'
|
||||
get('/', function(){
|
||||
this.render('hello.haml.html', { layout: 'front' })
|
||||
})
|
||||
-{ get('/') }.should.throw_error 'No such file or directory'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given a custom layout name'
|
||||
it 'should render the layout and view'
|
||||
get('/', function(){
|
||||
this.title = 'Express'
|
||||
this.render('hello.haml.html', { layout: 'page' })
|
||||
})
|
||||
get('/').body.should.include '<title>Express'
|
||||
get('/').body.should.include '<h2>Hello'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when layout: false'
|
||||
it 'should render the view only'
|
||||
get('/', function(){
|
||||
this.render('hello.haml.html', { layout: false })
|
||||
})
|
||||
get('/').body.should.not.include '<body>'
|
||||
get('/').body.should.include '<h2>Hello'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when "cache view contents" is enabled'
|
||||
it 'should read the views into memory only once'
|
||||
enable('cache view contents')
|
||||
get('/', function(){
|
||||
this.render('hello.haml.html')
|
||||
})
|
||||
get('/cached', function(){
|
||||
var self = this
|
||||
this.cache.get('view:spec/fixtures/hello.haml.html', function(val){
|
||||
self.halt(200, val ? 'yes' : 'no')
|
||||
})
|
||||
})
|
||||
get('/cached').body.should.eql 'no'
|
||||
get('/')
|
||||
get('/cached').body.should.eql 'yes'
|
||||
get('/')
|
||||
get('/cached').body.should.eql 'yes'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when engine cannot be found'
|
||||
it 'should throw an error'
|
||||
get('/', function(){
|
||||
this.render('user.invalid.html')
|
||||
})
|
||||
-{ get('/') }.should.throw_error "Cannot find module 'invalid'"
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when locals are passed'
|
||||
it 'should have direct access to locals'
|
||||
get('/user', function(){
|
||||
this.render('user.haml.html', {
|
||||
locals: {
|
||||
name: 'tj',
|
||||
email: 'tj@vision-media.ca'
|
||||
}
|
||||
})
|
||||
})
|
||||
get('/user').body.should.include '<h1>tj'
|
||||
get('/user').body.should.include '<p>tj@vision-media.ca'
|
||||
end
|
||||
|
||||
it 'should have direct access to locals within the layout'
|
||||
get('/user', function(){
|
||||
this.render('user.haml.html', {
|
||||
layout: 'layout.user',
|
||||
locals: {
|
||||
name: 'tj',
|
||||
email: 'tj@vision-media.ca'
|
||||
}
|
||||
})
|
||||
})
|
||||
get('/user').body.should.include '<title>Viewing tj'
|
||||
get('/user').body.should.include '<h1>tj'
|
||||
get('/user').body.should.include '<p>tj@vision-media.ca'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when context is passed'
|
||||
it 'should evaluate in context to that object'
|
||||
get('/article', function(){
|
||||
this.render('article.haml.html', {
|
||||
context: {
|
||||
name: 'Writing a Node.js Web Application'
|
||||
}
|
||||
})
|
||||
})
|
||||
get('/article').body.should.include '<h2>Writing a Node.js Web Application'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
+68
-13
@@ -39,6 +39,23 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#isXHR'
|
||||
it 'should return false unless X-Requested-With is "XMLHttpRequest"'
|
||||
get('/', function(){ return this.isXHR ? 'yay' : 'nope' })
|
||||
get('/').body.should.eql 'nope'
|
||||
end
|
||||
|
||||
it 'should return true when X-Requested-With is "XMLHttpRequest"'
|
||||
get('/', function(){ return this.isXHR ? 'yay' : 'nope' })
|
||||
get('/', { headers: { 'x-requested-with': 'XMLHttpRequest' }}).body.should.eql 'yay'
|
||||
end
|
||||
|
||||
it 'should be case insensitive'
|
||||
get('/', function(){ return this.isXHR ? 'yay' : 'nope' })
|
||||
get('/', { headers: { 'x-requested-with': 'xmlhttprequest' }}).body.should.eql 'yay'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#accepts()'
|
||||
describe 'when the Accept header is present'
|
||||
it 'should return true if the mime type is acceptable'
|
||||
@@ -58,6 +75,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,18 +124,6 @@ describe 'Express'
|
||||
get('/user').body.should.include('Oh noes!')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given an invalid status code'
|
||||
it 'should throw an InvalidStatusCode exception'
|
||||
// TODO: use throw_error when fixed...
|
||||
get('/user', function(){ this.halt(123123) })
|
||||
try { get('/user') }
|
||||
catch (e) {
|
||||
e.should.be_an_instance_of ExpressError
|
||||
e.should.be_an_instance_of InvalidStatusCode
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#contentType()'
|
||||
@@ -187,6 +215,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,19 +84,6 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with no response body'
|
||||
it 'should throw a InvalidResponseBody'
|
||||
// TODO: use throw_error when fixed...
|
||||
get('/user', function(){
|
||||
this.respond()
|
||||
})
|
||||
try { get('/user') }
|
||||
catch (e) {
|
||||
e.should.be_an_instance_of InvalidResponseBody
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with regular expression'
|
||||
it 'should match'
|
||||
get(/^\/user\/(\d+)\/(\w+)/, function(id, operation){
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
describe 'Express'
|
||||
describe 'StaticFile'
|
||||
before
|
||||
StaticFile = require('express/static').File
|
||||
end
|
||||
|
||||
describe '#init'
|
||||
it 'should accept and assign #path'
|
||||
(new StaticFile('/foo/bar')).path.should.eql '/foo/bar'
|
||||
end
|
||||
|
||||
it 'should throw an InvalidPathError when .. is found'
|
||||
// TODO: use throw_error when fixed...
|
||||
try { new StaticFile('/../foobar') }
|
||||
catch (e) {
|
||||
e.should.be_an_instance_of InvalidPathError
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+2
-36
@@ -4,21 +4,6 @@ describe 'Express'
|
||||
utils = require('express/utils')
|
||||
end
|
||||
|
||||
describe 'toArray()'
|
||||
describe 'when given an array'
|
||||
it 'should return the array'
|
||||
utils.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')
|
||||
utils.toArray(args).should.eql ['foo', 'bar']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'escape()'
|
||||
it 'should escape html'
|
||||
utils.escape('<p>this & that').should.eql '<p>this & that'
|
||||
@@ -32,31 +17,12 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'extname()'
|
||||
it 'should return the a files extension'
|
||||
utils.extname('image.png').should.eql 'png'
|
||||
utils.extname('image.large.png').should.eql 'png'
|
||||
utils.extname('/path/to/image.large.png').should.eql 'png'
|
||||
end
|
||||
|
||||
it 'should return null when not found'
|
||||
utils.extname('path').should.be_null
|
||||
utils.extname('/just/a/path').should.be_null
|
||||
end
|
||||
end
|
||||
|
||||
describe 'basename()'
|
||||
it 'should return a files basename'
|
||||
utils.basename('foo/bar/baz.image.png').should.eql 'baz.image.png'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'mergeParam()'
|
||||
describe 'with empty params'
|
||||
it 'should merge the given key and value'
|
||||
params = {}
|
||||
utils.mergeParam('user[names][first]', 'tj', params)
|
||||
params.user.names.first.should.eql 'tj'
|
||||
utils.mergeParam('user[names][firstName]', 'tj', params)
|
||||
params.user.names.firstName.should.eql 'tj'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Submodule spec/support/libxmljs deleted from 0be1b6f48f
Referência em uma Nova Issue
Bloquear um usuário