Comparar commits

...

22 Commits

Autor SHA1 Mensagem Data
visionmedia c8dd169ad9 Release 0.2.0 2010-02-03 19:28:31 -08:00
visionmedia 5495fbcd0c Merge branch 'upload' 2010-02-03 19:26:34 -08:00
visionmedia eb94667ec8 Refactored mergeParam() 2010-02-03 19:26:09 -08:00
visionmedia cf31355515 Added parseParam() support for name[] etc. Closes #180
PS. this is awesome! because now with WebKit (and opera?) you can have
<input type="file" name="images[]" multiple />
2010-02-03 19:24:33 -08:00
visionmedia 604c359a1c Fixed another spec 2010-02-03 18:45:39 -08:00
visionmedia dad64b8a3b Fixed specs 2010-02-03 18:45:08 -08:00
visionmedia ffa432baa2 More mergeParam() specs 2010-02-03 18:42:41 -08:00
visionmedia 5c4a356348 Added more specs 2010-02-03 18:23:31 -08:00
visionmedia ef949aec20 Specs for key[] with uploads 2010-02-03 18:20:59 -08:00
visionmedia eaea3f188d Added more mergeParam() specs 2010-02-03 18:14:25 -08:00
visionmedia 818135789b Added multiple file input for testing 2010-02-03 18:08:43 -08:00
visionmedia 2bbe573748 Removed some settings from chat app.
These can be used via EXPRESS_ENV=production
2010-02-03 17:38:55 -08:00
visionmedia 9efb15b267 Both Cache and Session option "reapInterval" may be "reapEvery". Closes #174 2010-02-03 16:59:00 -08:00
visionmedia 5ce1306b75 Merge branch 'cache-lifetime' 2010-02-03 16:57:24 -08:00
visionmedia 75c85663ad use() of the same plugin several time will always use latest options. Closes #176 2010-02-03 16:57:19 -08:00
visionmedia 8c1bcc4c47 Added expiration support to cache api with reaper. Closes #133 2010-02-03 16:27:25 -08:00
visionmedia 200d09c7bd cache Memory.Store#get() utilizing Collection 2010-02-03 16:11:01 -08:00
visionmedia ef86474830 Added cache Store.Memory#reap() 2010-02-03 16:01:25 -08:00
visionmedia e2fee9b353 Cache api using Cache instances 2010-02-03 15:57:59 -08:00
visionmedia 542cab1123 Renamed MemoryStore -> Store.Memory 2010-02-03 15:43:29 -08:00
visionmedia 315c05034f Merge branch 'abstract-session' 2010-02-03 15:33:35 -08:00
visionmedia 148d34629b Added abstract session Store. Closes #172 2010-02-03 15:33:29 -08:00
16 arquivos alterados com 320 adições e 120 exclusões
+13
Ver Arquivo
@@ -1,4 +1,17 @@
0.2.0 / 2010-02-03
==================
* Added parseParam() support for name[] etc. (allows for file inputs with "multiple" attr) Closes #180
* Added Both Cache and Session option "reapInterval" may be "reapEvery". Closes #174
* Added expiration support to cache api with reaper. Closes #133
* Added cache Store.Memory#reap()
* Added Cache; cache api now uses first class Cache instances
* Added abstract session Store. Closes #172
* Changed; cache Memory.Store#get() utilizing Collection
* Renamed MemoryStore -> Store.Memory
* Fixed use() of the same plugin several time will always use latest options. Closes #176
0.1.0 / 2010-02-03
==================
+1 -2
Ver Arquivo
@@ -10,10 +10,9 @@ configure(function(){
use(ContentLength)
use(CommonLogger)
use(Cookie)
use(Cache, { lifetime: fiveMinutes, reapInterval: oneMinute })
use(Session, { lifetime: fiveMinutes, reapInterval: oneMinute })
set('root', __dirname)
enable('cache static files')
//enable('cache view contents')
})
var messages = [],
-1
Ver Arquivo
@@ -8,7 +8,6 @@ configure(function(){
use(ContentLength)
use(CommonLogger)
set('root', __dirname)
enable('cache view contents')
})
get('/', function(){
+3 -10
Ver Arquivo
@@ -12,6 +12,8 @@ h1, h2
h1
:text-shadow 1px 2px 2px #ddd
:font-size 60px
h2
:margin-top 15px
#wrapper
:position relative
@@ -54,13 +56,4 @@ form
.panel
:float left
:width 100%
:clear both
a
:color #1ABFF1
:-webkit-transition-property padding
:-webkit-transition-duration 0.15s
a:hover
:padding 0 5px
a:hover:before
:content 'visit: '
:margin-bottom 15px
+8
Ver Arquivo
@@ -3,9 +3,17 @@
.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' }
+1 -1
Ver Arquivo
@@ -300,7 +300,7 @@ Server = Class({
// --- Express
Express = {
version: '0.1.0',
version: '0.2.0',
config: [],
routes: [],
plugins: [],
+4
Ver Arquivo
@@ -69,6 +69,10 @@ exports.disable = function(option) {
exports.run = function() {
configure(Express.environment = process.ENV.EXPRESS_ENV || 'development')
$(Express.plugins).each(function(plugin){
if ('init' in plugin.klass)
plugin.klass.init(plugin.options)
})
Express.server.run.apply(Express.server, arguments)
}
+8 -1
Ver Arquivo
@@ -3,6 +3,8 @@
/**
* Push _plugin_ with _options_ to the plugin stack.
* If _plugin_ has already been pushed, then it's options
* will override any previously set.
*
* @param {Plugin} plugin
* @param {hash} options
@@ -10,7 +12,12 @@
*/
exports.use = function(plugin, options) {
if ('init' in plugin) plugin.init(options)
if (Express.environment === 'test' && 'init' in plugin)
plugin.init(options)
$(Express.plugins).each(function(other, i){
if (other.klass === plugin)
delete Express.plugins[i]
})
Express.plugins.push({
klass: plugin,
options: options
+1
Ver Arquivo
@@ -2,6 +2,7 @@
// 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'))
+73 -14
Ver Arquivo
@@ -1,6 +1,21 @@
// Express - Cache - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
// --- Cache
var Cache = Class({
/**
* Initialize cache with _key_ and _val_.
*/
init: function(key, val) {
this.key = key
this.val = val
this.created = Number(new Date)
}
})
// --- Store
exports.Store = Class({
@@ -71,7 +86,7 @@ exports.Store.Memory = exports.Store.extend({
set: function(key, val) {
this.__super__(key, val)
return this.data[key] = val
return this.data[key] = new Cache(key, val), val
},
/**
@@ -95,14 +110,15 @@ exports.Store.Memory = exports.Store.extend({
get: function(key) {
this.__super__(key)
if (key.indexOf('*') === -1)
return this.data[key]
var vals = {},
regexp = this.normalize(key)
for (var key in this.data)
if (this.data.hasOwnProperty(key))
if (regexp.test(key))
vals[key] = this.data[key]
return vals
return this.data[key] instanceof Cache ?
this.data[key].val :
null
var regexp = this.normalize(key)
return $(this.data).reduce({}, function(vals, cache){
if (regexp.test(cache.key))
vals[cache.key] = cache.val
return vals
})
},
/**
@@ -127,6 +143,22 @@ exports.Store.Memory = exports.Store.extend({
delete this.data[key]
},
/**
* Reap caches older than _ms_.
*
* @param {int} ms
* @api private
*/
reap: function(ms) {
var self = this,
threshold = Number(new Date(Number(new Date) - ms))
$(this.data).each(function(cache){
if (cache.created < threshold)
self.clear(cache.key)
})
},
/**
* Convert the given key matching _pattern_
* into a RegExp.
@@ -143,17 +175,44 @@ exports.Store.Memory = exports.Store.extend({
}
})
// --- Cache
exports.Cache = Plugin.extend({
extend: {
/**
* Initialize extensions.
* Initialize memory store and start reaper.
*
* Options:
*
* - dataStore constructor name of cache data store, defaults to Store.Memory
* - lifetime lifetime of cache in milliseconds, defaults to one day
* - reapInterval, reapEvery interval in milliseconds in which to reap old caches, defaults to one hour
*
* @param {hash} options
* @api private
*/
init: function() {
Request.include({
cache: new exports.Store.Memory
})
init: function(options) {
process.mixin(this, options)
this.store = new (this.dataStore || exports.Store.Memory)(options)
Request.include({ cache: this.store })
this.startReaper()
},
/**
* Start reaper.
*
* @api private
*/
startReaper: function() {
var self = this,
oneDay = 86400000,
oneHour = 3600000
setInterval(function(){
self.store.reap(self.lifetime || oneDay)
}, self.reapInterval || self.reapEvery || oneHour)
}
}
})
+31 -7
Ver Arquivo
@@ -31,9 +31,31 @@ var Session = Class({
}
})
// --- MemoryStore
// --- Store
exports.MemoryStore = Class({
exports.Store = Class({
/**
* Convert to '[NAME Store]'.
*
* @return {string}
* @api public
*/
toString: function() {
return '[' + this.name + ' Store]'
}
})
// --- Store.Memory
exports.Store.Memory = exports.Store.extend({
/**
* Datastore name.
*/
name: 'Memory',
/**
* Initialize in-memory session store.
@@ -116,6 +138,8 @@ exports.MemoryStore = Class({
}
})
// --- Cache
exports.Session = Plugin.extend({
extend: {
@@ -124,9 +148,9 @@ exports.Session = Plugin.extend({
*
* Options:
*
* - dataStore constructor name of session data store, defaults to MemoryStore
* - lifetime lifetime of session in milliseconds, defaults to one day
* - reapInterval interval in milliseconds in which to reap old sessions, defaults to one hour
* - dataStore   constructor name of session data store, defaults to Store.Memory
* - lifetime lifetime of session in milliseconds, defaults to one day
* - reapInterval, reapEvery interval in milliseconds in which to reap old sessions, defaults to one hour
*
* @param {hash} options
* @api private
@@ -134,7 +158,7 @@ exports.Session = Plugin.extend({
init: function(options) {
process.mixin(this, options)
this.store = new (this.dataStore || exports.MemoryStore)(options)
this.store = new (this.dataStore || exports.Store.Memory)(options)
this.startReaper()
},
@@ -150,7 +174,7 @@ exports.Session = Plugin.extend({
oneHour = 3600000
setInterval(function(){
self.store.reap(self.lifetime || oneDay)
}, self.reapInterval || oneHour)
}, self.reapInterval || self.reapEvery || oneHour)
}
},
+23 -12
Ver Arquivo
@@ -1,6 +1,12 @@
// Express - Helpers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
/**
* Module dependencies.
*/
var queryString = require('querystring')
/**
* JSON aliases.
*/
@@ -99,25 +105,30 @@ exports.escapeRegexp = function(string, chars) {
/**
* Merge param _key_ and _val_ into _params_. Key
* should be a query string key such as 'user[name]',
* and _val_ is it's associated value. The root _params_
* and _val_ is it's associated object. The root _params_
* object is returned.
*
* This is primarily used by Express for merging multi-part
* body values together.
*
* @param {string} key
* @param {string} val
* @param {mixed} val
* @return {hash}
* @api public
*/
exports.mergeParam = function(key, val, params) {
var keys = key.match(/(\w+)/g),
orig = params
for (var i = 0, len = keys.length; i < len; ++i)
if (i == len - 1)
params[keys[i]] = val
else
params = (params[keys[i]] = params[keys[i]] || {})
var orig = params,
keys = key.trim().match(/\w+/g),
array = /\[\]$/.test(key)
$(keys).reduce(queryString.parseQuery(key), function(parts, key, i){
if (i === keys.length - 1)
if (key in params)
params[key] instanceof Array ?
params[key].push(val) :
params[key] = [params[key], val]
else
params[key] = array ? [val] : val
if (!(key in params)) params[key] = {}
params = params[key]
return parts[key]
})
return orig
}
+1
Ver Arquivo
@@ -64,4 +64,5 @@ switch (process.ARGV[2]) {
run([process.ARGV[2]])
}
Express.environment = 'test'
JSpec.run({ reporter: JSpec.reporters.Terminal, failuresOnly: true }).report()
+79 -70
Ver Arquivo
@@ -19,92 +19,101 @@ describe 'Express'
end
end
describe 'Store'
describe 'Memory'
before_each
store = new cache.Store.Memory
describe 'cache Store.Memory'
before_each
store = new cache.Store.Memory
end
describe '#toString()'
it 'should return [Memory Store]'
store.toString().should.eql '[Memory Store]'
end
describe '#toString()'
it 'should return [Memory Store]'
store.toString().should.eql '[Memory Store]'
end
end
describe '#set()'
describe 'given a key and value'
it 'should set the cache data'
store.set('foo', 'bar')
store.get('foo').should.eql 'bar'
end
it 'should override existing data'
store.set('foo', 'bar')
store.set('foo', 'baz')
store.get('foo').should.eql 'baz'
end
it 'should return data'
store.set('foo', 'bar').should.eql 'bar'
end
end
describe '#set()'
describe 'given a key and value'
it 'should set the cache data'
store.set('foo', 'bar')
store.get('foo').should.eql 'bar'
end
describe 'given an abitrary key'
it 'should throw an error'
-{ store.set({}, 'foo') }.should.throw_error
end
it 'should override existing data'
store.set('foo', 'bar')
store.set('foo', 'baz')
store.get('foo').should.eql 'baz'
end
describe 'given an abitrary value'
it 'should serialize as JSON'
store.set('user', { name: 'tj' }).should.eql { name: 'tj' }
end
it 'should return data'
store.set('foo', 'bar').should.eql 'bar'
end
end
describe '#get()'
describe 'given a key'
it 'should return cached value'
store.set('foo', 'bar')
store.get('foo').should.eql 'bar'
end
it 'should unserialize JSON data'
store.set('user', { name: 'tj' })
store.get('user').should.eql { name: 'tj' }
end
end
describe 'given wildcards'
it 'should return a set of caches'
store.set('user:1', 'a')
store.set('user:2', 'b')
store.set('foo', 'bar')
store.get('user:*').should.eql { 'user:1': 'a', 'user:2': 'b' }
end
describe 'given an abitrary key'
it 'should throw an error'
-{ store.set({}, 'foo') }.should.throw_error
end
end
describe '#clear()'
describe 'given a key'
it 'should delete previous data'
store.set('foo', 'bar')
store.clear('foo')
store.get('foo').should.be_null
end
describe 'given an abitrary value'
it 'should serialize as JSON'
store.set('user', { name: 'tj' }).should.eql { name: 'tj' }
end
end
end
describe '#get()'
describe 'given a key'
it 'should return cached value'
store.set('foo', 'bar')
store.get('foo').should.eql 'bar'
end
describe 'given wildcards'
it 'should clear a set of caches'
store.set('user:one', '1')
store.set('user:two', '2')
store.clear('user:*')
store.get('user:one').should.be_null
store.get('user:two').should.be_null
end
it 'should unserialize JSON data'
store.set('user', { name: 'tj' })
store.get('user').should.eql { name: 'tj' }
end
end
describe 'given wildcards'
it 'should return a set of caches'
store.set('user:1', 'a')
store.set('user:2', 'b')
store.set('foo', 'bar')
store.get('user:*').should.eql { 'user:1': 'a', 'user:2': 'b' }
end
end
end
describe '#clear()'
describe 'given a key'
it 'should delete previous data'
store.set('foo', 'bar')
store.clear('foo')
store.get('foo').should.be_null
end
end
describe 'given wildcards'
it 'should clear a set of caches'
store.set('user:one', '1')
store.set('user:two', '2')
store.clear('user:*')
store.get('user:one').should.be_null
store.get('user:two').should.be_null
end
end
end
describe '#reap()'
it 'should destroy caches older than the given age in milliseconds'
store.set('user:one', '1')
store.data['user:one'].created = Number(new Date) - 300
store.set('user:two', '2')
store.data['user:two'].created = Number(new Date) - 100
store.reap(200)
store.get('user:one').should.be_null
store.get('user:two').should.not.be_null
end
end
end
end
+8 -2
Ver Arquivo
@@ -22,9 +22,9 @@ describe 'Express'
end
end
describe 'MemoryStore'
describe 'session Store.Memory'
before_each
memory = new (require('express/plugins/session').MemoryStore)
memory = new (require('express/plugins/session').Store.Memory)
end
it 'should persist'
@@ -40,6 +40,12 @@ describe 'Express'
get('/login', headers).body.should.eql 'tj'
end
describe '#toString()'
it 'should return [Memory Store]'
memory.toString().should.eql '[Memory Store]'
end
end
describe '#fetch()'
describe 'when the session does not exist'
it 'should return a new Session'
+66
Ver Arquivo
@@ -68,5 +68,71 @@ describe 'Express'
params.user.email.should.eql 'tj@vision-media.ca'
end
end
describe 'with an object as value'
it 'should preserve it'
params = {}
utils.mergeParam('images[]', { name: 1 }, params)
utils.mergeParam('images[]', { name: 2 }, params)
params.images.should.eql [{ name: 1}, { name: 2 }]
end
end
describe 'key[number]'
it 'should merge correctly'
params = { images: { one: 'foo.png' }}
utils.mergeParam('images[0]', 'bar.png', params)
params.images.one.should.eql 'foo.png'
params.images[0].should.eql 'bar.png'
end
end
describe 'key[]'
describe 'with a single value'
it 'should still be an array'
params = {}
utils.mergeParam('images[]', '1', params)
params.images.should.eql ['1']
end
end
describe 'with empty params'
it 'should merge correctly'
params = {}
utils.mergeParam('images[]', '1', params)
utils.mergeParam('images[]', '2', params)
params.images.should.eql ['1', '2']
end
end
describe 'with populated params'
it 'should convert to an array'
params = { images: 'foo.png'}
utils.mergeParam('images[]', '1', params)
params.images.should.eql ['foo.png', '1']
end
end
describe 'with several merges'
it 'should push values'
params = {}
utils.mergeParam('images[]', '1', params)
utils.mergeParam('images[]', '2', params)
utils.mergeParam('images[]', '3', params)
params.images.should.eql ['1', '2', '3']
end
end
describe 'when nested'
it 'should marge correctly'
params = {}
utils.mergeParam('user[tj][images][]', '1', params)
utils.mergeParam('user[tj][images][]', '2', params)
utils.mergeParam('user[tj][images][]', '3', params)
params.user.should.eql { tj: { images: ['1', '2', '3'] }}
end
end
end
end
end