diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b90444 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +./node_modules diff --git a/.gitmodules b/.gitmodules index e69de29..8d4fef4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/jade-html5-boilerplate"] + path = deps/jade-html5-boilerplate + url = git://github.com/dmotz/jade-html5-boilerplate.git diff --git a/deps/jade-html5-boilerplate b/deps/jade-html5-boilerplate new file mode 160000 index 0000000..e0d69d4 --- /dev/null +++ b/deps/jade-html5-boilerplate @@ -0,0 +1 @@ +Subproject commit e0d69d474b61f2777b8edb0b91c4e44a31a8e31c diff --git a/node_modules/.bin/express b/node_modules/.bin/express new file mode 120000 index 0000000..b741d99 --- /dev/null +++ b/node_modules/.bin/express @@ -0,0 +1 @@ +../express/bin/express \ No newline at end of file diff --git a/node_modules/.bin/jade b/node_modules/.bin/jade new file mode 120000 index 0000000..571fae7 --- /dev/null +++ b/node_modules/.bin/jade @@ -0,0 +1 @@ +../jade/bin/jade \ No newline at end of file diff --git a/node_modules/express/.npmignore b/node_modules/express/.npmignore new file mode 100644 index 0000000..74bd365 --- /dev/null +++ b/node_modules/express/.npmignore @@ -0,0 +1,7 @@ +.git* +docs/ +examples/ +support/ +test/ +testing.js +.DS_Store diff --git a/node_modules/express/History.md b/node_modules/express/History.md new file mode 100644 index 0000000..1ba6588 --- /dev/null +++ b/node_modules/express/History.md @@ -0,0 +1,775 @@ + +2.5.2 / 2011-12-10 +================== + + * Fixed: express(1) LF -> CRLF for windows + +2.5.1 / 2011-11-17 +================== + + * Changed: updated connect to 1.8.x + * Removed sass.js support from express(1) + +2.5.0 / 2011-10-24 +================== + + * Added ./routes dir for generated app by default + * Added npm install reminder to express(1) app gen + * Added 0.5.x support + * Removed `make test-cov` since it wont work with node 0.5.x + * Fixed express(1) public dir for windows. Closes #866 + +2.4.7 / 2011-10-05 +================== + + * Added mkdirp to express(1). Closes #795 + * Added simple _json-config_ example + * Added shorthand for the parsed request's pathname via `req.path` + * Changed connect dep to 1.7.x to fix npm issue... + * Fixed `res.redirect()` __HEAD__ support. [reported by xerox] + * Fixed `req.flash()`, only escape args + * Fixed absolute path checking on windows. Closes #829 [reported by andrewpmckenzie] + +2.4.6 / 2011-08-22 +================== + + * Fixed multiple param callback regression. Closes #824 [reported by TroyGoode] + +2.4.5 / 2011-08-19 +================== + + * Added support for routes to handle errors. Closes #809 + * Added `app.routes.all()`. Closes #803 + * Added "basepath" setting to work in conjunction with reverse proxies etc. + * Refactored `Route` to use a single array of callbacks + * Added support for multiple callbacks for `app.param()`. Closes #801 +Closes #805 + * Changed: removed .call(self) for route callbacks + * Dependency: `qs >= 0.3.1` + * Fixed `res.redirect()` on windows due to `join()` usage. Closes #808 + +2.4.4 / 2011-08-05 +================== + + * Fixed `res.header()` intention of a set, even when `undefined` + * Fixed `*`, value no longer required + * Fixed `res.send(204)` support. Closes #771 + +2.4.3 / 2011-07-14 +================== + + * Added docs for `status` option special-case. Closes #739 + * Fixed `options.filename`, exposing the view path to template engines + +2.4.2. / 2011-07-06 +================== + + * Revert "removed jsonp stripping" for XSS + +2.4.1 / 2011-07-06 +================== + + * Added `res.json()` JSONP support. Closes #737 + * Added _extending-templates_ example. Closes #730 + * Added "strict routing" setting for trailing slashes + * Added support for multiple envs in `app.configure()` calls. Closes #735 + * Changed: `res.send()` using `res.json()` + * Changed: when cookie `path === null` don't default it + * Changed; default cookie path to "home" setting. Closes #731 + * Removed _pids/logs_ creation from express(1) + +2.4.0 / 2011-06-28 +================== + + * Added chainable `res.status(code)` + * Added `res.json()`, an explicit version of `res.send(obj)` + * Added simple web-service example + +2.3.12 / 2011-06-22 +================== + + * \#express is now on freenode! come join! + * Added `req.get(field, param)` + * Added links to Japanese documentation, thanks @hideyukisaito! + * Added; the `express(1)` generated app outputs the env + * Added `content-negotiation` example + * Dependency: connect >= 1.5.1 < 2.0.0 + * Fixed view layout bug. Closes #720 + * Fixed; ignore body on 304. Closes #701 + +2.3.11 / 2011-06-04 +================== + + * Added `npm test` + * Removed generation of dummy test file from `express(1)` + * Fixed; `express(1)` adds express as a dep + * Fixed; prune on `prepublish` + +2.3.10 / 2011-05-27 +================== + + * Added `req.route`, exposing the current route + * Added _package.json_ generation support to `express(1)` + * Fixed call to `app.param()` function for optional params. Closes #682 + +2.3.9 / 2011-05-25 +================== + + * Fixed bug-ish with `../' in `res.partial()` calls + +2.3.8 / 2011-05-24 +================== + + * Fixed `app.options()` + +2.3.7 / 2011-05-23 +================== + + * Added route `Collection`, ex: `app.get('/user/:id').remove();` + * Added support for `app.param(fn)` to define param logic + * Removed `app.param()` support for callback with return value + * Removed module.parent check from express(1) generated app. Closes #670 + * Refactored router. Closes #639 + +2.3.6 / 2011-05-20 +================== + + * Changed; using devDependencies instead of git submodules + * Fixed redis session example + * Fixed markdown example + * Fixed view caching, should not be enabled in development + +2.3.5 / 2011-05-20 +================== + + * Added export `.view` as alias for `.View` + +2.3.4 / 2011-05-08 +================== + + * Added `./examples/say` + * Fixed `res.sendfile()` bug preventing the transfer of files with spaces + +2.3.3 / 2011-05-03 +================== + + * Added "case sensitive routes" option. + * Changed; split methods supported per rfc [slaskis] + * Fixed route-specific middleware when using the same callback function several times + +2.3.2 / 2011-04-27 +================== + + * Fixed view hints + +2.3.1 / 2011-04-26 +================== + + * Added `app.match()` as `app.match.all()` + * Added `app.lookup()` as `app.lookup.all()` + * Added `app.remove()` for `app.remove.all()` + * Added `app.remove.VERB()` + * Fixed template caching collision issue. Closes #644 + * Moved router over from connect and started refactor + +2.3.0 / 2011-04-25 +================== + + * Added options support to `res.clearCookie()` + * Added `res.helpers()` as alias of `res.locals()` + * Added; json defaults to UTF-8 with `res.send()`. Closes #632. [Daniel * Dependency `connect >= 1.4.0` + * Changed; auto set Content-Type in res.attachement [Aaron Heckmann] + * Renamed "cache views" to "view cache". Closes #628 + * Fixed caching of views when using several apps. Closes #637 + * Fixed gotcha invoking `app.param()` callbacks once per route middleware. +Closes #638 + * Fixed partial lookup precedence. Closes #631 +Shaw] + +2.2.2 / 2011-04-12 +================== + + * Added second callback support for `res.download()` connection errors + * Fixed `filename` option passing to template engine + +2.2.1 / 2011-04-04 +================== + + * Added `layout(path)` helper to change the layout within a view. Closes #610 + * Fixed `partial()` collection object support. + Previously only anything with `.length` would work. + When `.length` is present one must still be aware of holes, + however now `{ collection: {foo: 'bar'}}` is valid, exposes + `keyInCollection` and `keysInCollection`. + + * Performance improved with better view caching + * Removed `request` and `response` locals + * Changed; errorHandler page title is now `Express` instead of `Connect` + +2.2.0 / 2011-03-30 +================== + + * Added `app.lookup.VERB()`, ex `app.lookup.put('/user/:id')`. Closes #606 + * Added `app.match.VERB()`, ex `app.match.put('/user/12')`. Closes #606 + * Added `app.VERB(path)` as alias of `app.lookup.VERB()`. + * Dependency `connect >= 1.2.0` + +2.1.1 / 2011-03-29 +================== + + * Added; expose `err.view` object when failing to locate a view + * Fixed `res.partial()` call `next(err)` when no callback is given [reported by aheckmann] + * Fixed; `res.send(undefined)` responds with 204 [aheckmann] + +2.1.0 / 2011-03-24 +================== + + * Added `/_?` partial lookup support. Closes #447 + * Added `request`, `response`, and `app` local variables + * Added `settings` local variable, containing the app's settings + * Added `req.flash()` exception if `req.session` is not available + * Added `res.send(bool)` support (json response) + * Fixed stylus example for latest version + * Fixed; wrap try/catch around `res.render()` + +2.0.0 / 2011-03-17 +================== + + * Fixed up index view path alternative. + * Changed; `res.locals()` without object returns the locals + +2.0.0rc3 / 2011-03-17 +================== + + * Added `res.locals(obj)` to compliment `res.local(key, val)` + * Added `res.partial()` callback support + * Fixed recursive error reporting issue in `res.render()` + +2.0.0rc2 / 2011-03-17 +================== + + * Changed; `partial()` "locals" are now optional + * Fixed `SlowBuffer` support. Closes #584 [reported by tyrda01] + * Fixed .filename view engine option [reported by drudge] + * Fixed blog example + * Fixed `{req,res}.app` reference when mounting [Ben Weaver] + +2.0.0rc / 2011-03-14 +================== + + * Fixed; expose `HTTPSServer` constructor + * Fixed express(1) default test charset. Closes #579 [reported by secoif] + * Fixed; default charset to utf-8 instead of utf8 for lame IE [reported by NickP] + +2.0.0beta3 / 2011-03-09 +================== + + * Added support for `res.contentType()` literal + The original `res.contentType('.json')`, + `res.contentType('application/json')`, and `res.contentType('json')` + will work now. + * Added `res.render()` status option support back + * Added charset option for `res.render()` + * Added `.charset` support (via connect 1.0.4) + * Added view resolution hints when in development and a lookup fails + * Added layout lookup support relative to the page view. + For example while rendering `./views/user/index.jade` if you create + `./views/user/layout.jade` it will be used in favour of the root layout. + * Fixed `res.redirect()`. RFC states absolute url [reported by unlink] + * Fixed; default `res.send()` string charset to utf8 + * Removed `Partial` constructor (not currently used) + +2.0.0beta2 / 2011-03-07 +================== + + * Added res.render() `.locals` support back to aid in migration process + * Fixed flash example + +2.0.0beta / 2011-03-03 +================== + + * Added HTTPS support + * Added `res.cookie()` maxAge support + * Added `req.header()` _Referrer_ / _Referer_ special-case, either works + * Added mount support for `res.redirect()`, now respects the mount-point + * Added `union()` util, taking place of `merge(clone())` combo + * Added stylus support to express(1) generated app + * Added secret to session middleware used in examples and generated app + * Added `res.local(name, val)` for progressive view locals + * Added default param support to `req.param(name, default)` + * Added `app.disabled()` and `app.enabled()` + * Added `app.register()` support for omitting leading ".", either works + * Added `res.partial()`, using the same interface as `partial()` within a view. Closes #539 + * Added `app.param()` to map route params to async/sync logic + * Added; aliased `app.helpers()` as `app.locals()`. Closes #481 + * Added extname with no leading "." support to `res.contentType()` + * Added `cache views` setting, defaulting to enabled in "production" env + * Added index file partial resolution, eg: partial('user') may try _views/user/index.jade_. + * Added `req.accepts()` support for extensions + * Changed; `res.download()` and `res.sendfile()` now utilize Connect's + static file server `connect.static.send()`. + * Changed; replaced `connect.utils.mime()` with npm _mime_ module + * Changed; allow `req.query` to be pre-defined (via middleware or other parent + * Changed view partial resolution, now relative to parent view + * Changed view engine signature. no longer `engine.render(str, options, callback)`, now `engine.compile(str, options) -> Function`, the returned function accepts `fn(locals)`. + * Fixed `req.param()` bug returning Array.prototype methods. Closes #552 + * Fixed; using `Stream#pipe()` instead of `sys.pump()` in `res.sendfile()` + * Fixed; using _qs_ module instead of _querystring_ + * Fixed; strip unsafe chars from jsonp callbacks + * Removed "stream threshold" setting + +1.0.8 / 2011-03-01 +================== + + * Allow `req.query` to be pre-defined (via middleware or other parent app) + * "connect": ">= 0.5.0 < 1.0.0". Closes #547 + * Removed the long deprecated __EXPRESS_ENV__ support + +1.0.7 / 2011-02-07 +================== + + * Fixed `render()` setting inheritance. + Mounted apps would not inherit "view engine" + +1.0.6 / 2011-02-07 +================== + + * Fixed `view engine` setting bug when period is in dirname + +1.0.5 / 2011-02-05 +================== + + * Added secret to generated app `session()` call + +1.0.4 / 2011-02-05 +================== + + * Added `qs` dependency to _package.json_ + * Fixed namespaced `require()`s for latest connect support + +1.0.3 / 2011-01-13 +================== + + * Remove unsafe characters from JSONP callback names [Ryan Grove] + +1.0.2 / 2011-01-10 +================== + + * Removed nested require, using `connect.router` + +1.0.1 / 2010-12-29 +================== + + * Fixed for middleware stacked via `createServer()` + previously the `foo` middleware passed to `createServer(foo)` + would not have access to Express methods such as `res.send()` + or props like `req.query` etc. + +1.0.0 / 2010-11-16 +================== + + * Added; deduce partial object names from the last segment. + For example by default `partial('forum/post', postObject)` will + give you the _post_ object, providing a meaningful default. + * Added http status code string representation to `res.redirect()` body + * Added; `res.redirect()` supporting _text/plain_ and _text/html_ via __Accept__. + * Added `req.is()` to aid in content negotiation + * Added partial local inheritance [suggested by masylum]. Closes #102 + providing access to parent template locals. + * Added _-s, --session[s]_ flag to express(1) to add session related middleware + * Added _--template_ flag to express(1) to specify the + template engine to use. + * Added _--css_ flag to express(1) to specify the + stylesheet engine to use (or just plain css by default). + * Added `app.all()` support [thanks aheckmann] + * Added partial direct object support. + You may now `partial('user', user)` providing the "user" local, + vs previously `partial('user', { object: user })`. + * Added _route-separation_ example since many people question ways + to do this with CommonJS modules. Also view the _blog_ example for + an alternative. + * Performance; caching view path derived partial object names + * Fixed partial local inheritance precedence. [reported by Nick Poulden] Closes #454 + * Fixed jsonp support; _text/javascript_ as per mailinglist discussion + +1.0.0rc4 / 2010-10-14 +================== + + * Added _NODE_ENV_ support, _EXPRESS_ENV_ is deprecated and will be removed in 1.0.0 + * Added route-middleware support (very helpful, see the [docs](http://expressjs.com/guide.html#Route-Middleware)) + * Added _jsonp callback_ setting to enable/disable jsonp autowrapping [Dav Glass] + * Added callback query check on response.send to autowrap JSON objects for simple webservice implementations [Dav Glass] + * Added `partial()` support for array-like collections. Closes #434 + * Added support for swappable querystring parsers + * Added session usage docs. Closes #443 + * Added dynamic helper caching. Closes #439 [suggested by maritz] + * Added authentication example + * Added basic Range support to `res.sendfile()` (and `res.download()` etc) + * Changed; `express(1)` generated app using 2 spaces instead of 4 + * Default env to "development" again [aheckmann] + * Removed _context_ option is no more, use "scope" + * Fixed; exposing _./support_ libs to examples so they can run without installs + * Fixed mvc example + +1.0.0rc3 / 2010-09-20 +================== + + * Added confirmation for `express(1)` app generation. Closes #391 + * Added extending of flash formatters via `app.flashFormatters` + * Added flash formatter support. Closes #411 + * Added streaming support to `res.sendfile()` using `sys.pump()` when >= "stream threshold" + * Added _stream threshold_ setting for `res.sendfile()` + * Added `res.send()` __HEAD__ support + * Added `res.clearCookie()` + * Added `res.cookie()` + * Added `res.render()` headers option + * Added `res.redirect()` response bodies + * Added `res.render()` status option support. Closes #425 [thanks aheckmann] + * Fixed `res.sendfile()` responding with 403 on malicious path + * Fixed `res.download()` bug; when an error occurs remove _Content-Disposition_ + * Fixed; mounted apps settings now inherit from parent app [aheckmann] + * Fixed; stripping Content-Length / Content-Type when 204 + * Fixed `res.send()` 204. Closes #419 + * Fixed multiple _Set-Cookie_ headers via `res.header()`. Closes #402 + * Fixed bug messing with error handlers when `listenFD()` is called instead of `listen()`. [thanks guillermo] + + +1.0.0rc2 / 2010-08-17 +================== + + * Added `app.register()` for template engine mapping. Closes #390 + * Added `res.render()` callback support as second argument (no options) + * Added callback support to `res.download()` + * Added callback support for `res.sendfile()` + * Added support for middleware access via `express.middlewareName()` vs `connect.middlewareName()` + * Added "partials" setting to docs + * Added default expresso tests to `express(1)` generated app. Closes #384 + * Fixed `res.sendfile()` error handling, defer via `next()` + * Fixed `res.render()` callback when a layout is used [thanks guillermo] + * Fixed; `make install` creating ~/.node_libraries when not present + * Fixed issue preventing error handlers from being defined anywhere. Closes #387 + +1.0.0rc / 2010-07-28 +================== + + * Added mounted hook. Closes #369 + * Added connect dependency to _package.json_ + + * Removed "reload views" setting and support code + development env never caches, production always caches. + + * Removed _param_ in route callbacks, signature is now + simply (req, res, next), previously (req, res, params, next). + Use _req.params_ for path captures, _req.query_ for GET params. + + * Fixed "home" setting + * Fixed middleware/router precedence issue. Closes #366 + * Fixed; _configure()_ callbacks called immediately. Closes #368 + +1.0.0beta2 / 2010-07-23 +================== + + * Added more examples + * Added; exporting `Server` constructor + * Added `Server#helpers()` for view locals + * Added `Server#dynamicHelpers()` for dynamic view locals. Closes #349 + * Added support for absolute view paths + * Added; _home_ setting defaults to `Server#route` for mounted apps. Closes #363 + * Added Guillermo Rauch to the contributor list + * Added support for "as" for non-collection partials. Closes #341 + * Fixed _install.sh_, ensuring _~/.node_libraries_ exists. Closes #362 [thanks jf] + * Fixed `res.render()` exceptions, now passed to `next()` when no callback is given [thanks guillermo] + * Fixed instanceof `Array` checks, now `Array.isArray()` + * Fixed express(1) expansion of public dirs. Closes #348 + * Fixed middleware precedence. Closes #345 + * Fixed view watcher, now async [thanks aheckmann] + +1.0.0beta / 2010-07-15 +================== + + * Re-write + - much faster + - much lighter + - Check [ExpressJS.com](http://expressjs.com) for migration guide and updated docs + +0.14.0 / 2010-06-15 +================== + + * Utilize relative requires + * Added Static bufferSize option [aheckmann] + * Fixed caching of view and partial subdirectories [aheckmann] + * Fixed mime.type() comments now that ".ext" is not supported + * Updated haml submodule + * Updated class submodule + * Removed bin/express + +0.13.0 / 2010-06-01 +================== + + * Added node v0.1.97 compatibility + * Added support for deleting cookies via Request#cookie('key', null) + * Updated haml submodule + * Fixed not-found page, now using using charset utf-8 + * Fixed show-exceptions page, now using using charset utf-8 + * Fixed view support due to fs.readFile Buffers + * Changed; mime.type() no longer accepts ".type" due to node extname() changes + +0.12.0 / 2010-05-22 +================== + + * Added node v0.1.96 compatibility + * Added view `helpers` export which act as additional local variables + * Updated haml submodule + * Changed ETag; removed inode, modified time only + * Fixed LF to CRLF for setting multiple cookies + * Fixed cookie complation; values are now urlencoded + * Fixed cookies parsing; accepts quoted values and url escaped cookies + +0.11.0 / 2010-05-06 +================== + + * Added support for layouts using different engines + - this.render('page.html.haml', { layout: 'super-cool-layout.html.ejs' }) + - this.render('page.html.haml', { layout: 'foo' }) // assumes 'foo.html.haml' + - this.render('page.html.haml', { layout: false }) // no layout + * Updated ext submodule + * Updated haml submodule + * Fixed EJS partial support by passing along the context. Issue #307 + +0.10.1 / 2010-05-03 +================== + + * Fixed binary uploads. + +0.10.0 / 2010-04-30 +================== + + * Added charset support via Request#charset (automatically assigned to 'UTF-8' when respond()'s + encoding is set to 'utf8' or 'utf-8'. + * Added "encoding" option to Request#render(). Closes #299 + * Added "dump exceptions" setting, which is enabled by default. + * Added simple ejs template engine support + * Added error reponse support for text/plain, application/json. Closes #297 + * Added callback function param to Request#error() + * Added Request#sendHead() + * Added Request#stream() + * Added support for Request#respond(304, null) for empty response bodies + * Added ETag support to Request#sendfile() + * Added options to Request#sendfile(), passed to fs.createReadStream() + * Added filename arg to Request#download() + * Performance enhanced due to pre-reversing plugins so that plugins.reverse() is not called on each request + * Performance enhanced by preventing several calls to toLowerCase() in Router#match() + * Changed; Request#sendfile() now streams + * Changed; Renamed Request#halt() to Request#respond(). Closes #289 + * Changed; Using sys.inspect() instead of JSON.encode() for error output + * Changed; run() returns the http.Server instance. Closes #298 + * Changed; Defaulting Server#host to null (INADDR_ANY) + * Changed; Logger "common" format scale of 0.4f + * Removed Logger "request" format + * Fixed; Catching ENOENT in view caching, preventing error when "views/partials" is not found + * Fixed several issues with http client + * Fixed Logger Content-Length output + * Fixed bug preventing Opera from retaining the generated session id. Closes #292 + +0.9.0 / 2010-04-14 +================== + + * Added DSL level error() route support + * Added DSL level notFound() route support + * Added Request#error() + * Added Request#notFound() + * Added Request#render() callback function. Closes #258 + * Added "max upload size" setting + * Added "magic" variables to collection partials (\_\_index\_\_, \_\_length\_\_, \_\_isFirst\_\_, \_\_isLast\_\_). Closes #254 + * Added [haml.js](http://github.com/visionmedia/haml.js) submodule; removed haml-js + * Added callback function support to Request#halt() as 3rd/4th arg + * Added preprocessing of route param wildcards using param(). Closes #251 + * Added view partial support (with collections etc) + * Fixed bug preventing falsey params (such as ?page=0). Closes #286 + * Fixed setting of multiple cookies. Closes #199 + * Changed; view naming convention is now NAME.TYPE.ENGINE (for example page.html.haml) + * Changed; session cookie is now httpOnly + * Changed; Request is no longer global + * Changed; Event is no longer global + * Changed; "sys" module is no longer global + * Changed; moved Request#download to Static plugin where it belongs + * Changed; Request instance created before body parsing. Closes #262 + * Changed; Pre-caching views in memory when "cache view contents" is enabled. Closes #253 + * Changed; Pre-caching view partials in memory when "cache view partials" is enabled + * Updated support to node --version 0.1.90 + * Updated dependencies + * Removed set("session cookie") in favour of use(Session, { cookie: { ... }}) + * Removed utils.mixin(); use Object#mergeDeep() + +0.8.0 / 2010-03-19 +================== + + * Added coffeescript example app. Closes #242 + * Changed; cache api now async friendly. Closes #240 + * Removed deprecated 'express/static' support. Use 'express/plugins/static' + +0.7.6 / 2010-03-19 +================== + + * Added Request#isXHR. Closes #229 + * Added `make install` (for the executable) + * Added `express` executable for setting up simple app templates + * Added "GET /public/*" to Static plugin, defaulting to /public + * Added Static plugin + * Fixed; Request#render() only calls cache.get() once + * Fixed; Namespacing View caches with "view:" + * Fixed; Namespacing Static caches with "static:" + * Fixed; Both example apps now use the Static plugin + * Fixed set("views"). Closes #239 + * Fixed missing space for combined log format + * Deprecated Request#sendfile() and 'express/static' + * Removed Server#running + +0.7.5 / 2010-03-16 +================== + + * Added Request#flash() support without args, now returns all flashes + * Updated ext submodule + +0.7.4 / 2010-03-16 +================== + + * Fixed session reaper + * Changed; class.js replacing js-oo Class implementation (quite a bit faster, no browser cruft) + +0.7.3 / 2010-03-16 +================== + + * Added package.json + * Fixed requiring of haml / sass due to kiwi removal + +0.7.2 / 2010-03-16 +================== + + * Fixed GIT submodules (HAH!) + +0.7.1 / 2010-03-16 +================== + + * Changed; Express now using submodules again until a PM is adopted + * Changed; chat example using millisecond conversions from ext + +0.7.0 / 2010-03-15 +================== + + * Added Request#pass() support (finds the next matching route, or the given path) + * Added Logger plugin (default "common" format replaces CommonLogger) + * Removed Profiler plugin + * Removed CommonLogger plugin + +0.6.0 / 2010-03-11 +================== + + * Added seed.yml for kiwi package management support + * Added HTTP client query string support when method is GET. Closes #205 + + * Added support for arbitrary view engines. + For example "foo.engine.html" will now require('engine'), + the exports from this module are cached after the first require(). + + * Added async plugin support + + * Removed usage of RESTful route funcs as http client + get() etc, use http.get() and friends + + * Removed custom exceptions + +0.5.0 / 2010-03-10 +================== + + * Added ext dependency (library of js extensions) + * Removed extname() / basename() utils. Use path module + * Removed toArray() util. Use arguments.values + * Removed escapeRegexp() util. Use RegExp.escape() + * Removed process.mixin() dependency. Use utils.mixin() + * Removed Collection + * Removed ElementCollection + * Shameless self promotion of ebook "Advanced JavaScript" (http://dev-mag.com) ;) + +0.4.0 / 2010-02-11 +================== + + * Added flash() example to sample upload app + * Added high level restful http client module (express/http) + * Changed; RESTful route functions double as HTTP clients. Closes #69 + * Changed; throwing error when routes are added at runtime + * Changed; defaulting render() context to the current Request. Closes #197 + * Updated haml submodule + +0.3.0 / 2010-02-11 +================== + + * Updated haml / sass submodules. Closes #200 + * Added flash message support. Closes #64 + * Added accepts() now allows multiple args. fixes #117 + * Added support for plugins to halt. Closes #189 + * Added alternate layout support. Closes #119 + * Removed Route#run(). Closes #188 + * Fixed broken specs due to use(Cookie) missing + +0.2.1 / 2010-02-05 +================== + + * Added "plot" format option for Profiler (for gnuplot processing) + * Added request number to Profiler plugin + * Fixed binary encoding for multi-part file uploads, was previously defaulting to UTF8 + * Fixed issue with routes not firing when not files are present. Closes #184 + * Fixed process.Promise -> events.Promise + +0.2.0 / 2010-02-03 +================== + + * Added parseParam() support for name[] etc. (allows for file inputs with "multiple" attr) Closes #180 + * Added Both Cache and Session option "reapInterval" may be "reapEvery". Closes #174 + * Added expiration support to cache api with reaper. Closes #133 + * Added cache Store.Memory#reap() + * Added Cache; cache api now uses first class Cache instances + * Added abstract session Store. Closes #172 + * Changed; cache Memory.Store#get() utilizing Collection + * Renamed MemoryStore -> Store.Memory + * Fixed use() of the same plugin several time will always use latest options. Closes #176 + +0.1.0 / 2010-02-03 +================== + + * Changed; Hooks (before / after) pass request as arg as well as evaluated in their context + * Updated node support to 0.1.27 Closes #169 + * Updated dirname(__filename) -> __dirname + * Updated libxmljs support to v0.2.0 + * Added session support with memory store / reaping + * Added quick uid() helper + * Added multi-part upload support + * Added Sass.js support / submodule + * Added production env caching view contents and static files + * Added static file caching. Closes #136 + * Added cache plugin with memory stores + * Added support to StaticFile so that it works with non-textual files. + * Removed dirname() helper + * Removed several globals (now their modules must be required) + +0.0.2 / 2010-01-10 +================== + + * Added view benchmarks; currently haml vs ejs + * Added Request#attachment() specs. Closes #116 + * Added use of node's parseQuery() util. Closes #123 + * Added `make init` for submodules + * Updated Haml + * Updated sample chat app to show messages on load + * Updated libxmljs parseString -> parseHtmlString + * Fixed `make init` to work with older versions of git + * Fixed specs can now run independant specs for those who cant build deps. Closes #127 + * Fixed issues introduced by the node url module changes. Closes 126. + * Fixed two assertions failing due to Collection#keys() returning strings + * Fixed faulty Collection#toArray() spec due to keys() returning strings + * Fixed `make test` now builds libxmljs.node before testing + +0.0.1 / 2010-01-03 +================== + + * Initial release diff --git a/node_modules/express/LICENSE b/node_modules/express/LICENSE new file mode 100644 index 0000000..36075a3 --- /dev/null +++ b/node_modules/express/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2009-2011 TJ Holowaychuk + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/express/Makefile b/node_modules/express/Makefile new file mode 100644 index 0000000..dfbfd67 --- /dev/null +++ b/node_modules/express/Makefile @@ -0,0 +1,29 @@ + +DOCS = $(shell find docs/*.md) +HTMLDOCS = $(DOCS:.md=.html) +TESTS = $(shell find test/*.test.js) + +test: + @NODE_ENV=test ./node_modules/.bin/expresso $(TESTS) + +docs: $(HTMLDOCS) + @ echo "... generating TOC" + @./support/toc.js docs/guide.html + +%.html: %.md + @echo "... $< -> $@" + @markdown $< \ + | cat docs/layout/head.html - docs/layout/foot.html \ + > $@ + +site: + rm -fr /tmp/docs \ + && cp -fr docs /tmp/docs \ + && git checkout gh-pages \ + && cp -fr /tmp/docs/* . \ + && echo "done" + +docclean: + rm -f docs/*.{1,html} + +.PHONY: site test docs docclean \ No newline at end of file diff --git a/node_modules/express/Readme.md b/node_modules/express/Readme.md new file mode 100644 index 0000000..d2c64c7 --- /dev/null +++ b/node_modules/express/Readme.md @@ -0,0 +1,145 @@ + +# Express + + Insanely fast (and small) server-side JavaScript web development framework + built on [node](http://nodejs.org) and [Connect](http://github.com/senchalabs/connect). + + var app = express.createServer(); + + app.get('/', function(req, res){ + res.send('Hello World'); + }); + + app.listen(3000); + +## Installation + + $ npm install express + +or to access the `express(1)` executable install globally: + + $ npm install -g express + +## Quick Start + + The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below: + + Create the app: + + $ npm install -g express + $ express /tmp/foo && cd /tmp/foo + + Install dependencies: + + $ npm install -d + + Start the server: + + $ node app.js + +## Features + + * Robust routing + * Redirection helpers + * Dynamic view helpers + * Content negotiation + * Focus on high performance + * View rendering and partials support + * Environment based configuration + * Session based flash notifications + * Built on [Connect](http://github.com/senchalabs/connect) + * High test coverage + * Executable for generating applications quickly + * Application level view options + +Via Connect: + + * Session support + * Cache API + * Mime helpers + * ETag support + * Persistent flash notifications + * Cookie support + * JSON-RPC + * Logging + * and _much_ more! + +## Contributors + +The following are the major contributors of Express (in no specific order). + + * TJ Holowaychuk ([visionmedia](http://github.com/visionmedia)) + * Ciaran Jessup ([ciaranj](http://github.com/ciaranj)) + * Aaron Heckmann ([aheckmann](http://github.com/aheckmann)) + * Guillermo Rauch ([guille](http://github.com/guille)) + +## More Information + + * #express on freenode + * [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js with ease + * [express-configure](http://github.com/visionmedia/express-configuration) async configuration support + * [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper + * [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support + * [express-params](https://github.com/visionmedia/express-params) param pre-condition functions + * [express-mongoose](https://github.com/LearnBoost/express-mongoose) plugin for easy rendering of Mongoose async Query results + * Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates + * [Google Group](http://groups.google.com/group/express-js) for discussion + * Visit the [Wiki](http://github.com/visionmedia/express/wiki) + * [日本語ドキュメンテーション](http://hideyukisaito.com/doc/expressjs/) by [hideyukisaito](https://github.com/hideyukisaito) + * Screencast - [Introduction](http://bit.ly/eRYu0O) + * Screencast - [View Partials](http://bit.ly/dU13Fx) + * Screencast - [Route Specific Middleware](http://bit.ly/hX4IaH) + * Screencast - [Route Path Placeholder Preconditions](http://bit.ly/eNqmVs) + +## Node Compatibility + +Express 1.x is compatible with node 0.2.x and connect < 1.0. + +Express 2.x is compatible with node 0.4.x or 0.6.x, and connect 1.x + +Express 3.x (master) will be compatible with node 0.6.x and connect 2.x + +## Viewing Examples + +First install the dev dependencies to install all the example / test suite deps: + + $ npm install + +then run whichever tests you want: + + $ node examples/jade/app.js + +## Running Tests + +To run the test suite first invoke the following command within the repo, installing the development dependencies: + + $ npm install + +then run the tests: + + $ make test + +## License + +(The MIT License) + +Copyright (c) 2009-2011 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/express/bin/express b/node_modules/express/bin/express new file mode 100755 index 0000000..5e763f1 --- /dev/null +++ b/node_modules/express/bin/express @@ -0,0 +1,407 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var fs = require('fs') + , exec = require('child_process').exec + , mkdirp = require('mkdirp'); + +/** + * Framework version. + */ + +var version = '2.5.2'; + +/** + * Add session support. + */ + +var sessions = false; + +/** + * CSS engine to utilize. + */ + +var cssEngine; + +/** + * Template engine to utilize. + */ + +var templateEngine = 'jade'; + +/** + * Usage documentation. + */ + +var usage = '' + + '\n' + + ' Usage: express [options] [path]\n' + + '\n' + + ' Options:\n' + + ' -s, --sessions add session support\n' + + ' -t, --template add template support (jade|ejs). default=jade\n' + + ' -c, --css add stylesheet support (stylus). default=plain css\n' + + ' -v, --version output framework version\n' + + ' -h, --help output help information\n' + ; + +/** + * Routes index template. + */ + +var index = [ + '' + , '/*' + , ' * GET home page.' + , ' */' + , '' + , 'exports.index = function(req, res){' + , ' res.render(\'index\', { title: \'Express\' })' + , '};' +].join('\r\n'); + +/** + * Jade layout template. + */ + +var jadeLayout = [ + '!!!' + , 'html' + , ' head' + , ' title= title' + , ' link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')' + , ' body!= body' +].join('\r\n'); + +/** + * Jade index template. + */ + +var jadeIndex = [ + 'h1= title' + , 'p Welcome to #{title}' +].join('\r\n'); + +/** + * EJS layout template. + */ + +var ejsLayout = [ + '' + , '' + , ' ' + , ' <%= title %>' + , ' ' + , ' ' + , ' ' + , ' <%- body %>' + , ' ' + , '' +].join('\r\n'); + +/** + * EJS index template. + */ + +var ejsIndex = [ + '

<%= title %>

' + , '

Welcome to <%= title %>

' + ].join('\r\n'); + +/** + * Default css template. + */ + +var css = [ + 'body {' + , ' padding: 50px;' + , ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;' + , '}' + , '' + , 'a {' + , ' color: #00B7FF;' + , '}' +].join('\r\n'); + +/** + * Default stylus template. + */ + +var stylus = [ + 'body' + , ' padding: 50px' + , ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif' + , 'a' + , ' color: #00B7FF' +].join('\r\n'); + +/** + * App template. + */ + +var app = [ + '' + , '/**' + , ' * Module dependencies.' + , ' */' + , '' + , 'var express = require(\'express\')' + , ' , routes = require(\'./routes\')' + , '' + , 'var app = module.exports = express.createServer();' + , '' + , '// Configuration' + , '' + , 'app.configure(function(){' + , ' app.set(\'views\', __dirname + \'/views\');' + , ' app.set(\'view engine\', \':TEMPLATE\');' + , ' app.use(express.bodyParser());' + , ' app.use(express.methodOverride());{sess}{css}' + , ' app.use(app.router);' + , ' app.use(express.static(__dirname + \'/public\'));' + , '});' + , '' + , 'app.configure(\'development\', function(){' + , ' app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); ' + , '});' + , '' + , 'app.configure(\'production\', function(){' + , ' app.use(express.errorHandler()); ' + , '});' + , '' + , '// Routes' + , '' + , 'app.get(\'/\', routes.index);' + , '' + , 'app.listen(3000);' + , 'console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);' + , '' +].join('\r\n'); + +// Parse arguments + +var args = process.argv.slice(2) + , path = '.'; + +while (args.length) { + var arg = args.shift(); + switch (arg) { + case '-h': + case '--help': + abort(usage); + break; + case '-v': + case '--version': + abort(version); + break; + case '-s': + case '--session': + case '--sessions': + sessions = true; + break; + case '-c': + case '--css': + args.length + ? (cssEngine = args.shift()) + : abort('--css requires an argument'); + break; + case '-t': + case '--template': + args.length + ? (templateEngine = args.shift()) + : abort('--template requires an argument'); + break; + default: + path = arg; + } +} + +// Generate application + +(function createApplication(path) { + emptyDirectory(path, function(empty){ + if (empty) { + createApplicationAt(path); + } else { + confirm('destination is not empty, continue? ', function(ok){ + if (ok) { + process.stdin.destroy(); + createApplicationAt(path); + } else { + abort('aborting'); + } + }); + } + }); +})(path); + +/** + * Create application at the given directory `path`. + * + * @param {String} path + */ + +function createApplicationAt(path) { + console.log(); + process.on('exit', function(){ + console.log(); + console.log(' dont forget to install dependencies:'); + console.log(' $ cd %s && npm install', path); + console.log(); + }); + + mkdir(path, function(){ + mkdir(path + '/public'); + mkdir(path + '/public/javascripts'); + mkdir(path + '/public/images'); + mkdir(path + '/public/stylesheets', function(){ + switch (cssEngine) { + case 'stylus': + write(path + '/public/stylesheets/style.styl', stylus); + break; + default: + write(path + '/public/stylesheets/style.css', css); + } + }); + + mkdir(path + '/routes', function(){ + write(path + '/routes/index.js', index); + }); + + mkdir(path + '/views', function(){ + switch (templateEngine) { + case 'ejs': + write(path + '/views/layout.ejs', ejsLayout); + write(path + '/views/index.ejs', ejsIndex); + break; + case 'jade': + write(path + '/views/layout.jade', jadeLayout); + write(path + '/views/index.jade', jadeIndex); + break; + } + }); + + // CSS Engine support + switch (cssEngine) { + case 'stylus': + app = app.replace('{css}', '\r\n app.use(require(\'stylus\').middleware({ src: __dirname + \'/public\' }));'); + break; + default: + app = app.replace('{css}', ''); + } + + // Session support + app = app.replace('{sess}', sessions + ? '\r\n app.use(express.cookieParser());\r\n app.use(express.session({ secret: \'your secret here\' }));' + : ''); + + // Template support + app = app.replace(':TEMPLATE', templateEngine); + + // package.json + var json = '{\r\n'; + json += ' "name": "application-name"\r\n'; + json += ' , "version": "0.0.1"\r\n'; + json += ' , "private": true\r\n'; + json += ' , "dependencies": {\r\n'; + json += ' "express": "' + version + '"\r\n'; + if (cssEngine) json += ' , "' + cssEngine + '": ">= 0.0.1"\r\n'; + if (templateEngine) json += ' , "' + templateEngine + '": ">= 0.0.1"\r\n'; + json += ' }\r\n'; + json += '}'; + + + write(path + '/package.json', json); + write(path + '/app.js', app); + }); +} + +/** + * Check if the given directory `path` is empty. + * + * @param {String} path + * @param {Function} fn + */ + +function emptyDirectory(path, fn) { + fs.readdir(path, function(err, files){ + if (err && 'ENOENT' != err.code) throw err; + fn(!files || !files.length); + }); +} + +/** + * echo str > path. + * + * @param {String} path + * @param {String} str + */ + +function write(path, str) { + fs.writeFile(path, str); + console.log(' \x1b[36mcreate\x1b[0m : ' + path); +} + +/** + * Prompt confirmation with the given `msg`. + * + * @param {String} msg + * @param {Function} fn + */ + +function confirm(msg, fn) { + prompt(msg, function(val){ + fn(/^ *y(es)?/i.test(val)); + }); +} + +/** + * Prompt input with the given `msg` and callback `fn`. + * + * @param {String} msg + * @param {Function} fn + */ + +function prompt(msg, fn) { + // prompt + if (' ' == msg[msg.length - 1]) { + process.stdout.write(msg); + } else { + console.log(msg); + } + + // stdin + process.stdin.setEncoding('ascii'); + process.stdin.once('data', function(data){ + fn(data); + }).resume(); +} + +/** + * Mkdir -p. + * + * @param {String} path + * @param {Function} fn + */ + +function mkdir(path, fn) { + mkdirp(path, 0755, function(err){ + if (err) throw err; + console.log(' \033[36mcreate\033[0m : ' + path); + fn && fn(); + }); +} + +/** + * Exit with the given `str`. + * + * @param {String} str + */ + +function abort(str) { + console.error(str); + process.exit(1); +} diff --git a/node_modules/express/index.js b/node_modules/express/index.js new file mode 100644 index 0000000..8d81ea7 --- /dev/null +++ b/node_modules/express/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/express'); \ No newline at end of file diff --git a/node_modules/express/lib/express.js b/node_modules/express/lib/express.js new file mode 100644 index 0000000..9e51826 --- /dev/null +++ b/node_modules/express/lib/express.js @@ -0,0 +1,79 @@ + +/*! + * Express + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var connect = require('connect') + , HTTPSServer = require('./https') + , HTTPServer = require('./http') + , Route = require('./router/route') + +/** + * Re-export connect auto-loaders. + * + * This prevents the need to `require('connect')` in order + * to access core middleware, so for example `express.logger()` instead + * of `require('connect').logger()`. + */ + +var exports = module.exports = connect.middleware; + +/** + * Framework version. + */ + +exports.version = '2.5.2'; + +/** + * Shortcut for `new Server(...)`. + * + * @param {Function} ... + * @return {Server} + * @api public + */ + +exports.createServer = function(options){ + if ('object' == typeof options) { + return new HTTPSServer(options, Array.prototype.slice.call(arguments, 1)); + } else { + return new HTTPServer(Array.prototype.slice.call(arguments)); + } +}; + +/** + * Expose constructors. + */ + +exports.HTTPServer = HTTPServer; +exports.HTTPSServer = HTTPSServer; +exports.Route = Route; + +/** + * View extensions. + */ + +exports.View = +exports.view = require('./view'); + +/** + * Response extensions. + */ + +require('./response'); + +/** + * Request extensions. + */ + +require('./request'); + +// Error handler title + +exports.errorHandler.title = 'Express'; + diff --git a/node_modules/express/lib/http.js b/node_modules/express/lib/http.js new file mode 100644 index 0000000..4256dc4 --- /dev/null +++ b/node_modules/express/lib/http.js @@ -0,0 +1,583 @@ + +/*! + * Express - HTTPServer + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var qs = require('qs') + , connect = require('connect') + , router = require('./router') + , Router = require('./router') + , view = require('./view') + , toArray = require('./utils').toArray + , methods = router.methods.concat('del', 'all') + , url = require('url') + , utils = connect.utils; + +/** + * Expose `HTTPServer`. + */ + +exports = module.exports = HTTPServer; + +/** + * Server proto. + */ + +var app = HTTPServer.prototype; + +/** + * Initialize a new `HTTPServer` with optional `middleware`. + * + * @param {Array} middleware + * @api public + */ + +function HTTPServer(middleware){ + connect.HTTPServer.call(this, []); + this.init(middleware); +}; + +/** + * Inherit from `connect.HTTPServer`. + */ + +app.__proto__ = connect.HTTPServer.prototype; + +/** + * Initialize the server. + * + * @param {Array} middleware + * @api private + */ + +app.init = function(middleware){ + var self = this; + this.cache = {}; + this.settings = {}; + this.redirects = {}; + this.isCallbacks = {}; + this._locals = {}; + this.dynamicViewHelpers = {}; + this.errorHandlers = []; + + this.set('env', process.env.NODE_ENV || 'development'); + + // expose objects to each other + this.use(function(req, res, next){ + req.query = req.query || {}; + res.setHeader('X-Powered-By', 'Express'); + req.app = res.app = self; + req.res = res; + res.req = req; + req.next = next; + // assign req.query + if (req.url.indexOf('?') > 0) { + var query = url.parse(req.url).query; + req.query = qs.parse(query); + } + next(); + }); + + // apply middleware + if (middleware) middleware.forEach(self.use.bind(self)); + + // router + this.routes = new Router(this); + this.__defineGetter__('router', function(){ + this.__usedRouter = true; + return self.routes.middleware; + }); + + // default locals + this.locals({ + settings: this.settings + , app: this + }); + + // default development configuration + this.configure('development', function(){ + this.enable('hints'); + }); + + // default production configuration + this.configure('production', function(){ + this.enable('view cache'); + }); + + // register error handlers on "listening" + // so that they disregard definition position. + this.on('listening', this.registerErrorHandlers.bind(this)); + + // route manipulation methods + methods.forEach(function(method){ + self.lookup[method] = function(path){ + return self.routes.lookup(method, path); + }; + + self.match[method] = function(path){ + return self.routes.match(method, path); + }; + + self.remove[method] = function(path){ + return self.routes.lookup(method, path).remove(); + }; + }); + + // del -> delete + self.lookup.del = self.lookup.delete; + self.match.del = self.match.delete; + self.remove.del = self.remove.delete; +}; + +/** + * Remove routes matching the given `path`. + * + * @param {Route} path + * @return {Boolean} + * @api public + */ + +app.remove = function(path){ + return this.routes.lookup('all', path).remove(); +}; + +/** + * Lookup routes defined with a path + * equivalent to `path`. + * + * @param {Stirng} path + * @return {Array} + * @api public + */ + +app.lookup = function(path){ + return this.routes.lookup('all', path); +}; + +/** + * Lookup routes matching the given `url`. + * + * @param {Stirng} url + * @return {Array} + * @api public + */ + +app.match = function(url){ + return this.routes.match('all', url); +}; + +/** + * When using the vhost() middleware register error handlers. + */ + +app.onvhost = function(){ + this.registerErrorHandlers(); +}; + +/** + * Register error handlers. + * + * @return {Server} for chaining + * @api public + */ + +app.registerErrorHandlers = function(){ + this.errorHandlers.forEach(function(fn){ + this.use(function(err, req, res, next){ + fn.apply(this, arguments); + }); + }, this); + return this; +}; + +/** + * Proxy `connect.HTTPServer#use()` to apply settings to + * mounted applications. + * + * @param {String|Function|Server} route + * @param {Function|Server} middleware + * @return {Server} for chaining + * @api public + */ + +app.use = function(route, middleware){ + var app, base, handle; + + if ('string' != typeof route) { + middleware = route, route = '/'; + } + + // express app + if (middleware.handle && middleware.set) app = middleware; + + // restore .app property on req and res + if (app) { + app.route = route; + middleware = function(req, res, next) { + var orig = req.app; + app.handle(req, res, function(err){ + req.app = res.app = orig; + next(err); + }); + }; + } + + connect.HTTPServer.prototype.use.call(this, route, middleware); + + // mounted an app, invoke the hook + // and adjust some settings + if (app) { + base = this.set('basepath') || this.route; + if ('/' == base) base = ''; + base = base + (app.set('basepath') || app.route); + app.set('basepath', base); + app.parent = this; + if (app.__mounted) app.__mounted.call(app, this); + } + + return this; +}; + +/** + * Assign a callback `fn` which is called + * when this `Server` is passed to `Server#use()`. + * + * Examples: + * + * var app = express.createServer() + * , blog = express.createServer(); + * + * blog.mounted(function(parent){ + * // parent is app + * // "this" is blog + * }); + * + * app.use(blog); + * + * @param {Function} fn + * @return {Server} for chaining + * @api public + */ + +app.mounted = function(fn){ + this.__mounted = fn; + return this; +}; + +/** + * See: view.register. + * + * @return {Server} for chaining + * @api public + */ + +app.register = function(){ + view.register.apply(this, arguments); + return this; +}; + +/** + * Register the given view helpers `obj`. This method + * can be called several times to apply additional helpers. + * + * @param {Object} obj + * @return {Server} for chaining + * @api public + */ + +app.helpers = +app.locals = function(obj){ + utils.merge(this._locals, obj); + return this; +}; + +/** + * Register the given dynamic view helpers `obj`. This method + * can be called several times to apply additional helpers. + * + * @param {Object} obj + * @return {Server} for chaining + * @api public + */ + +app.dynamicHelpers = function(obj){ + utils.merge(this.dynamicViewHelpers, obj); + return this; +}; + +/** + * Map the given param placeholder `name`(s) to the given callback(s). + * + * Param mapping is used to provide pre-conditions to routes + * which us normalized placeholders. This callback has the same + * signature as regular middleware, for example below when ":userId" + * is used this function will be invoked in an attempt to load the user. + * + * app.param('userId', function(req, res, next, id){ + * User.find(id, function(err, user){ + * if (err) { + * next(err); + * } else if (user) { + * req.user = user; + * next(); + * } else { + * next(new Error('failed to load user')); + * } + * }); + * }); + * + * Passing a single function allows you to map logic + * to the values passed to `app.param()`, for example + * this is useful to provide coercion support in a concise manner. + * + * The following example maps regular expressions to param values + * ensuring that they match, otherwise passing control to the next + * route: + * + * app.param(function(name, regexp){ + * if (regexp instanceof RegExp) { + * return function(req, res, next, val){ + * var captures; + * if (captures = regexp.exec(String(val))) { + * req.params[name] = captures; + * next(); + * } else { + * next('route'); + * } + * } + * } + * }); + * + * We can now use it as shown below, where "/commit/:commit" expects + * that the value for ":commit" is at 5 or more digits. The capture + * groups are then available as `req.params.commit` as we defined + * in the function above. + * + * app.param('commit', /^\d{5,}$/); + * + * For more of this useful functionality take a look + * at [express-params](http://github.com/visionmedia/express-params). + * + * @param {String|Array|Function} name + * @param {Function} fn + * @return {Server} for chaining + * @api public + */ + +app.param = function(name, fn){ + var self = this + , fns = [].slice.call(arguments, 1); + + // array + if (Array.isArray(name)) { + name.forEach(function(name){ + fns.forEach(function(fn){ + self.param(name, fn); + }); + }); + // param logic + } else if ('function' == typeof name) { + this.routes.param(name); + // single + } else { + if (':' == name[0]) name = name.substr(1); + fns.forEach(function(fn){ + self.routes.param(name, fn); + }); + } + + return this; +}; + +/** + * Assign a custom exception handler callback `fn`. + * These handlers are always _last_ in the middleware stack. + * + * @param {Function} fn + * @return {Server} for chaining + * @api public + */ + +app.error = function(fn){ + this.errorHandlers.push(fn); + return this; +}; + +/** + * Register the given callback `fn` for the given `type`. + * + * @param {String} type + * @param {Function} fn + * @return {Server} for chaining + * @api public + */ + +app.is = function(type, fn){ + if (!fn) return this.isCallbacks[type]; + this.isCallbacks[type] = fn; + return this; +}; + +/** + * Assign `setting` to `val`, or return `setting`'s value. + * Mounted servers inherit their parent server's settings. + * + * @param {String} setting + * @param {String} val + * @return {Server|Mixed} for chaining, or the setting value + * @api public + */ + +app.set = function(setting, val){ + if (val === undefined) { + if (this.settings.hasOwnProperty(setting)) { + return this.settings[setting]; + } else if (this.parent) { + return this.parent.set(setting); + } + } else { + this.settings[setting] = val; + return this; + } +}; + +/** + * Check if `setting` is enabled. + * + * @param {String} setting + * @return {Boolean} + * @api public + */ + +app.enabled = function(setting){ + return !!this.set(setting); +}; + +/** + * Check if `setting` is disabled. + * + * @param {String} setting + * @return {Boolean} + * @api public + */ + +app.disabled = function(setting){ + return !this.set(setting); +}; + +/** + * Enable `setting`. + * + * @param {String} setting + * @return {Server} for chaining + * @api public + */ + +app.enable = function(setting){ + return this.set(setting, true); +}; + +/** + * Disable `setting`. + * + * @param {String} setting + * @return {Server} for chaining + * @api public + */ + +app.disable = function(setting){ + return this.set(setting, false); +}; + +/** + * Redirect `key` to `url`. + * + * @param {String} key + * @param {String} url + * @return {Server} for chaining + * @api public + */ + +app.redirect = function(key, url){ + this.redirects[key] = url; + return this; +}; + +/** + * Configure callback for zero or more envs, + * when no env is specified that callback will + * be invoked for all environments. Any combination + * can be used multiple times, in any order desired. + * + * Examples: + * + * app.configure(function(){ + * // executed for all envs + * }); + * + * app.configure('stage', function(){ + * // executed staging env + * }); + * + * app.configure('stage', 'production', function(){ + * // executed for stage and production + * }); + * + * @param {String} env... + * @param {Function} fn + * @return {Server} for chaining + * @api public + */ + +app.configure = function(env, fn){ + var envs = 'all' + , args = toArray(arguments); + fn = args.pop(); + if (args.length) envs = args; + if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this); + return this; +}; + +/** + * Delegate `.VERB(...)` calls to `.route(VERB, ...)`. + */ + +methods.forEach(function(method){ + app[method] = function(path){ + if (1 == arguments.length) return this.routes.lookup(method, path); + var args = [method].concat(toArray(arguments)); + if (!this.__usedRouter) this.use(this.router); + return this.routes._route.apply(this.routes, args); + } +}); + +/** + * Special-cased "all" method, applying the given route `path`, + * middleware, and callback to _every_ HTTP method. + * + * @param {String} path + * @param {Function} ... + * @return {Server} for chaining + * @api public + */ + +app.all = function(path){ + var args = arguments; + if (1 == args.length) return this.routes.lookup('all', path); + methods.forEach(function(method){ + if ('all' == method) return; + app[method].apply(this, args); + }, this); + return this; +}; + +// del -> delete alias + +app.del = app.delete; + diff --git a/node_modules/express/lib/https.js b/node_modules/express/lib/https.js new file mode 100644 index 0000000..8a8c008 --- /dev/null +++ b/node_modules/express/lib/https.js @@ -0,0 +1,52 @@ + +/*! + * Express - HTTPSServer + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var connect = require('connect') + , HTTPServer = require('./http') + , https = require('https'); + +/** + * Expose `HTTPSServer`. + */ + +exports = module.exports = HTTPSServer; + +/** + * Server proto. + */ + +var app = HTTPSServer.prototype; + +/** + * Initialize a new `HTTPSServer` with the + * given `options`, and optional `middleware`. + * + * @param {Object} options + * @param {Array} middleware + * @api public + */ + +function HTTPSServer(options, middleware){ + connect.HTTPSServer.call(this, options, []); + this.init(middleware); +}; + +/** + * Inherit from `connect.HTTPSServer`. + */ + +app.__proto__ = connect.HTTPSServer.prototype; + +// mixin HTTPServer methods + +Object.keys(HTTPServer.prototype).forEach(function(method){ + app[method] = HTTPServer.prototype[method]; +}); diff --git a/node_modules/express/lib/request.js b/node_modules/express/lib/request.js new file mode 100644 index 0000000..bc95eae --- /dev/null +++ b/node_modules/express/lib/request.js @@ -0,0 +1,321 @@ + +/*! + * Express - request + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var http = require('http') + , req = http.IncomingMessage.prototype + , utils = require('./utils') + , parse = require('url').parse + , mime = require('mime'); + +/** + * Default flash formatters. + * + * @type Object + */ + +var flashFormatters = exports.flashFormatters = { + s: function(val){ + return String(val); + } +}; + +/** + * Return request header or optional default. + * + * The `Referrer` header field is special-cased, + * both `Referrer` and `Referer` will yield are + * interchangeable. + * + * Examples: + * + * req.header('Content-Type'); + * // => "text/plain" + * + * req.header('content-type'); + * // => "text/plain" + * + * req.header('Accept'); + * // => undefined + * + * req.header('Accept', 'text/html'); + * // => "text/html" + * + * @param {String} name + * @param {String} defaultValue + * @return {String} + * @api public + */ + +req.header = function(name, defaultValue){ + switch (name = name.toLowerCase()) { + case 'referer': + case 'referrer': + return this.headers.referrer + || this.headers.referer + || defaultValue; + default: + return this.headers[name] || defaultValue; + } +}; + +/** + * Get `field`'s `param` value, defaulting to ''. + * + * Examples: + * + * req.get('content-disposition', 'filename'); + * // => "something.png" + * + * @param {String} field + * @param {String} param + * @return {String} + * @api public + */ + +req.get = function(field, param){ + var val = this.header(field); + if (!val) return ''; + var regexp = new RegExp(param + ' *= *(?:"([^"]+)"|([^;]+))', 'i'); + if (!regexp.exec(val)) return ''; + return RegExp.$1 || RegExp.$2; +}; + +/** + * Short-hand for `require('url').parse(req.url).pathname`. + * + * @return {String} + * @api public + */ + +req.__defineGetter__('path', function(){ + return parse(this.url).pathname; +}); + +/** + * Check if the _Accept_ header is present, and includes the given `type`. + * + * When the _Accept_ header is not present `true` is returned. Otherwise + * the given `type` is matched by an exact match, and then subtypes. You + * may pass the subtype such as "html" which is then converted internally + * to "text/html" using the mime lookup table. + * + * Examples: + * + * // Accept: text/html + * req.accepts('html'); + * // => true + * + * // Accept: text/*; application/json + * req.accepts('html'); + * req.accepts('text/html'); + * req.accepts('text/plain'); + * req.accepts('application/json'); + * // => true + * + * req.accepts('image/png'); + * req.accepts('png'); + * // => false + * + * @param {String} type + * @return {Boolean} + * @api public + */ + +req.accepts = function(type){ + var accept = this.header('Accept'); + + // normalize extensions ".json" -> "json" + if (type && '.' == type[0]) type = type.substr(1); + + // when Accept does not exist, or is '*/*' return true + if (!accept || '*/*' == accept) { + return true; + } else if (type) { + // allow "html" vs "text/html" etc + if (!~type.indexOf('/')) type = mime.lookup(type); + + // check if we have a direct match + if (~accept.indexOf(type)) return true; + + // check if we have type/* + type = type.split('/')[0] + '/*'; + return !!~accept.indexOf(type); + } else { + return false; + } +}; + +/** + * Return the value of param `name` when present or `defaultValue`. + * + * - Checks route placeholders, ex: _/user/:id_ + * - Checks query string params, ex: ?id=12 + * - Checks urlencoded body params, ex: id=12 + * + * To utilize urlencoded request bodies, `req.body` + * should be an object. This can be done by using + * the `connect.bodyParser` middleware. + * + * @param {String} name + * @param {Mixed} defaultValue + * @return {String} + * @api public + */ + +req.param = function(name, defaultValue){ + // route params like /user/:id + if (this.params && this.params.hasOwnProperty(name) && undefined !== this.params[name]) { + return this.params[name]; + } + // query string params + if (undefined !== this.query[name]) { + return this.query[name]; + } + // request body params via connect.bodyParser + if (this.body && undefined !== this.body[name]) { + return this.body[name]; + } + return defaultValue; +}; + +/** + * Queue flash `msg` of the given `type`. + * + * Examples: + * + * req.flash('info', 'email sent'); + * req.flash('error', 'email delivery failed'); + * req.flash('info', 'email re-sent'); + * // => 2 + * + * req.flash('info'); + * // => ['email sent', 'email re-sent'] + * + * req.flash('info'); + * // => [] + * + * req.flash(); + * // => { error: ['email delivery failed'], info: [] } + * + * Formatting: + * + * Flash notifications also support arbitrary formatting support. + * For example you may pass variable arguments to `req.flash()` + * and use the %s specifier to be replaced by the associated argument: + * + * req.flash('info', 'email has been sent to %s.', userName); + * + * To add custom formatters use the `exports.flashFormatters` object. + * + * @param {String} type + * @param {String} msg + * @return {Array|Object|Number} + * @api public + */ + +req.flash = function(type, msg){ + if (this.session === undefined) throw Error('req.flash() requires sessions'); + var msgs = this.session.flash = this.session.flash || {}; + if (type && msg) { + var i = 2 + , args = arguments + , formatters = this.app.flashFormatters || {}; + formatters.__proto__ = flashFormatters; + msg = utils.miniMarkdown(msg); + msg = msg.replace(/%([a-zA-Z])/g, function(_, format){ + var formatter = formatters[format]; + if (formatter) return formatter(utils.escape(args[i++])); + }); + return (msgs[type] = msgs[type] || []).push(msg); + } else if (type) { + var arr = msgs[type]; + delete msgs[type]; + return arr || []; + } else { + this.session.flash = {}; + return msgs; + } +}; + +/** + * Check if the incoming request contains the "Content-Type" + * header field, and it contains the give mime `type`. + * + * Examples: + * + * // With Content-Type: text/html; charset=utf-8 + * req.is('html'); + * req.is('text/html'); + * // => true + * + * // When Content-Type is application/json + * req.is('json'); + * req.is('application/json'); + * // => true + * + * req.is('html'); + * // => false + * + * Ad-hoc callbacks can also be registered with Express, to perform + * assertions again the request, for example if we need an expressive + * way to check if our incoming request is an image, we can register "an image" + * callback: + * + * app.is('an image', function(req){ + * return 0 == req.headers['content-type'].indexOf('image'); + * }); + * + * Now within our route callbacks, we can use to to assert content types + * such as "image/jpeg", "image/png", etc. + * + * app.post('/image/upload', function(req, res, next){ + * if (req.is('an image')) { + * // do something + * } else { + * next(); + * } + * }); + * + * @param {String} type + * @return {Boolean} + * @api public + */ + +req.is = function(type){ + var fn = this.app.is(type); + if (fn) return fn(this); + var contentType = this.headers['content-type']; + if (!contentType) return; + if (!~type.indexOf('/')) type = mime.lookup(type); + if (~type.indexOf('*')) { + type = type.split('/') + contentType = contentType.split('/'); + if ('*' == type[0] && type[1] == contentType[1]) return true; + if ('*' == type[1] && type[0] == contentType[0]) return true; + } + return !! ~contentType.indexOf(type); +}; + +// Callback for isXMLHttpRequest / xhr + +function isxhr() { + return this.header('X-Requested-With', '').toLowerCase() === 'xmlhttprequest'; +} + +/** + * Check if the request was an _XMLHttpRequest_. + * + * @return {Boolean} + * @api public + */ + +req.__defineGetter__('isXMLHttpRequest', isxhr); +req.__defineGetter__('xhr', isxhr); diff --git a/node_modules/express/lib/response.js b/node_modules/express/lib/response.js new file mode 100644 index 0000000..a671771 --- /dev/null +++ b/node_modules/express/lib/response.js @@ -0,0 +1,460 @@ + +/*! + * Express - response + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var fs = require('fs') + , http = require('http') + , path = require('path') + , connect = require('connect') + , utils = connect.utils + , parseRange = require('./utils').parseRange + , res = http.ServerResponse.prototype + , send = connect.static.send + , mime = require('mime') + , basename = path.basename + , join = path.join; + +/** + * Send a response with the given `body` and optional `headers` and `status` code. + * + * Examples: + * + * res.send(); + * res.send(new Buffer('wahoo')); + * res.send({ some: 'json' }); + * res.send('

some html

'); + * res.send('Sorry, cant find that', 404); + * res.send('text', { 'Content-Type': 'text/plain' }, 201); + * res.send(404); + * + * @param {String|Object|Number|Buffer} body or status + * @param {Object|Number} headers or status + * @param {Number} status + * @return {ServerResponse} + * @api public + */ + +res.send = function(body, headers, status){ + // allow status as second arg + if ('number' == typeof headers) { + status = headers, + headers = null; + } + + // default status + status = status || this.statusCode; + + // allow 0 args as 204 + if (!arguments.length || undefined === body) status = 204; + + // determine content type + switch (typeof body) { + case 'number': + if (!this.header('Content-Type')) { + this.contentType('.txt'); + } + body = http.STATUS_CODES[status = body]; + break; + case 'string': + if (!this.header('Content-Type')) { + this.charset = this.charset || 'utf-8'; + this.contentType('.html'); + } + break; + case 'boolean': + case 'object': + if (Buffer.isBuffer(body)) { + if (!this.header('Content-Type')) { + this.contentType('.bin'); + } + } else { + return this.json(body, headers, status); + } + break; + } + + // populate Content-Length + if (undefined !== body && !this.header('Content-Length')) { + this.header('Content-Length', Buffer.isBuffer(body) + ? body.length + : Buffer.byteLength(body)); + } + + // merge headers passed + if (headers) { + var fields = Object.keys(headers); + for (var i = 0, len = fields.length; i < len; ++i) { + var field = fields[i]; + this.header(field, headers[field]); + } + } + + // strip irrelevant headers + if (204 == status || 304 == status) { + this.removeHeader('Content-Type'); + this.removeHeader('Content-Length'); + body = ''; + } + + // respond + this.statusCode = status; + this.end('HEAD' == this.req.method ? null : body); + return this; +}; + +/** + * Send JSON response with `obj`, optional `headers`, and optional `status`. + * + * Examples: + * + * res.json(null); + * res.json({ user: 'tj' }); + * res.json('oh noes!', 500); + * res.json('I dont have that', 404); + * + * @param {Mixed} obj + * @param {Object|Number} headers or status + * @param {Number} status + * @return {ServerResponse} + * @api public + */ + +res.json = function(obj, headers, status){ + var body = JSON.stringify(obj) + , callback = this.req.query.callback + , jsonp = this.app.enabled('jsonp callback'); + + this.charset = this.charset || 'utf-8'; + this.header('Content-Type', 'application/json'); + + if (callback && jsonp) { + this.header('Content-Type', 'text/javascript'); + body = callback.replace(/[^\w$.]/g, '') + '(' + body + ');'; + } + + return this.send(body, headers, status); +}; + +/** + * Set status `code`. + * + * @param {Number} code + * @return {ServerResponse} + * @api public + */ + +res.status = function(code){ + this.statusCode = code; + return this; +}; + +/** + * Transfer the file at the given `path`. Automatically sets + * the _Content-Type_ response header field. `next()` is called + * when `path` is a directory, or when an error occurs. + * + * Options: + * + * - `maxAge` defaulting to 0 + * - `root` root directory for relative filenames + * + * @param {String} path + * @param {Object|Function} options or fn + * @param {Function} fn + * @api public + */ + +res.sendfile = function(path, options, fn){ + var next = this.req.next; + options = options || {}; + + // support function as second arg + if ('function' == typeof options) { + fn = options; + options = {}; + } + + options.path = encodeURIComponent(path); + options.callback = fn; + send(this.req, this, next, options); +}; + +/** + * Set _Content-Type_ response header passed through `mime.lookup()`. + * + * Examples: + * + * var filename = 'path/to/image.png'; + * res.contentType(filename); + * // res.headers['Content-Type'] is now "image/png" + * + * res.contentType('.html'); + * res.contentType('html'); + * res.contentType('json'); + * res.contentType('png'); + * + * @param {String} type + * @return {String} the resolved mime type + * @api public + */ + +res.contentType = function(type){ + return this.header('Content-Type', mime.lookup(type)); +}; + +/** + * Set _Content-Disposition_ header to _attachment_ with optional `filename`. + * + * @param {String} filename + * @return {ServerResponse} + * @api public + */ + +res.attachment = function(filename){ + if (filename) this.contentType(filename); + this.header('Content-Disposition', filename + ? 'attachment; filename="' + basename(filename) + '"' + : 'attachment'); + return this; +}; + +/** + * Transfer the file at the given `path`, with optional + * `filename` as an attachment and optional callback `fn(err)`, + * and optional `fn2(err)` which is invoked when an error has + * occurred after header has been sent. + * + * @param {String} path + * @param {String|Function} filename or fn + * @param {Function} fn + * @param {Function} fn2 + * @api public + */ + +res.download = function(path, filename, fn, fn2){ + var self = this; + + // support callback as second arg + if ('function' == typeof filename) { + fn2 = fn; + fn = filename; + filename = null; + } + + // transfer the file + this.attachment(filename || path).sendfile(path, function(err){ + var sentHeader = self._header; + if (err) { + if (!sentHeader) self.removeHeader('Content-Disposition'); + if (sentHeader) { + fn2 && fn2(err); + } else if (fn) { + fn(err); + } else { + self.req.next(err); + } + } else if (fn) { + fn(); + } + }); +}; + +/** + * Set or get response header `name` with optional `val`. + * + * @param {String} name + * @param {String} val + * @return {ServerResponse} for chaining + * @api public + */ + +res.header = function(name, val){ + if (1 == arguments.length) return this.getHeader(name); + this.setHeader(name, val); + return this; +}; + +/** + * Clear cookie `name`. + * + * @param {String} name + * @param {Object} options + * @api public + */ + +res.clearCookie = function(name, options){ + var opts = { expires: new Date(1) }; + this.cookie(name, '', options + ? utils.merge(options, opts) + : opts); +}; + +/** + * Set cookie `name` to `val`, with the given `options`. + * + * Options: + * + * - `maxAge` max-age in milliseconds, converted to `expires` + * - `path` defaults to the "basepath" setting which is typically "/" + * + * Examples: + * + * // "Remember Me" for 15 minutes + * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }); + * + * // save as above + * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true }) + * + * @param {String} name + * @param {String} val + * @param {Options} options + * @api public + */ + +res.cookie = function(name, val, options){ + options = options || {}; + if ('maxAge' in options) options.expires = new Date(Date.now() + options.maxAge); + if (undefined === options.path) options.path = this.app.set('basepath'); + var cookie = utils.serializeCookie(name, val, options); + this.header('Set-Cookie', cookie); +}; + +/** + * Redirect to the given `url` with optional response `status` + * defauling to 302. + * + * The given `url` can also be the name of a mapped url, for + * example by default express supports "back" which redirects + * to the _Referrer_ or _Referer_ headers or the application's + * "basepath" setting. Express also supports "basepath" out of the box, + * which can be set via `app.set('basepath', '/blog');`, and defaults + * to '/'. + * + * Redirect Mapping: + * + * To extend the redirect mapping capabilities that Express provides, + * we may use the `app.redirect()` method: + * + * app.redirect('google', 'http://google.com'); + * + * Now in a route we may call: + * + * res.redirect('google'); + * + * We may also map dynamic redirects: + * + * app.redirect('comments', function(req, res){ + * return '/post/' + req.params.id + '/comments'; + * }); + * + * So now we may do the following, and the redirect will dynamically adjust to + * the context of the request. If we called this route with _GET /post/12_ our + * redirect _Location_ would be _/post/12/comments_. + * + * app.get('/post/:id', function(req, res){ + * res.redirect('comments'); + * }); + * + * Unless an absolute `url` is given, the app's mount-point + * will be respected. For example if we redirect to `/posts`, + * and our app is mounted at `/blog` we will redirect to `/blog/posts`. + * + * @param {String} url + * @param {Number} code + * @api public + */ + +res.redirect = function(url, status){ + var app = this.app + , req = this.req + , base = app.set('basepath') || app.route + , status = status || 302 + , head = 'HEAD' == req.method + , body; + + // Setup redirect map + var map = { + back: req.header('Referrer', base) + , home: base + }; + + // Support custom redirect map + map.__proto__ = app.redirects; + + // Attempt mapped redirect + var mapped = 'function' == typeof map[url] + ? map[url](req, this) + : map[url]; + + // Perform redirect + url = mapped || url; + + // Relative + if (!~url.indexOf('://')) { + // Respect mount-point + if ('/' != base && 0 != url.indexOf(base)) url = base + url; + + // Absolute + var host = req.headers.host + , tls = req.connection.encrypted; + url = 'http' + (tls ? 's' : '') + '://' + host + url; + } + + // Support text/{plain,html} by default + if (req.accepts('html')) { + body = '

' + http.STATUS_CODES[status] + '. Redirecting to ' + url + '

'; + this.header('Content-Type', 'text/html'); + } else { + body = http.STATUS_CODES[status] + '. Redirecting to ' + url; + this.header('Content-Type', 'text/plain'); + } + + // Respond + this.statusCode = status; + this.header('Location', url); + this.end(head ? null : body); +}; + +/** + * Assign the view local variable `name` to `val` or return the + * local previously assigned to `name`. + * + * @param {String} name + * @param {Mixed} val + * @return {Mixed} val + * @api public + */ + +res.local = function(name, val){ + this._locals = this._locals || {}; + return undefined === val + ? this._locals[name] + : this._locals[name] = val; +}; + +/** + * Assign several locals with the given `obj`, + * or return the locals. + * + * @param {Object} obj + * @return {Object|Undefined} + * @api public + */ + +res.locals = +res.helpers = function(obj){ + if (obj) { + for (var key in obj) { + this.local(key, obj[key]); + } + } else { + return this._locals; + } +}; diff --git a/node_modules/express/lib/router/collection.js b/node_modules/express/lib/router/collection.js new file mode 100644 index 0000000..991a9a2 --- /dev/null +++ b/node_modules/express/lib/router/collection.js @@ -0,0 +1,53 @@ + +/*! + * Express - router - Collection + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Expose `Collection`. + */ + +module.exports = Collection; + +/** + * Initialize a new route `Collection` + * with the given `router`. + * + * @param {Router} router + * @api private + */ + +function Collection(router) { + Array.apply(this, arguments); + this.router = router; +} + +/** + * Inherit from `Array.prototype`. + */ + +Collection.prototype.__proto__ = Array.prototype; + +/** + * Remove the routes in this collection. + * + * @return {Collection} of routes removed + * @api public + */ + +Collection.prototype.remove = function(){ + var router = this.router + , len = this.length + , ret = new Collection(this.router); + + for (var i = 0; i < len; ++i) { + if (router.remove(this[i])) { + ret.push(this[i]); + } + } + + return ret; +}; + diff --git a/node_modules/express/lib/router/index.js b/node_modules/express/lib/router/index.js new file mode 100644 index 0000000..ff1498b --- /dev/null +++ b/node_modules/express/lib/router/index.js @@ -0,0 +1,398 @@ + +/*! + * Express - Router + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Route = require('./route') + , Collection = require('./collection') + , utils = require('../utils') + , parse = require('url').parse + , toArray = utils.toArray; + +/** + * Expose `Router` constructor. + */ + +exports = module.exports = Router; + +/** + * Expose HTTP methods. + */ + +var methods = exports.methods = require('./methods'); + +/** + * Initialize a new `Router` with the given `app`. + * + * @param {express.HTTPServer} app + * @api private + */ + +function Router(app) { + var self = this; + this.app = app; + this.routes = {}; + this.params = {}; + this._params = []; + + this.middleware = function(req, res, next){ + self._dispatch(req, res, next); + }; +} + +/** + * Register a param callback `fn` for the given `name`. + * + * @param {String|Function} name + * @param {Function} fn + * @return {Router} for chaining + * @api public + */ + +Router.prototype.param = function(name, fn){ + // param logic + if ('function' == typeof name) { + this._params.push(name); + return; + } + + // apply param functions + var params = this._params + , len = params.length + , ret; + + for (var i = 0; i < len; ++i) { + if (ret = params[i](name, fn)) { + fn = ret; + } + } + + // ensure we end up with a + // middleware function + if ('function' != typeof fn) { + throw new Error('invalid param() call for ' + name + ', got ' + fn); + } + + (this.params[name] = this.params[name] || []).push(fn); + return this; +}; + +/** + * Return a `Collection` of all routes defined. + * + * @return {Collection} + * @api public + */ + +Router.prototype.all = function(){ + return this.find(function(){ + return true; + }); +}; + +/** + * Remove the given `route`, returns + * a bool indicating if the route was present + * or not. + * + * @param {Route} route + * @return {Boolean} + * @api public + */ + +Router.prototype.remove = function(route){ + var routes = this.routes[route.method] + , len = routes.length; + + for (var i = 0; i < len; ++i) { + if (route == routes[i]) { + routes.splice(i, 1); + return true; + } + } +}; + +/** + * Return routes with route paths matching `path`. + * + * @param {String} method + * @param {String} path + * @return {Collection} + * @api public + */ + +Router.prototype.lookup = function(method, path){ + return this.find(function(route){ + return path == route.path + && (route.method == method + || method == 'all'); + }); +}; + +/** + * Return routes with regexps that match the given `url`. + * + * @param {String} method + * @param {String} url + * @return {Collection} + * @api public + */ + +Router.prototype.match = function(method, url){ + return this.find(function(route){ + return route.match(url) + && (route.method == method + || method == 'all'); + }); +}; + +/** + * Find routes based on the return value of `fn` + * which is invoked once per route. + * + * @param {Function} fn + * @return {Collection} + * @api public + */ + +Router.prototype.find = function(fn){ + var len = methods.length + , ret = new Collection(this) + , method + , routes + , route; + + for (var i = 0; i < len; ++i) { + method = methods[i]; + routes = this.routes[method]; + if (!routes) continue; + for (var j = 0, jlen = routes.length; j < jlen; ++j) { + route = routes[j]; + if (fn(route)) ret.push(route); + } + } + + return ret; +}; + +/** + * Route dispatcher aka the route "middleware". + * + * @param {IncomingMessage} req + * @param {ServerResponse} res + * @param {Function} next + * @api private + */ + +Router.prototype._dispatch = function(req, res, next){ + var params = this.params + , self = this; + + // route dispatch + (function pass(i, err){ + var paramCallbacks + , paramIndex = 0 + , paramVal + , route + , keys + , key + , ret; + + // match next route + function nextRoute(err) { + pass(req._route_index + 1, err); + } + + // match route + req.route = route = self._match(req, i); + + // implied OPTIONS + if (!route && 'OPTIONS' == req.method) return self._options(req, res); + + // no route + if (!route) return next(err); + + // we have a route + // start at param 0 + req.params = route.params; + keys = route.keys; + i = 0; + + // param callbacks + function param(err) { + paramIndex = 0; + key = keys[i++]; + paramVal = key && req.params[key.name]; + paramCallbacks = key && params[key.name]; + + try { + if ('route' == err) { + nextRoute(); + } else if (err) { + i = 0; + callbacks(err); + } else if (paramCallbacks && undefined !== paramVal) { + paramCallback(); + } else if (key) { + param(); + } else { + i = 0; + callbacks(); + } + } catch (err) { + param(err); + } + }; + + param(err); + + // single param callbacks + function paramCallback(err) { + var fn = paramCallbacks[paramIndex++]; + if (err || !fn) return param(err); + fn(req, res, paramCallback, paramVal, key.name); + } + + // invoke route callbacks + function callbacks(err) { + var fn = route.callbacks[i++]; + try { + if ('route' == err) { + nextRoute(); + } else if (err && fn) { + if (fn.length < 4) return callbacks(err); + fn(err, req, res, callbacks); + } else if (fn) { + fn(req, res, callbacks); + } else { + nextRoute(err); + } + } catch (err) { + callbacks(err); + } + } + })(0); +}; + +/** + * Respond to __OPTIONS__ method. + * + * @param {IncomingMessage} req + * @param {ServerResponse} res + * @api private + */ + +Router.prototype._options = function(req, res){ + var path = parse(req.url).pathname + , body = this._optionsFor(path).join(','); + res.send(body, { Allow: body }); +}; + +/** + * Return an array of HTTP verbs or "options" for `path`. + * + * @param {String} path + * @return {Array} + * @api private + */ + +Router.prototype._optionsFor = function(path){ + var self = this; + return methods.filter(function(method){ + var routes = self.routes[method]; + if (!routes || 'options' == method) return; + for (var i = 0, len = routes.length; i < len; ++i) { + if (routes[i].match(path)) return true; + } + }).map(function(method){ + return method.toUpperCase(); + }); +}; + +/** + * Attempt to match a route for `req` + * starting from offset `i`. + * + * @param {IncomingMessage} req + * @param {Number} i + * @return {Route} + * @api private + */ + +Router.prototype._match = function(req, i){ + var method = req.method.toLowerCase() + , url = parse(req.url) + , path = url.pathname + , routes = this.routes + , captures + , route + , keys; + + // pass HEAD to GET routes + if ('head' == method) method = 'get'; + + // routes for this method + if (routes = routes[method]) { + + // matching routes + for (var len = routes.length; i < len; ++i) { + route = routes[i]; + if (captures = route.match(path)) { + keys = route.keys; + route.params = []; + + // params from capture groups + for (var j = 1, jlen = captures.length; j < jlen; ++j) { + var key = keys[j-1] + , val = 'string' == typeof captures[j] + ? decodeURIComponent(captures[j]) + : captures[j]; + if (key) { + route.params[key.name] = val; + } else { + route.params.push(val); + } + } + + // all done + req._route_index = i; + return route; + } + } + } +}; + +/** + * Route `method`, `path`, and one or more callbacks. + * + * @param {String} method + * @param {String} path + * @param {Function} callback... + * @return {Router} for chaining + * @api private + */ + +Router.prototype._route = function(method, path, callbacks){ + var app = this.app + , callbacks = utils.flatten(toArray(arguments, 2)); + + // ensure path was given + if (!path) throw new Error('app.' + method + '() requires a path'); + + // create the route + var route = new Route(method, path, callbacks, { + sensitive: app.enabled('case sensitive routes') + , strict: app.enabled('strict routing') + }); + + // add it + (this.routes[method] = this.routes[method] || []) + .push(route); + return this; +}; diff --git a/node_modules/express/lib/router/methods.js b/node_modules/express/lib/router/methods.js new file mode 100644 index 0000000..e19787b --- /dev/null +++ b/node_modules/express/lib/router/methods.js @@ -0,0 +1,70 @@ + +/*! + * Express - router - methods + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Hypertext Transfer Protocol -- HTTP/1.1 + * http://www.ietf.org/rfc/rfc2616.txt + */ + +var RFC2616 = ['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT']; + +/** + * HTTP Extensions for Distributed Authoring -- WEBDAV + * http://www.ietf.org/rfc/rfc2518.txt + */ + +var RFC2518 = ['PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK']; + +/** + * Versioning Extensions to WebDAV + * http://www.ietf.org/rfc/rfc3253.txt + */ + +var RFC3253 = ['VERSION-CONTROL', 'REPORT', 'CHECKOUT', 'CHECKIN', 'UNCHECKOUT', 'MKWORKSPACE', 'UPDATE', 'LABEL', 'MERGE', 'BASELINE-CONTROL', 'MKACTIVITY']; + +/** + * Ordered Collections Protocol (WebDAV) + * http://www.ietf.org/rfc/rfc3648.txt + */ + +var RFC3648 = ['ORDERPATCH']; + +/** + * Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol + * http://www.ietf.org/rfc/rfc3744.txt + */ + +var RFC3744 = ['ACL']; + +/** + * Web Distributed Authoring and Versioning (WebDAV) SEARCH + * http://www.ietf.org/rfc/rfc5323.txt + */ + +var RFC5323 = ['SEARCH']; + +/** + * PATCH Method for HTTP + * http://www.ietf.org/rfc/rfc5789.txt + */ + +var RFC5789 = ['PATCH']; + +/** + * Expose the methods. + */ + +module.exports = [].concat( + RFC2616 + , RFC2518 + , RFC3253 + , RFC3648 + , RFC3744 + , RFC5323 + , RFC5789).map(function(method){ + return method.toLowerCase(); + }); diff --git a/node_modules/express/lib/router/route.js b/node_modules/express/lib/router/route.js new file mode 100644 index 0000000..7f2965c --- /dev/null +++ b/node_modules/express/lib/router/route.js @@ -0,0 +1,88 @@ + +/*! + * Express - router - Route + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Expose `Route`. + */ + +module.exports = Route; + +/** + * Initialize `Route` with the given HTTP `method`, `path`, + * and an array of `callbacks` and `options`. + * + * Options: + * + * - `sensitive` enable case-sensitive routes + * - `strict` enable strict matching for trailing slashes + * + * @param {String} method + * @param {String} path + * @param {Array} callbacks + * @param {Object} options. + * @api private + */ + +function Route(method, path, callbacks, options) { + options = options || {}; + this.path = path; + this.method = method; + this.callbacks = callbacks; + this.regexp = normalize(path + , this.keys = [] + , options.sensitive + , options.strict); +} + +/** + * Check if this route matches `path` and return captures made. + * + * @param {String} path + * @return {Array} + * @api private + */ + +Route.prototype.match = function(path){ + return this.regexp.exec(path); +}; + +/** + * Normalize the given path string, + * returning a regular expression. + * + * An empty array should be passed, + * which will contain the placeholder + * key names. For example "/user/:id" will + * then contain ["id"]. + * + * @param {String|RegExp} path + * @param {Array} keys + * @param {Boolean} sensitive + * @param {Boolean} strict + * @return {RegExp} + * @api private + */ + +function normalize(path, keys, sensitive, strict) { + if (path instanceof RegExp) return path; + path = path + .concat(strict ? '' : '/?') + .replace(/\/\(/g, '(?:/') + .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){ + keys.push({ name: key, optional: !! optional }); + slash = slash || ''; + return '' + + (optional ? '' : slash) + + '(?:' + + (optional ? slash : '') + + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + + (optional || ''); + }) + .replace(/([\/.])/g, '\\$1') + .replace(/\*/g, '(.*)'); + return new RegExp('^' + path + '$', sensitive ? '' : 'i'); +} diff --git a/node_modules/express/lib/utils.js b/node_modules/express/lib/utils.js new file mode 100644 index 0000000..d579f7c --- /dev/null +++ b/node_modules/express/lib/utils.js @@ -0,0 +1,152 @@ + +/*! + * Express - Utils + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Check if `path` looks absolute. + * + * @param {String} path + * @return {Boolean} + * @api private + */ + +exports.isAbsolute = function(path){ + if ('/' == path[0]) return true; + if (':' == path[1] && '\\' == path[2]) return true; +}; + +/** + * Merge object `b` with `a` giving precedence to + * values in object `a`. + * + * @param {Object} a + * @param {Object} b + * @return {Object} a + * @api private + */ + +exports.union = function(a, b){ + if (a && b) { + var keys = Object.keys(b) + , len = keys.length + , key; + for (var i = 0; i < len; ++i) { + key = keys[i]; + if (!a.hasOwnProperty(key)) { + a[key] = b[key]; + } + } + } + return a; +}; + +/** + * Flatten the given `arr`. + * + * @param {Array} arr + * @return {Array} + * @api private + */ + +exports.flatten = function(arr, ret){ + var ret = ret || [] + , len = arr.length; + for (var i = 0; i < len; ++i) { + if (Array.isArray(arr[i])) { + exports.flatten(arr[i], ret); + } else { + ret.push(arr[i]); + } + } + return ret; +}; + +/** + * Parse mini markdown implementation. + * The following conversions are supported, + * primarily for the "flash" middleware: + * + * _foo_ or *foo* become foo + * __foo__ or **foo** become foo + * [A](B) becomes A + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.miniMarkdown = function(str){ + return String(str) + .replace(/(__|\*\*)(.*?)\1/g, '$2') + .replace(/(_|\*)(.*?)\1/g, '$2') + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); +}; + +/** + * Escape special characters in the given string of html. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function(html) { + return String(html) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +}; + +/** + * Parse "Range" header `str` relative to the given file `size`. + * + * @param {Number} size + * @param {String} str + * @return {Array} + * @api private + */ + +exports.parseRange = function(size, str){ + var valid = true; + var arr = str.substr(6).split(',').map(function(range){ + var range = range.split('-') + , start = parseInt(range[0], 10) + , end = parseInt(range[1], 10); + + // -500 + if (isNaN(start)) { + start = size - end; + end = size - 1; + // 500- + } else if (isNaN(end)) { + end = size - 1; + } + + // Invalid + if (isNaN(start) || isNaN(end) || start > end) valid = false; + + return { start: start, end: end }; + }); + return valid ? arr : undefined; +}; + +/** + * Fast alternative to `Array.prototype.slice.call()`. + * + * @param {Arguments} args + * @param {Number} n + * @return {Array} + * @api public + */ + +exports.toArray = function(args, i){ + var arr = [] + , len = args.length + , i = i || 0; + for (; i < len; ++i) arr.push(args[i]); + return arr; +}; diff --git a/node_modules/express/lib/view.js b/node_modules/express/lib/view.js new file mode 100644 index 0000000..876d795 --- /dev/null +++ b/node_modules/express/lib/view.js @@ -0,0 +1,457 @@ + +/*! + * Express - view + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('path') + , extname = path.extname + , dirname = path.dirname + , basename = path.basename + , utils = require('connect').utils + , View = require('./view/view') + , partial = require('./view/partial') + , union = require('./utils').union + , merge = utils.merge + , http = require('http') + , res = http.ServerResponse.prototype; + +/** + * Expose constructors. + */ + +exports = module.exports = View; + +/** + * Export template engine registrar. + */ + +exports.register = View.register; + +/** + * Lookup and compile `view` with cache support by supplying + * both the `cache` object and `cid` string, + * followed by `options` passed to `exports.lookup()`. + * + * @param {String} view + * @param {Object} cache + * @param {Object} cid + * @param {Object} options + * @return {View} + * @api private + */ + +exports.compile = function(view, cache, cid, options){ + if (cache && cid && cache[cid]) return cache[cid]; + + // lookup + view = exports.lookup(view, options); + + // hints + if (!view.exists) { + if (options.hint) hintAtViewPaths(view.original, options); + var err = new Error('failed to locate view "' + view.original.view + '"'); + err.view = view.original; + throw err; + } + + // compile + options.filename = view.path; + view.fn = view.templateEngine.compile(view.contents, options); + cache[cid] = view; + + return view; +}; + +/** + * Lookup `view`, returning an instanceof `View`. + * + * Options: + * + * - `root` root directory path + * - `defaultEngine` default template engine + * - `parentView` parent `View` object + * - `cache` cache object + * - `cacheid` optional cache id + * + * Lookup: + * + * - partial `_` + * - any `/index` + * - non-layout `..//index` + * - any `/` + * - partial `/_` + * + * @param {String} view + * @param {Object} options + * @return {View} + * @api private + */ + +exports.lookup = function(view, options){ + var orig = view = new View(view, options) + , partial = options.isPartial + , layout = options.isLayout; + + // Try _ prefix ex: ./views/_.jade + // taking precedence over the direct path + if (partial) { + view = new View(orig.prefixPath, options); + if (!view.exists) view = orig; + } + + // Try index ex: ./views/user/index.jade + if (!layout && !view.exists) view = new View(orig.indexPath, options); + + // Try ..//index ex: ../user/index.jade + // when calling partial('user') within the same dir + if (!layout && !view.exists) view = new View(orig.upIndexPath, options); + + // Try root ex: /user.jade + if (!view.exists) view = new View(orig.rootPath, options); + + // Try root _ prefix ex: /_user.jade + if (!view.exists && partial) view = new View(view.prefixPath, options); + + view.original = orig; + return view; +}; + +/** + * Partial render helper. + * + * @api private + */ + +function renderPartial(res, view, options, parentLocals, parent){ + var collection, object, locals; + + if (options) { + // collection + if (options.collection) { + collection = options.collection; + delete options.collection; + } else if ('length' in options) { + collection = options; + options = {}; + } + + // locals + if (options.locals) { + locals = options.locals; + delete options.locals; + } + + // object + if ('Object' != options.constructor.name) { + object = options; + options = {}; + } else if (undefined != options.object) { + object = options.object; + delete options.object; + } + } else { + options = {}; + } + + // Inherit locals from parent + union(options, parentLocals); + + // Merge locals + if (locals) merge(options, locals); + + // Partials dont need layouts + options.isPartial = true; + options.layout = false; + + // Deduce name from view path + var name = options.as || partial.resolveObjectName(view); + + // Render partial + function render(){ + if (object) { + if ('string' == typeof name) { + options[name] = object; + } else if (name === global) { + merge(options, object); + } + } + return res.render(view, options, null, parent, true); + } + + // Collection support + if (collection) { + var len = collection.length + , buf = '' + , keys + , key + , val; + + options.collectionLength = len; + + if ('number' == typeof len || Array.isArray(collection)) { + for (var i = 0; i < len; ++i) { + val = collection[i]; + options.firstInCollection = i == 0; + options.indexInCollection = i; + options.lastInCollection = i == len - 1; + object = val; + buf += render(); + } + } else { + keys = Object.keys(collection); + len = keys.length; + options.collectionLength = len; + options.collectionKeys = keys; + for (var i = 0; i < len; ++i) { + key = keys[i]; + val = collection[key]; + options.keyInCollection = key; + options.firstInCollection = i == 0; + options.indexInCollection = i; + options.lastInCollection = i == len - 1; + object = val; + buf += render(); + } + } + + return buf; + } else { + return render(); + } +}; + +/** + * Render `view` partial with the given `options`. Optionally a + * callback `fn(err, str)` may be passed instead of writing to + * the socket. + * + * Options: + * + * - `object` Single object with name derived from the view (unless `as` is present) + * + * - `as` Variable name for each `collection` value, defaults to the view name. + * * as: 'something' will add the `something` local variable + * * as: this will use the collection value as the template context + * * as: global will merge the collection value's properties with `locals` + * + * - `collection` Array of objects, the name is derived from the view name itself. + * For example _video.html_ will have a object _video_ available to it. + * + * @param {String} view + * @param {Object|Array|Function} options, collection, callback, or object + * @param {Function} fn + * @return {String} + * @api public + */ + +res.partial = function(view, options, fn){ + var app = this.app + , options = options || {} + , viewEngine = app.set('view engine') + , parent = {}; + + // accept callback as second argument + if ('function' == typeof options) { + fn = options; + options = {}; + } + + // root "views" option + parent.dirname = app.set('views') || process.cwd() + '/views'; + + // utilize "view engine" option + if (viewEngine) parent.engine = viewEngine; + + // render the partial + try { + var str = renderPartial(this, view, options, null, parent); + } catch (err) { + if (fn) { + fn(err); + } else { + this.req.next(err); + } + return; + } + + // callback or transfer + if (fn) { + fn(null, str); + } else { + this.send(str); + } +}; + +/** + * Render `view` with the given `options` and optional callback `fn`. + * When a callback function is given a response will _not_ be made + * automatically, however otherwise a response of _200_ and _text/html_ is given. + * + * Options: + * + * - `scope` Template evaluation context (the value of `this`) + * - `debug` Output debugging information + * - `status` Response status code + * + * @param {String} view + * @param {Object|Function} options or callback function + * @param {Function} fn + * @api public + */ + +res.render = function(view, opts, fn, parent, sub){ + // support callback function as second arg + if ('function' == typeof opts) { + fn = opts, opts = null; + } + + try { + return this._render(view, opts, fn, parent, sub); + } catch (err) { + // callback given + if (fn) { + fn(err); + // unwind to root call to prevent multiple callbacks + } else if (sub) { + throw err; + // root template, next(err) + } else { + this.req.next(err); + } + } +}; + +// private render() + +res._render = function(view, opts, fn, parent, sub){ + var options = {} + , self = this + , app = this.app + , helpers = app._locals + , dynamicHelpers = app.dynamicViewHelpers + , viewOptions = app.set('view options') + , root = app.set('views') || process.cwd() + '/views'; + + // cache id + var cid = app.enabled('view cache') + ? view + (parent ? ':' + parent.path : '') + : false; + + // merge "view options" + if (viewOptions) merge(options, viewOptions); + + // merge res._locals + if (this._locals) merge(options, this._locals); + + // merge render() options + if (opts) merge(options, opts); + + // merge render() .locals + if (opts && opts.locals) merge(options, opts.locals); + + // status support + if (options.status) this.statusCode = options.status; + + // capture attempts + options.attempts = []; + + var partial = options.isPartial + , layout = options.layout; + + // Layout support + if (true === layout || undefined === layout) { + layout = 'layout'; + } + + // Default execution scope to a plain object + options.scope = options.scope || {}; + + // Populate view + options.parentView = parent; + + // "views" setting + options.root = root; + + // "view engine" setting + options.defaultEngine = app.set('view engine'); + + // charset option + if (options.charset) this.charset = options.charset; + + // Dynamic helper support + if (false !== options.dynamicHelpers) { + // cache + if (!this.__dynamicHelpers) { + this.__dynamicHelpers = {}; + for (var key in dynamicHelpers) { + this.__dynamicHelpers[key] = dynamicHelpers[key].call( + this.app + , this.req + , this); + } + } + + // apply + merge(options, this.__dynamicHelpers); + } + + // Merge view helpers + union(options, helpers); + + // Always expose partial() as a local + options.partial = function(path, opts){ + return renderPartial(self, path, opts, options, view); + }; + + // View lookup + options.hint = app.enabled('hints'); + view = exports.compile(view, app.cache, cid, options); + + // layout helper + options.layout = function(path){ + layout = path; + }; + + // render + var str = view.fn.call(options.scope, options); + + // layout expected + if (layout) { + options.isLayout = true; + options.layout = false; + options.body = str; + this.render(layout, options, fn, view, true); + // partial return + } else if (partial) { + return str; + // render complete, and + // callback given + } else if (fn) { + fn(null, str); + // respond + } else { + this.send(str); + } +} + +/** + * Hint at view path resolution, outputting the + * paths that Express has tried. + * + * @api private + */ + +function hintAtViewPaths(view, options) { + console.error(); + console.error('failed to locate view "' + view.view + '", tried:'); + options.attempts.forEach(function(path){ + console.error(' - %s', path); + }); + console.error(); +} \ No newline at end of file diff --git a/node_modules/express/lib/view/partial.js b/node_modules/express/lib/view/partial.js new file mode 100644 index 0000000..7d2f69b --- /dev/null +++ b/node_modules/express/lib/view/partial.js @@ -0,0 +1,40 @@ + +/*! + * Express - view - Partial + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Memory cache. + */ + +var cache = {}; + +/** + * Resolve partial object name from the view path. + * + * Examples: + * + * "user.ejs" becomes "user" + * "forum thread.ejs" becomes "forumThread" + * "forum/thread/post.ejs" becomes "post" + * "blog-post.ejs" becomes "blogPost" + * + * @return {String} + * @api private + */ + +exports.resolveObjectName = function(view){ + return cache[view] || (cache[view] = view + .split('/') + .slice(-1)[0] + .split('.')[0] + .replace(/^_/, '') + .replace(/[^a-zA-Z0-9 ]+/g, ' ') + .split(/ +/).map(function(word, i){ + return i + ? word[0].toUpperCase() + word.substr(1) + : word; + }).join('')); +}; \ No newline at end of file diff --git a/node_modules/express/lib/view/view.js b/node_modules/express/lib/view/view.js new file mode 100644 index 0000000..7d9392c --- /dev/null +++ b/node_modules/express/lib/view/view.js @@ -0,0 +1,210 @@ + +/*! + * Express - View + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('path') + , utils = require('../utils') + , extname = path.extname + , dirname = path.dirname + , basename = path.basename + , fs = require('fs') + , stat = fs.statSync; + +/** + * Expose `View`. + */ + +exports = module.exports = View; + +/** + * Require cache. + */ + +var cache = {}; + +/** + * Initialize a new `View` with the given `view` path and `options`. + * + * @param {String} view + * @param {Object} options + * @api private + */ + +function View(view, options) { + options = options || {}; + this.view = view; + this.root = options.root; + this.relative = false !== options.relative; + this.defaultEngine = options.defaultEngine; + this.parent = options.parentView; + this.basename = basename(view); + this.engine = this.resolveEngine(); + this.extension = '.' + this.engine; + this.name = this.basename.replace(this.extension, ''); + this.path = this.resolvePath(); + this.dirname = dirname(this.path); + if (options.attempts) { + if (!~options.attempts.indexOf(this.path)) + options.attempts.push(this.path); + } +}; + +/** + * Check if the view path exists. + * + * @return {Boolean} + * @api public + */ + +View.prototype.__defineGetter__('exists', function(){ + try { + stat(this.path); + return true; + } catch (err) { + return false; + } +}); + +/** + * Resolve view engine. + * + * @return {String} + * @api private + */ + +View.prototype.resolveEngine = function(){ + // Explicit + if (~this.basename.indexOf('.')) return extname(this.basename).substr(1); + // Inherit from parent + if (this.parent) return this.parent.engine; + // Default + return this.defaultEngine; +}; + +/** + * Resolve view path. + * + * @return {String} + * @api private + */ + +View.prototype.resolvePath = function(){ + var path = this.view; + // Implicit engine + if (!~this.basename.indexOf('.')) path += this.extension; + // Absolute + if (utils.isAbsolute(path)) return path; + // Relative to parent + if (this.relative && this.parent) return this.parent.dirname + '/' + path; + // Relative to root + return this.root + ? this.root + '/' + path + : path; +}; + +/** + * Get view contents. This is a one-time hit, so we + * can afford to be sync. + * + * @return {String} + * @api public + */ + +View.prototype.__defineGetter__('contents', function(){ + return fs.readFileSync(this.path, 'utf8'); +}); + +/** + * Get template engine api, cache exports to reduce + * require() calls. + * + * @return {Object} + * @api public + */ + +View.prototype.__defineGetter__('templateEngine', function(){ + var ext = this.extension; + return cache[ext] || (cache[ext] = require(this.engine)); +}); + +/** + * Return root path alternative. + * + * @return {String} + * @api public + */ + +View.prototype.__defineGetter__('rootPath', function(){ + this.relative = false; + return this.resolvePath(); +}); + +/** + * Return index path alternative. + * + * @return {String} + * @api public + */ + +View.prototype.__defineGetter__('indexPath', function(){ + return this.dirname + + '/' + this.basename.replace(this.extension, '') + + '/index' + this.extension; +}); + +/** + * Return ..//index path alternative. + * + * @return {String} + * @api public + */ + +View.prototype.__defineGetter__('upIndexPath', function(){ + return this.dirname + '/../' + this.name + '/index' + this.extension; +}); + +/** + * Return _ prefix path alternative + * + * @return {String} + * @api public + */ + +View.prototype.__defineGetter__('prefixPath', function(){ + return this.dirname + '/_' + this.basename; +}); + +/** + * Register the given template engine `exports` + * as `ext`. For example we may wish to map ".html" + * files to jade: + * + * app.register('.html', require('jade')); + * + * or + * + * app.register('html', require('jade')); + * + * This is also useful for libraries that may not + * match extensions correctly. For example my haml.js + * library is installed from npm as "hamljs" so instead + * of layout.hamljs, we can register the engine as ".haml": + * + * app.register('.haml', require('haml-js')); + * + * @param {String} ext + * @param {Object} obj + * @api public + */ + +exports.register = function(ext, exports) { + if ('.' != ext[0]) ext = '.' + ext; + cache[ext] = exports; +}; diff --git a/node_modules/express/node_modules/connect/.npmignore b/node_modules/express/node_modules/connect/.npmignore new file mode 100644 index 0000000..b04a224 --- /dev/null +++ b/node_modules/express/node_modules/connect/.npmignore @@ -0,0 +1,11 @@ +*.markdown +*.md +.git* +Makefile +benchmarks/ +docs/ +examples/ +install.sh +support/ +test/ +.DS_Store diff --git a/node_modules/express/node_modules/connect/LICENSE b/node_modules/express/node_modules/connect/LICENSE new file mode 100644 index 0000000..0c5d22d --- /dev/null +++ b/node_modules/express/node_modules/connect/LICENSE @@ -0,0 +1,24 @@ +(The MIT License) + +Copyright (c) 2010 Sencha Inc. +Copyright (c) 2011 LearnBoost +Copyright (c) 2011 TJ Holowaychuk + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/index.js b/node_modules/express/node_modules/connect/index.js new file mode 100644 index 0000000..7477048 --- /dev/null +++ b/node_modules/express/node_modules/connect/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/connect'); \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/cache.js b/node_modules/express/node_modules/connect/lib/cache.js new file mode 100644 index 0000000..4aa026e --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/cache.js @@ -0,0 +1,81 @@ + +/*! + * Connect - Cache + * Copyright(c) 2011 Sencha Inc. + * MIT Licensed + */ + +/** + * Expose `Cache`. + */ + +module.exports = Cache; + +/** + * LRU cache store. + * + * @param {Number} limit + * @api private + */ + +function Cache(limit) { + this.store = {}; + this.keys = []; + this.limit = limit; +} + +/** + * Touch `key`, promoting the object. + * + * @param {String} key + * @param {Number} i + * @api private + */ + +Cache.prototype.touch = function(key, i){ + this.keys.splice(i,1); + this.keys.push(key); +}; + +/** + * Remove `key`. + * + * @param {String} key + * @api private + */ + +Cache.prototype.remove = function(key){ + delete this.store[key]; +}; + +/** + * Get the object stored for `key`. + * + * @param {String} key + * @return {Array} + * @api private + */ + +Cache.prototype.get = function(key){ + return this.store[key]; +}; + +/** + * Add a cache `key`. + * + * @param {String} key + * @return {Array} + * @api private + */ + +Cache.prototype.add = function(key){ + // initialize store + var len = this.keys.push(key); + + // limit reached, invalid LRU + if (len > this.limit) this.remove(this.keys.shift()); + + var arr = this.store[key] = []; + arr.createdAt = new Date; + return arr; +}; diff --git a/node_modules/express/node_modules/connect/lib/connect.js b/node_modules/express/node_modules/connect/lib/connect.js new file mode 100644 index 0000000..7857023 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/connect.js @@ -0,0 +1,106 @@ + +/*! + * Connect + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var HTTPServer = require('./http').Server + , HTTPSServer = require('./https').Server + , fs = require('fs'); + +// node patches + +require('./patch'); + +// expose createServer() as the module + +exports = module.exports = createServer; + +/** + * Framework version. + */ + +exports.version = '1.8.5'; + +/** + * Initialize a new `connect.HTTPServer` with the middleware + * passed to this function. When an object is passed _first_, + * we assume these are the tls options, and return a `connect.HTTPSServer`. + * + * Examples: + * + * An example HTTP server, accepting several middleware. + * + * var server = connect.createServer( + * connect.logger() + * , connect.static(__dirname + '/public') + * ); + * + * An HTTPS server, utilizing the same middleware as above. + * + * var server = connect.createServer( + * { key: key, cert: cert } + * , connect.logger() + * , connect.static(__dirname + '/public') + * ); + * + * Alternatively with connect 1.0 we may omit `createServer()`. + * + * connect( + * connect.logger() + * , connect.static(__dirname + '/public') + * ).listen(3000); + * + * @param {Object|Function} ... + * @return {Server} + * @api public + */ + +function createServer() { + if ('object' == typeof arguments[0]) { + return new HTTPSServer(arguments[0], Array.prototype.slice.call(arguments, 1)); + } else { + return new HTTPServer(Array.prototype.slice.call(arguments)); + } +}; + +// support connect.createServer() + +exports.createServer = createServer; + +// auto-load getters + +exports.middleware = {}; + +/** + * Auto-load bundled middleware with getters. + */ + +fs.readdirSync(__dirname + '/middleware').forEach(function(filename){ + if (/\.js$/.test(filename)) { + var name = filename.substr(0, filename.lastIndexOf('.')); + exports.middleware.__defineGetter__(name, function(){ + return require('./middleware/' + name); + }); + } +}); + +// expose utils + +exports.utils = require('./utils'); + +// expose getters as first-class exports + +exports.utils.merge(exports, exports.middleware); + +// expose constructors + +exports.HTTPServer = HTTPServer; +exports.HTTPSServer = HTTPSServer; + diff --git a/node_modules/express/node_modules/connect/lib/http.js b/node_modules/express/node_modules/connect/lib/http.js new file mode 100644 index 0000000..09898e2 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/http.js @@ -0,0 +1,217 @@ + +/*! + * Connect - HTTPServer + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var http = require('http') + , parse = require('url').parse + , assert = require('assert'); + +// environment + +var env = process.env.NODE_ENV || 'development'; + +/** + * Initialize a new `Server` with the given `middleware`. + * + * Examples: + * + * var server = connect.createServer( + * connect.favicon() + * , connect.logger() + * , connect.static(__dirname + '/public') + * ); + * + * @params {Array} middleware + * @return {Server} + * @api public + */ + +var Server = exports.Server = function HTTPServer(middleware) { + this.stack = []; + middleware.forEach(function(fn){ + this.use(fn); + }, this); + http.Server.call(this, this.handle); +}; + +/** + * Inherit from `http.Server.prototype`. + */ + +Server.prototype.__proto__ = http.Server.prototype; + +/** + * Utilize the given middleware `handle` to the given `route`, + * defaulting to _/_. This "route" is the mount-point for the + * middleware, when given a value other than _/_ the middleware + * is only effective when that segment is present in the request's + * pathname. + * + * For example if we were to mount a function at _/admin_, it would + * be invoked on _/admin_, and _/admin/settings_, however it would + * not be invoked for _/_, or _/posts_. + * + * This is effectively the same as passing middleware to `connect.createServer()`, + * however provides a progressive api. + * + * Examples: + * + * var server = connect.createServer(); + * server.use(connect.favicon()); + * server.use(connect.logger()); + * server.use(connect.static(__dirname + '/public')); + * + * If we wanted to prefix static files with _/public_, we could + * "mount" the `static()` middleware: + * + * server.use('/public', connect.static(__dirname + '/public')); + * + * This api is chainable, meaning the following is valid: + * + * connect.createServer() + * .use(connect.favicon()) + * .use(connect.logger()) + * .use(connect.static(__dirname + '/public')) + * .listen(3000); + * + * @param {String|Function} route or handle + * @param {Function} handle + * @return {Server} + * @api public + */ + +Server.prototype.use = function(route, handle){ + this.route = '/'; + + // default route to '/' + if ('string' != typeof route) { + handle = route; + route = '/'; + } + + // wrap sub-apps + if ('function' == typeof handle.handle) { + var server = handle; + server.route = route; + handle = function(req, res, next) { + server.handle(req, res, next); + }; + } + + // wrap vanilla http.Servers + if (handle instanceof http.Server) { + handle = handle.listeners('request')[0]; + } + + // normalize route to not trail with slash + if ('/' == route[route.length - 1]) { + route = route.substr(0, route.length - 1); + } + + // add the middleware + this.stack.push({ route: route, handle: handle }); + + // allow chaining + return this; +}; + +/** + * Handle server requests, punting them down + * the middleware stack. + * + * @api private + */ + +Server.prototype.handle = function(req, res, out) { + var writeHead = res.writeHead + , stack = this.stack + , removed = '' + , index = 0; + + function next(err) { + var layer, path, c; + req.url = removed + req.url; + req.originalUrl = req.originalUrl || req.url; + removed = ''; + + layer = stack[index++]; + + // all done + if (!layer || res.headerSent) { + // but wait! we have a parent + if (out) return out(err); + + // error + if (err) { + var msg = 'production' == env + ? 'Internal Server Error' + : err.stack || err.toString(); + + // output to stderr in a non-test env + if ('test' != env) console.error(err.stack || err.toString()); + + // unable to respond + if (res.headerSent) return req.socket.destroy(); + + res.statusCode = 500; + res.setHeader('Content-Type', 'text/plain'); + if ('HEAD' == req.method) return res.end(); + res.end(msg); + } else { + res.statusCode = 404; + res.setHeader('Content-Type', 'text/plain'); + if ('HEAD' == req.method) return res.end(); + res.end('Cannot ' + req.method + ' ' + req.url); + } + return; + } + + try { + path = parse(req.url).pathname; + if (undefined == path) path = '/'; + + // skip this layer if the route doesn't match. + if (0 != path.indexOf(layer.route)) return next(err); + + c = path[layer.route.length]; + if (c && '/' != c && '.' != c) return next(err); + + // Call the layer handler + // Trim off the part of the url that matches the route + removed = layer.route; + req.url = req.url.substr(removed.length); + + // Ensure leading slash + if ('/' != req.url[0]) req.url = '/' + req.url; + + var arity = layer.handle.length; + if (err) { + if (arity === 4) { + layer.handle(err, req, res, next); + } else { + next(err); + } + } else if (arity < 4) { + layer.handle(req, res, next); + } else { + next(); + } + } catch (e) { + if (e instanceof assert.AssertionError) { + console.error(e.stack + '\n'); + next(e); + } else { + next(e); + } + } + } + next(); +}; \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/https.js b/node_modules/express/node_modules/connect/lib/https.js new file mode 100644 index 0000000..9b09fa4 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/https.js @@ -0,0 +1,47 @@ + +/*! + * Connect - HTTPServer + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var HTTPServer = require('./http').Server + , https = require('https'); + +/** + * Initialize a new `Server` with the given + *`options` and `middleware`. The HTTPS api + * is identical to the [HTTP](http.html) server, + * however TLS `options` must be provided before + * passing in the optional middleware. + * + * @params {Object} options + * @params {Array} middleawre + * @return {Server} + * @api public + */ + +var Server = exports.Server = function HTTPSServer(options, middleware) { + this.stack = []; + middleware.forEach(function(fn){ + this.use(fn); + }, this); + https.Server.call(this, options, this.handle); +}; + +/** + * Inherit from `http.Server.prototype`. + */ + +Server.prototype.__proto__ = https.Server.prototype; + +// mixin HTTPServer methods + +Object.keys(HTTPServer.prototype).forEach(function(method){ + Server.prototype[method] = HTTPServer.prototype[method]; +}); \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/index.js b/node_modules/express/node_modules/connect/lib/index.js new file mode 100644 index 0000000..77b14c3 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/index.js @@ -0,0 +1,46 @@ + +/** + * # Connect + * + * Connect is a middleware framework for node, + * shipping with over 11 bundled middleware and a rich choice of + * [3rd-party middleware](https://github.com/senchalabs/connect/wiki). + * + * Installation: + * + * $ npm install connect + * + * API: + * + * - [connect](connect.html) general + * - [http](http.html) http server + * - [https](https.html) https server + * + * Middleware: + * + * - [logger](middleware-logger.html) request logger with custom format support + * - [csrf](middleware-csrf.html) Cross-site request forgery protection + * - [basicAuth](middleware-basicAuth.html) basic http authentication + * - [bodyParser](middleware-bodyParser.html) extensible request body parser + * - [cookieParser](middleware-cookieParser.html) cookie parser + * - [session](middleware-session.html) session management support with bundled [MemoryStore](middleware-session-memory.html) + * - [compiler](middleware-compiler.html) static asset compiler (sass, less, coffee-script, etc) + * - [methodOverride](middleware-methodOverride.html) faux HTTP method support + * - [responseTime](middleware-responseTime.html) calculates response-time and exposes via X-Response-Time + * - [router](middleware-router.html) provides rich Sinatra / Express-like routing + * - [staticCache](middleware-staticCache.html) memory cache layer for the static() middleware + * - [static](middleware-static.html) streaming static file server supporting `Range` and more + * - [directory](middleware-directory.html) directory listing middleware + * - [vhost](middleware-vhost.html) virtual host sub-domain mapping middleware + * - [favicon](middleware-favicon.html) efficient favicon server (with default icon) + * - [limit](middleware-limit.html) limit the bytesize of request bodies + * - [profiler](middleware-profiler.html) request profiler reporting response-time, memory usage, etc + * - [query](middleware-query.html) automatic querystring parser, populating `req.query` + * - [errorHandler](middleware-errorHandler.html) flexible error handler + * + * Internals: + * + * - connect [utilities](utils.html) + * - node monkey [patches](patch.html) + * + */ \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/basicAuth.js b/node_modules/express/node_modules/connect/lib/middleware/basicAuth.js new file mode 100644 index 0000000..3ff472b --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/basicAuth.js @@ -0,0 +1,93 @@ + +/*! + * Connect - basicAuth + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var utils = require('../utils') + , unauthorized = utils.unauthorized + , badRequest = utils.badRequest; + +/** + * Enfore basic authentication by providing a `callback(user, pass)`, + * which must return `true` in order to gain access. Alternatively an async + * method is provided as well, invoking `callback(user, pass, callback)`. Populates + * `req.remoteUser`. The final alternative is simply passing username / password + * strings. + * + * Examples: + * + * connect(connect.basicAuth('username', 'password')); + * + * connect( + * connect.basicAuth(function(user, pass){ + * return 'tj' == user & 'wahoo' == pass; + * }) + * ); + * + * connect( + * connect.basicAuth(function(user, pass, fn){ + * User.authenticate({ user: user, pass: pass }, fn); + * }) + * ); + * + * @param {Function|String} callback or username + * @param {String} realm + * @api public + */ + +module.exports = function basicAuth(callback, realm) { + var username, password; + + // user / pass strings + if ('string' == typeof callback) { + username = callback; + password = realm; + if ('string' != typeof password) throw new Error('password argument required'); + realm = arguments[2]; + callback = function(user, pass){ + return user == username && pass == password; + } + } + + realm = realm || 'Authorization Required'; + + return function(req, res, next) { + var authorization = req.headers.authorization; + + if (req.remoteUser) return next(); + if (!authorization) return unauthorized(res, realm); + + var parts = authorization.split(' ') + , scheme = parts[0] + , credentials = new Buffer(parts[1], 'base64').toString().split(':'); + + if ('Basic' != scheme) return badRequest(res); + + // async + if (callback.length >= 3) { + var pause = utils.pause(req); + callback(credentials[0], credentials[1], function(err, user){ + if (err || !user) return unauthorized(res, realm); + req.remoteUser = user; + next(); + pause.resume(); + }); + // sync + } else { + if (callback(credentials[0], credentials[1])) { + req.remoteUser = credentials[0]; + next(); + } else { + unauthorized(res, realm); + } + } + } +}; + diff --git a/node_modules/express/node_modules/connect/lib/middleware/bodyParser.js b/node_modules/express/node_modules/connect/lib/middleware/bodyParser.js new file mode 100644 index 0000000..a52568c --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/bodyParser.js @@ -0,0 +1,196 @@ + +/*! + * Connect - bodyParser + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var qs = require('qs') + , formidable = require('formidable'); + +/** + * Extract the mime type from the given request's + * _Content-Type_ header. + * + * @param {IncomingMessage} req + * @return {String} + * @api private + */ + +function mime(req) { + var str = req.headers['content-type'] || ''; + return str.split(';')[0]; +} + +/** + * Parse request bodies. + * + * By default _application/json_, _application/x-www-form-urlencoded_, + * and _multipart/form-data_ are supported, however you may map `connect.bodyParser.parse[contentType]` + * to a function receiving `(req, options, callback)`. + * + * Examples: + * + * connect.createServer( + * connect.bodyParser() + * , function(req, res) { + * res.end('viewing user ' + req.body.user.name); + * } + * ); + * + * $ curl -d 'user[name]=tj' http://localhost/ + * $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://localhost/ + * + * Multipart req.files: + * + * As a security measure files are stored in a separate object, stored + * as `req.files`. This prevents attacks that may potentially alter + * filenames, and depending on the application gain access to restricted files. + * + * Multipart configuration: + * + * The `options` passed are provided to each parser function. + * The _multipart/form-data_ parser merges these with formidable's + * IncomingForm object, allowing you to tweak the upload directory, + * size limits, etc. For example you may wish to retain the file extension + * and change the upload directory: + * + * server.use(bodyParser({ uploadDir: '/www/mysite.com/uploads' })); + * + * View [node-formidable](https://github.com/felixge/node-formidable) for more information. + * + * If you wish to use formidable directly within your app, and do not + * desire this behaviour for multipart requests simply remove the + * parser: + * + * delete connect.bodyParser.parse['multipart/form-data']; + * + * Or + * + * delete express.bodyParser.parse['multipart/form-data']; + * + * @param {Object} options + * @return {Function} + * @api public + */ + +exports = module.exports = function bodyParser(options){ + options = options || {}; + return function bodyParser(req, res, next) { + if (req.body) return next(); + req.body = {}; + + if ('GET' == req.method || 'HEAD' == req.method) return next(); + var parser = exports.parse[mime(req)]; + if (parser) { + parser(req, options, next); + } else { + next(); + } + } +}; + +/** + * Parsers. + */ + +exports.parse = {}; + +/** + * Parse application/x-www-form-urlencoded. + */ + +exports.parse['application/x-www-form-urlencoded'] = function(req, options, fn){ + var buf = ''; + req.setEncoding('utf8'); + req.on('data', function(chunk){ buf += chunk }); + req.on('end', function(){ + try { + req.body = buf.length + ? qs.parse(buf) + : {}; + fn(); + } catch (err){ + fn(err); + } + }); +}; + +/** + * Parse application/json. + */ + +exports.parse['application/json'] = function(req, options, fn){ + var buf = ''; + req.setEncoding('utf8'); + req.on('data', function(chunk){ buf += chunk }); + req.on('end', function(){ + try { + req.body = buf.length + ? JSON.parse(buf) + : {}; + fn(); + } catch (err){ + fn(err); + } + }); +}; + +/** + * Parse multipart/form-data. + * + * TODO: make multiple support optional + * TODO: revisit "error" flag if it's a formidable bug + */ + +exports.parse['multipart/form-data'] = function(req, options, fn){ + var form = new formidable.IncomingForm + , data = {} + , files = {} + , done; + + Object.keys(options).forEach(function(key){ + form[key] = options[key]; + }); + + function ondata(name, val, data){ + if (Array.isArray(data[name])) { + data[name].push(val); + } else if (data[name]) { + data[name] = [data[name], val]; + } else { + data[name] = val; + } + } + + form.on('field', function(name, val){ + ondata(name, val, data); + }); + + form.on('file', function(name, val){ + ondata(name, val, files); + }); + + form.on('error', function(err){ + fn(err); + done = true; + }); + + form.on('end', function(){ + if (done) return; + try { + req.body = qs.parse(data); + req.files = qs.parse(files); + fn(); + } catch (err) { + fn(err); + } + }); + + form.parse(req); +}; \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/compiler.js b/node_modules/express/node_modules/connect/lib/middleware/compiler.js new file mode 100644 index 0000000..dc4dd66 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/compiler.js @@ -0,0 +1,163 @@ + +/*! + * Connect - compiler + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var fs = require('fs') + , path = require('path') + , parse = require('url').parse; + +/** + * Require cache. + */ + +var cache = {}; + +/** + * Setup compiler. + * + * Options: + * + * - `src` Source directory, defaults to **CWD**. + * - `dest` Destination directory, defaults `src`. + * - `enable` Array of enabled compilers. + * + * Compilers: + * + * - `sass` Compiles sass to css + * - `less` Compiles less to css + * - `coffeescript` Compiles coffee to js + * + * @param {Object} options + * @api public + */ + +exports = module.exports = function compiler(options){ + options = options || {}; + + var srcDir = options.src || process.cwd() + , destDir = options.dest || srcDir + , enable = options.enable; + + if (!enable || enable.length === 0) { + throw new Error('compiler\'s "enable" option is not set, nothing will be compiled.'); + } + + return function compiler(req, res, next){ + if ('GET' != req.method) return next(); + var pathname = parse(req.url).pathname; + for (var i = 0, len = enable.length; i < len; ++i) { + var name = enable[i] + , compiler = compilers[name]; + if (compiler.match.test(pathname)) { + var src = (srcDir + pathname).replace(compiler.match, compiler.ext) + , dest = destDir + pathname; + + // Compare mtimes + fs.stat(src, function(err, srcStats){ + if (err) { + if ('ENOENT' == err.code) { + next(); + } else { + next(err); + } + } else { + fs.stat(dest, function(err, destStats){ + if (err) { + // Oh snap! it does not exist, compile it + if ('ENOENT' == err.code) { + compile(); + } else { + next(err); + } + } else { + // Source has changed, compile it + if (srcStats.mtime > destStats.mtime) { + compile(); + } else { + // Defer file serving + next(); + } + } + }); + } + }); + + // Compile to the destination + function compile() { + fs.readFile(src, 'utf8', function(err, str){ + if (err) { + next(err); + } else { + compiler.compile(str, function(err, str){ + if (err) { + next(err); + } else { + fs.writeFile(dest, str, 'utf8', function(err){ + next(err); + }); + } + }); + } + }); + } + return; + } + } + next(); + }; +}; + +/** + * Bundled compilers: + * + * - [sass](http://github.com/visionmedia/sass.js) to _css_ + * - [less](http://github.com/cloudhead/less.js) to _css_ + * - [coffee](http://github.com/jashkenas/coffee-script) to _js_ + */ + +var compilers = exports.compilers = { + sass: { + match: /\.css$/, + ext: '.sass', + compile: function(str, fn){ + var sass = cache.sass || (cache.sass = require('sass')); + try { + fn(null, sass.render(str)); + } catch (err) { + fn(err); + } + } + }, + less: { + match: /\.css$/, + ext: '.less', + compile: function(str, fn){ + var less = cache.less || (cache.less = require('less')); + try { + less.render(str, fn); + } catch (err) { + fn(err); + } + } + }, + coffeescript: { + match: /\.js$/, + ext: '.coffee', + compile: function(str, fn){ + var coffee = cache.coffee || (cache.coffee = require('coffee-script')); + try { + fn(null, coffee.compile(str)); + } catch (err) { + fn(err); + } + } + } +}; \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/cookieParser.js b/node_modules/express/node_modules/connect/lib/middleware/cookieParser.js new file mode 100644 index 0000000..d6b69de --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/cookieParser.js @@ -0,0 +1,46 @@ + +/*! + * Connect - cookieParser + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var utils = require('./../utils'); + +/** + * Parse _Cookie_ header and populate `req.cookies` + * with an object keyed by the cookie names. + * + * Examples: + * + * connect.createServer( + * connect.cookieParser() + * , function(req, res, next){ + * res.end(JSON.stringify(req.cookies)); + * } + * ); + * + * @return {Function} + * @api public + */ + +module.exports = function cookieParser(){ + return function cookieParser(req, res, next) { + var cookie = req.headers.cookie; + if (req.cookies) return next(); + req.cookies = {}; + if (cookie) { + try { + req.cookies = utils.parseCookie(cookie); + } catch (err) { + return next(err); + } + } + next(); + }; +}; \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/csrf.js b/node_modules/express/node_modules/connect/lib/middleware/csrf.js new file mode 100644 index 0000000..1dcf0d1 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/csrf.js @@ -0,0 +1,105 @@ + +/*! + * Connect - csrf + * Copyright(c) 2011 Sencha Inc. + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var utils = require('../utils') + , crypto = require('crypto'); + +/** + * CRSF protection middleware. + * + * By default this middleware generates a token named "_csrf" + * which should be added to requests which mutate + * state, within a hidden form field, query-string etc. This + * token is validated against the visitor's `req.session._csrf` + * property which is re-generated per request. + * + * The default `value` function checks `req.body` generated + * by the `bodyParser()` middleware, `req.query` generated + * by `query()`, and the "X-CSRF-Token" header field. + * + * This middleware requires session support, thus should be added + * somewhere _below_ `session()` and `cookieParser()`. + * + * Examples: + * + * var form = '\n\ + *
\n\ + * \n\ + * \n\ + * \n\ + * \n\ + *
\n\ + * '; + * + * connect( + * connect.cookieParser() + * , connect.session({ secret: 'keyboard cat' }) + * , connect.bodyParser() + * , connect.csrf() + * + * , function(req, res, next){ + * if ('POST' != req.method) return next(); + * req.session.user = req.body.user; + * next(); + * } + * + * , function(req, res){ + * res.setHeader('Content-Type', 'text/html'); + * var body = form + * .replace('{token}', req.session._csrf) + * .replace('{user}', req.session.user && req.session.user.name || ''); + * res.end(body); + * } + * ).listen(3000); + * + * Options: + * + * - `value` a function accepting the request, returning the token + * + * @param {Object} options + * @api public + */ + +module.exports = function csrf(options) { + var options = options || {} + , value = options.value || defaultValue; + + return function(req, res, next){ + // generate CSRF token + var token = req.session._csrf || (req.session._csrf = utils.uid(24)); + + // ignore GET (for now) + if ('GET' == req.method) return next(); + + // determine value + var val = value(req); + + // check + if (val != token) return utils.forbidden(res); + + next(); + } +}; + +/** + * Default value function, checking the `req.body` + * and `req.query` for the CSRF token. + * + * @param {IncomingMessage} req + * @return {String} + * @api private + */ + +function defaultValue(req) { + return (req.body && req.body._csrf) + || (req.query && req.query._csrf) + || (req.headers['x-csrf-token']); +} \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/directory.js b/node_modules/express/node_modules/connect/lib/middleware/directory.js new file mode 100644 index 0000000..df5b5e6 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/directory.js @@ -0,0 +1,222 @@ + +/*! + * Connect - directory + * Copyright(c) 2011 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +// TODO: icon / style for directories +// TODO: arrow key navigation +// TODO: make icons extensible + +/** + * Module dependencies. + */ + +var fs = require('fs') + , parse = require('url').parse + , utils = require('../utils') + , path = require('path') + , normalize = path.normalize + , extname = path.extname + , join = path.join; + +/** + * Icon cache. + */ + +var cache = {}; + +/** + * Serve directory listings with the given `root` path. + * + * Options: + * + * - `hidden` display hidden (dot) files. Defaults to false. + * - `icons` display icons. Defaults to false. + * - `filter` Apply this filter function to files. Defaults to false. + * + * @param {String} root + * @param {Object} options + * @return {Function} + * @api public + */ + +exports = module.exports = function directory(root, options){ + options = options || {}; + + // root required + if (!root) throw new Error('directory() root path required'); + var hidden = options.hidden + , icons = options.icons + , filter = options.filter + , root = normalize(root); + + return function directory(req, res, next) { + var accept = req.headers.accept || 'text/plain' + , url = parse(req.url) + , dir = decodeURIComponent(url.pathname) + , path = normalize(join(root, dir)) + , originalUrl = parse(req.originalUrl) + , originalDir = decodeURIComponent(originalUrl.pathname) + , showUp = path != root && path != root + '/'; + + // null byte(s) + if (~path.indexOf('\0')) return utils.badRequest(res); + + // malicious path + if (0 != path.indexOf(root)) return utils.forbidden(res); + + // check if we have a directory + fs.stat(path, function(err, stat){ + if (err) return 'ENOENT' == err.code + ? next() + : next(err); + + if (!stat.isDirectory()) return next(); + + // fetch files + fs.readdir(path, function(err, files){ + if (err) return next(err); + if (!hidden) files = removeHidden(files); + if (filter) files = files.filter(filter); + files.sort(); + // content-negotiation + for (var key in exports) { + if (~accept.indexOf(key) || ~accept.indexOf('*/*')) { + exports[key](req, res, files, next, originalDir, showUp, icons); + return; + } + } + utils.notAcceptable(res); + }); + }); + }; +}; + +/** + * Respond with text/html. + */ + +exports.html = function(req, res, files, next, dir, showUp, icons){ + fs.readFile(__dirname + '/../public/directory.html', 'utf8', function(err, str){ + if (err) return next(err); + fs.readFile(__dirname + '/../public/style.css', 'utf8', function(err, style){ + if (err) return next(err); + if (showUp) files.unshift('..'); + str = str + .replace('{style}', style) + .replace('{files}', html(files, dir, icons)) + .replace('{directory}', dir) + .replace('{linked-path}', htmlPath(dir)); + res.setHeader('Content-Type', 'text/html'); + res.setHeader('Content-Length', str.length); + res.end(str); + }); + }); +}; + +/** + * Respond with application/json. + */ + +exports.json = function(req, res, files){ + files = JSON.stringify(files); + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Content-Length', files.length); + res.end(files); +}; + +/** + * Respond with text/plain. + */ + +exports.plain = function(req, res, files){ + files = files.join('\n') + '\n'; + res.setHeader('Content-Type', 'text/plain'); + res.setHeader('Content-Length', files.length); + res.end(files); +}; + +/** + * Map html `dir`, returning a linked path. + */ + +function htmlPath(dir) { + var curr = []; + return dir.split('/').map(function(part){ + curr.push(part); + return '' + part + ''; + }).join(' / '); +} + +/** + * Map html `files`, returning an html unordered list. + */ + +function html(files, dir, useIcons) { + return '
    ' + files.map(function(file){ + var icon = '' + , classes = []; + + if (useIcons && '..' != file) { + icon = icons[extname(file)] || icons.default; + icon = ''; + classes.push('icon'); + } + + return '
  • ' + + icon + file + '
  • '; + + }).join('\n') + '
'; +} + +/** + * Load and cache the given `icon`. + * + * @param {String} icon + * @return {String} + * @api private + */ + +function load(icon) { + if (cache[icon]) return cache[icon]; + return cache[icon] = fs.readFileSync(__dirname + '/../public/icons/' + icon, 'base64'); +} + +/** + * Filter "hidden" `files`, aka files + * beginning with a `.`. + * + * @param {Array} files + * @return {Array} + * @api private + */ + +function removeHidden(files) { + return files.filter(function(file){ + return '.' != file[0]; + }); +} + +/** + * Icon map. + */ + +var icons = { + '.js': 'page_white_code_red.png' + , '.c': 'page_white_c.png' + , '.h': 'page_white_h.png' + , '.cc': 'page_white_cplusplus.png' + , '.php': 'page_white_php.png' + , '.rb': 'page_white_ruby.png' + , '.cpp': 'page_white_cplusplus.png' + , '.swf': 'page_white_flash.png' + , '.pdf': 'page_white_acrobat.png' + , 'default': 'page_white.png' +}; diff --git a/node_modules/express/node_modules/connect/lib/middleware/errorHandler.js b/node_modules/express/node_modules/connect/lib/middleware/errorHandler.js new file mode 100644 index 0000000..f2fc44f --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/errorHandler.js @@ -0,0 +1,100 @@ +/*! + * Connect - errorHandler + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var utils = require('../utils') + , url = require('url') + , fs = require('fs'); + +/** + * Flexible error handler, providing (_optional_) stack traces + * and error message responses for requests accepting text, html, + * or json. + * + * Options: + * + * - `showStack`, `stack` respond with both the error message and stack trace. Defaults to `false` + * - `showMessage`, `message`, respond with the exception message only. Defaults to `false` + * - `dumpExceptions`, `dump`, dump exceptions to stderr (without terminating the process). Defaults to `false` + * + * Text: + * + * By default, and when _text/plain_ is accepted a simple stack trace + * or error message will be returned. + * + * JSON: + * + * When _application/json_ is accepted, connect will respond with + * an object in the form of `{ "error": error }`. + * + * HTML: + * + * When accepted connect will output a nice html stack trace. + * + * @param {Object} options + * @return {Function} + * @api public + */ + +exports = module.exports = function errorHandler(options){ + options = options || {}; + + // defaults + var showStack = options.showStack || options.stack + , showMessage = options.showMessage || options.message + , dumpExceptions = options.dumpExceptions || options.dump + , formatUrl = options.formatUrl; + + return function errorHandler(err, req, res, next){ + res.statusCode = 500; + if (dumpExceptions) console.error(err.stack); + if (showStack) { + var accept = req.headers.accept || ''; + // html + if (~accept.indexOf('html')) { + fs.readFile(__dirname + '/../public/style.css', 'utf8', function(e, style){ + fs.readFile(__dirname + '/../public/error.html', 'utf8', function(e, html){ + var stack = (err.stack || '') + .split('\n').slice(1) + .map(function(v){ return '
  • ' + v + '
  • '; }).join(''); + html = html + .replace('{style}', style) + .replace('{stack}', stack) + .replace('{title}', exports.title) + .replace(/\{error\}/g, utils.escape(err.toString())); + res.setHeader('Content-Type', 'text/html'); + res.end(html); + }); + }); + // json + } else if (~accept.indexOf('json')) { + var json = JSON.stringify({ error: err }); + res.setHeader('Content-Type', 'application/json'); + res.end(json); + // plain text + } else { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end(err.stack); + } + } else { + var body = showMessage + ? err.toString() + : 'Internal Server Error'; + res.setHeader('Content-Type', 'text/plain'); + res.end(body); + } + }; +}; + +/** + * Template title. + */ + +exports.title = 'Connect'; \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/favicon.js b/node_modules/express/node_modules/connect/lib/middleware/favicon.js new file mode 100644 index 0000000..8eeafba --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/favicon.js @@ -0,0 +1,76 @@ + +/*! + * Connect - favicon + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var fs = require('fs') + , utils = require('../utils'); + +/** + * Favicon cache. + */ + +var icon; + +/** + * By default serves the connect favicon, or the favicon + * located by the given `path`. + * + * Options: + * + * - `maxAge` cache-control max-age directive, defaulting to 1 day + * + * Examples: + * + * connect.createServer( + * connect.favicon() + * ); + * + * connect.createServer( + * connect.favicon(__dirname + '/public/favicon.ico') + * ); + * + * @param {String} path + * @param {Object} options + * @return {Function} + * @api public + */ + +module.exports = function favicon(path, options){ + var options = options || {} + , path = path || __dirname + '/../public/favicon.ico' + , maxAge = options.maxAge || 86400000; + + return function favicon(req, res, next){ + if ('/favicon.ico' == req.url) { + if (icon) { + res.writeHead(200, icon.headers); + res.end(icon.body); + } else { + fs.readFile(path, function(err, buf){ + if (err) return next(err); + icon = { + headers: { + 'Content-Type': 'image/x-icon' + , 'Content-Length': buf.length + , 'ETag': '"' + utils.md5(buf) + '"' + , 'Cache-Control': 'public, max-age=' + (maxAge / 1000) + }, + body: buf + }; + res.writeHead(200, icon.headers); + res.end(icon.body); + }); + } + } else { + next(); + } + }; +}; \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/limit.js b/node_modules/express/node_modules/connect/lib/middleware/limit.js new file mode 100644 index 0000000..4dafdd3 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/limit.js @@ -0,0 +1,82 @@ + +/*! + * Connect - limit + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Limit request bodies to the given size in `bytes`. + * + * A string representation of the bytesize may also be passed, + * for example "5mb", "200kb", "1gb", etc. + * + * Examples: + * + * var server = connect( + * connect.limit('5.5mb') + * ).listen(3000); + * + * TODO: pause EV_READ + * + * @param {Number|String} bytes + * @return {Function} + * @api public + */ + +module.exports = function limit(bytes){ + if ('string' == typeof bytes) bytes = parse(bytes); + if ('number' != typeof bytes) throw new Error('limit() bytes required'); + return function limit(req, res, next){ + var received = 0 + , len = req.headers['content-length'] + ? parseInt(req.headers['content-length'], 10) + : null; + + // deny the request + function deny() { + req.destroy(); + } + + // self-awareness + if (req._limit) return next(); + req._limit = true; + + // limit by content-length + if (len && len > bytes) { + res.statusCode = 413; + res.end('Request Entity Too Large'); + return; + } + + // limit + req.on('data', function(chunk){ + received += chunk.length; + if (received > bytes) deny(); + }); + + next(); + }; +}; + +/** + * Parse byte `size` string. + * + * @param {String} size + * @return {Number} + * @api private + */ + +function parse(size) { + var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/) + , n = parseFloat(parts[1]) + , type = parts[2]; + + var map = { + kb: 1024 + , mb: 1024 * 1024 + , gb: 1024 * 1024 * 1024 + }; + + return map[type] * n; +} \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/logger.js b/node_modules/express/node_modules/connect/lib/middleware/logger.js new file mode 100644 index 0000000..75cc5aa --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/logger.js @@ -0,0 +1,299 @@ + +/*! + * Connect - logger + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Log buffer. + */ + +var buf = []; + +/** + * Default log buffer duration. + */ + +var defaultBufferDuration = 1000; + +/** + * Log requests with the given `options` or a `format` string. + * + * Options: + * + * - `format` Format string, see below for tokens + * - `stream` Output stream, defaults to _stdout_ + * - `buffer` Buffer duration, defaults to 1000ms when _true_ + * - `immediate` Write log line on request instead of response (for response times) + * + * Tokens: + * + * - `:req[header]` ex: `:req[Accept]` + * - `:res[header]` ex: `:res[Content-Length]` + * - `:http-version` + * - `:response-time` + * - `:remote-addr` + * - `:date` + * - `:method` + * - `:url` + * - `:referrer` + * - `:user-agent` + * - `:status` + * + * Formats: + * + * Pre-defined formats that ship with connect: + * + * - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"' + * - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms' + * - `tiny` ':method :url :status :res[content-length] - :response-time ms' + * - `dev` concise output colored by response status for development use + * + * Examples: + * + * connect.logger() // default + * connect.logger('short') + * connect.logger('tiny') + * connect.logger('dev') + * connect.logger(':method :url - :referrer') + * connect.logger(':req[content-type] -> :res[content-type]') + * connect.logger(function(req, res){ return 'some format string' }) + * + * Defining Tokens: + * + * To define a token, simply invoke `connect.logger.token()` with the + * name and a callback function. The value returned is then available + * as ":type" in this case. + * + * connect.logger.token('type', function(req, res){ return req.headers['content-type']; }) + * + * Defining Formats: + * + * All default formats are defined this way, however it's public API as well: + * + * connect.logger.format('name', 'string or function') + * + * @param {String|Function|Object} format or options + * @return {Function} + * @api public + */ + +exports = module.exports = function logger(options) { + if ('object' == typeof options) { + options = options || {}; + } else if (options) { + options = { format: options }; + } else { + options = {}; + } + + // output on request instead of response + var immediate = options.immediate; + + // format name + var fmt = exports[options.format] || options.format || exports.default; + + // compile format + if ('function' != typeof fmt) fmt = compile(fmt); + + // options + var stream = options.stream || process.stdout + , buffer = options.buffer; + + // buffering support + if (buffer) { + var realStream = stream + , interval = 'number' == typeof buffer + ? buffer + : defaultBufferDuration; + + // flush interval + setInterval(function(){ + if (buf.length) { + realStream.write(buf.join(''), 'ascii'); + buf.length = 0; + } + }, interval); + + // swap the stream + stream = { + write: function(str){ + buf.push(str); + } + }; + } + + return function logger(req, res, next) { + req._startTime = new Date; + + // mount safety + if (req._logging) return next(); + + // flag as logging + req._logging = true; + + // immediate + if (immediate) { + var line = fmt(exports, req, res); + if (null == line) return; + stream.write(line + '\n', 'ascii'); + } else { + // proxy end to output loggging + var end = res.end; + res.end = function(chunk, encoding){ + res.end = end; + res.end(chunk, encoding); + var line = fmt(exports, req, res); + if (null == line) return; + stream.write(line + '\n', 'ascii'); + }; + } + + + next(); + }; +}; + +/** + * Compile `fmt` into a function. + * + * @param {String} fmt + * @return {Function} + * @api private + */ + +function compile(fmt) { + fmt = fmt.replace(/"/g, '\\"'); + var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){ + return '"\n + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "'; + }) + '";' + return new Function('tokens, req, res', js); +}; + +/** + * Define a token function with the given `name`, + * and callback `fn(req, res)`. + * + * @param {String} name + * @param {Function} fn + * @return {Object} exports for chaining + * @api public + */ + +exports.token = function(name, fn) { + exports[name] = fn; + return this; +}; + +/** + * Define a `fmt` with the given `name`. + * + * @param {String} name + * @param {String|Function} fmt + * @return {Object} exports for chaining + * @api public + */ + +exports.format = function(name, str){ + exports[name] = str; + return this; +}; + +// default format + +exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'); + +// short format + +exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'); + +// tiny format + +exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms'); + +// dev (colored) + +exports.format('dev', function(tokens, req, res){ + var status = res.statusCode + , color = 32; + + if (status >= 500) color = 31 + else if (status >= 400) color = 33 + else if (status >= 300) color = 36; + + return '\033[90m' + req.method + + ' ' + req.originalUrl + ' ' + + '\033[' + color + 'm' + res.statusCode + + ' \033[90m' + + (new Date - req._startTime) + + 'ms\033[0m'; +}); + +// request url + +exports.token('url', function(req){ + return req.originalUrl; +}); + +// request method + +exports.token('method', function(req){ + return req.method; +}); + +// response time in milliseconds + +exports.token('response-time', function(req){ + return new Date - req._startTime; +}); + +// UTC date + +exports.token('date', function(){ + return new Date().toUTCString(); +}); + +// response status code + +exports.token('status', function(req, res){ + return res.statusCode; +}); + +// normalized referrer + +exports.token('referrer', function(req){ + return req.headers['referer'] || req.headers['referrer']; +}); + +// remote address + +exports.token('remote-addr', function(req){ + return req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress)); +}); + +// HTTP version + +exports.token('http-version', function(req){ + return req.httpVersionMajor + '.' + req.httpVersionMinor; +}); + +// UA string + +exports.token('user-agent', function(req){ + return req.headers['user-agent']; +}); + +// request header + +exports.token('req', function(req, res, field){ + return req.headers[field.toLowerCase()]; +}); + +// response header + +exports.token('res', function(req, res, field){ + return (res._headers || {})[field.toLowerCase()]; +}); + diff --git a/node_modules/express/node_modules/connect/lib/middleware/methodOverride.js b/node_modules/express/node_modules/connect/lib/middleware/methodOverride.js new file mode 100644 index 0000000..db4e9f3 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/methodOverride.js @@ -0,0 +1,38 @@ + +/*! + * Connect - methodOverride + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Provides faux HTTP method support. + * + * Pass an optional `key` to use when checking for + * a method override, othewise defaults to _\_method_. + * The original method is available via `req.originalMethod`. + * + * @param {String} key + * @return {Function} + * @api public + */ + +module.exports = function methodOverride(key){ + key = key || "_method"; + return function methodOverride(req, res, next) { + req.originalMethod = req.originalMethod || req.method; + + // req.body + if (req.body && key in req.body) { + req.method = req.body[key].toUpperCase(); + delete req.body[key]; + // check X-HTTP-Method-Override + } else if (req.headers['x-http-method-override']) { + req.method = req.headers['x-http-method-override'].toUpperCase(); + } + + next(); + }; +}; + diff --git a/node_modules/express/node_modules/connect/lib/middleware/profiler.js b/node_modules/express/node_modules/connect/lib/middleware/profiler.js new file mode 100644 index 0000000..b0b5bac --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/profiler.js @@ -0,0 +1,100 @@ + +/*! + * Connect - profiler + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Profile the duration of a request. + * + * Typically this middleware should be utilized + * _above_ all others, as it proxies the `res.end()` + * method, being first allows it to encapsulate all + * other middleware. + * + * Example Output: + * + * GET / + * response time 2ms + * memory rss 52.00kb + * memory vsize 2.07mb + * heap before 3.76mb / 8.15mb + * heap after 3.80mb / 8.15mb + * + * @api public + */ + +module.exports = function profiler(){ + return function(req, res, next){ + var end = res.end + , start = snapshot(); + + // state snapshot + function snapshot() { + return { + mem: process.memoryUsage() + , time: new Date + }; + } + + // proxy res.end() + res.end = function(data, encoding){ + res.end = end; + res.end(data, encoding); + compare(req, start, snapshot()) + }; + + next(); + } +}; + +/** + * Compare `start` / `end` snapshots. + * + * @param {IncomingRequest} req + * @param {Object} start + * @param {Object} end + * @api private + */ + +function compare(req, start, end) { + console.log(); + row(req.method, req.url); + row('response time:', (end.time - start.time) + 'ms'); + row('memory rss:', formatBytes(end.mem.rss - start.mem.rss)); + row('memory vsize:', formatBytes(end.mem.vsize - start.mem.vsize)); + row('heap before:', formatBytes(start.mem.heapUsed) + ' / ' + formatBytes(start.mem.heapTotal)); + row('heap after:', formatBytes(end.mem.heapUsed) + ' / ' + formatBytes(end.mem.heapTotal)); + console.log(); +} + +/** + * Row helper + * + * @param {String} key + * @param {String} val + * @api private + */ + +function row(key, val) { + console.log(' \033[90m%s\033[0m \033[36m%s\033[0m', key, val); +} + +/** + * Format byte-size. + * + * @param {Number} bytes + * @return {String} + * @api private + */ + +function formatBytes(bytes) { + var kb = 1024 + , mb = 1024 * kb + , gb = 1024 * mb; + if (bytes < kb) return bytes + 'b'; + if (bytes < mb) return (bytes / kb).toFixed(2) + 'kb'; + if (bytes < gb) return (bytes / mb).toFixed(2) + 'mb'; + return (bytes / gb).toFixed(2) + 'gb'; +}; diff --git a/node_modules/express/node_modules/connect/lib/middleware/query.js b/node_modules/express/node_modules/connect/lib/middleware/query.js new file mode 100644 index 0000000..d3b1acd --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/query.js @@ -0,0 +1,40 @@ + +/*! + * Connect - query + * Copyright(c) 2011 TJ Holowaychuk + * Copyright(c) 2011 Sencha Inc. + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var qs = require('qs') + , parse = require('url').parse; + +/** + * Automatically parse the query-string when available, + * populating the `req.query` object. + * + * Examples: + * + * connect( + * connect.query() + * , function(req, res){ + * res.end(JSON.stringify(req.query)); + * } + * ).listen(3000); + * + * @return {Function} + * @api public + */ + +module.exports = function query(){ + return function query(req, res, next){ + req.query = ~req.url.indexOf('?') + ? qs.parse(parse(req.url).query) + : {}; + next(); + }; +}; diff --git a/node_modules/express/node_modules/connect/lib/middleware/responseTime.js b/node_modules/express/node_modules/connect/lib/middleware/responseTime.js new file mode 100644 index 0000000..2b2133a --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/responseTime.js @@ -0,0 +1,34 @@ + +/*! + * Connect - responseTime + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Adds the `X-Response-Time` header displaying the response + * duration in milliseconds. + * + * @return {Function} + * @api public + */ + +module.exports = function responseTime(){ + return function(req, res, next){ + var writeHead = res.writeHead + , start = new Date; + + if (res._responseTime) return next(); + res._responseTime = true; + + // proxy writeHead to calculate duration + res.writeHead = function(status, headers){ + var duration = new Date - start; + res.setHeader('X-Response-Time', duration + 'ms'); + res.writeHead = writeHead; + res.writeHead(status, headers); + }; + + next(); + }; +}; diff --git a/node_modules/express/node_modules/connect/lib/middleware/router.js b/node_modules/express/node_modules/connect/lib/middleware/router.js new file mode 100644 index 0000000..a07452e --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/router.js @@ -0,0 +1,379 @@ + +/*! + * Connect - router + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var utils = require('../utils') + , parse = require('url').parse; + +/** + * Expose router. + */ + +exports = module.exports = router; + +/** + * Supported HTTP / WebDAV methods. + */ + +var _methods = exports.methods = [ + 'get' + , 'post' + , 'put' + , 'delete' + , 'connect' + , 'options' + , 'trace' + , 'copy' + , 'lock' + , 'mkcol' + , 'move' + , 'propfind' + , 'proppatch' + , 'unlock' + , 'report' + , 'mkactivity' + , 'checkout' + , 'merge' +]; + +/** + * Provides Sinatra and Express-like routing capabilities. + * + * Examples: + * + * connect.router(function(app){ + * app.get('/user/:id', function(req, res, next){ + * // populates req.params.id + * }); + * app.put('/user/:id', function(req, res, next){ + * // populates req.params.id + * }); + * }) + * + * @param {Function} fn + * @return {Function} + * @api public + */ + +function router(fn){ + var self = this + , methods = {} + , routes = {} + , params = {}; + + if (!fn) throw new Error('router provider requires a callback function'); + + // Generate method functions + _methods.forEach(function(method){ + methods[method] = generateMethodFunction(method.toUpperCase()); + }); + + // Alias del -> delete + methods.del = methods.delete; + + // Apply callback to all methods + methods.all = function(){ + var args = arguments; + _methods.forEach(function(name){ + methods[name].apply(this, args); + }); + return self; + }; + + // Register param callback + methods.param = function(name, fn){ + params[name] = fn; + }; + + fn.call(this, methods); + + function generateMethodFunction(name) { + var localRoutes = routes[name] = routes[name] || []; + return function(path, fn){ + var keys = [] + , middleware = []; + + // slice middleware + if (arguments.length > 2) { + middleware = Array.prototype.slice.call(arguments, 1, arguments.length); + fn = middleware.pop(); + middleware = utils.flatten(middleware); + } + + fn.middleware = middleware; + + if (!path) throw new Error(name + ' route requires a path'); + if (!fn) throw new Error(name + ' route ' + path + ' requires a callback'); + var regexp = path instanceof RegExp + ? path + : normalizePath(path, keys); + localRoutes.push({ + fn: fn + , path: regexp + , keys: keys + , orig: path + , method: name + }); + return self; + }; + } + + function router(req, res, next){ + var route + , self = this; + + (function pass(i){ + if (route = match(req, routes, i)) { + var i = 0 + , keys = route.keys; + + req.params = route.params; + + // Param preconditions + (function param(err) { + try { + var key = keys[i++] + , val = req.params[key] + , fn = params[key]; + + if ('route' == err) { + pass(req._route_index + 1); + // Error + } else if (err) { + next(err); + // Param has callback + } else if (fn) { + // Return style + if (1 == fn.length) { + req.params[key] = fn(val); + param(); + // Middleware style + } else { + fn(req, res, param, val); + } + // Finished processing params + } else if (!key) { + // route middleware + i = 0; + (function nextMiddleware(err){ + var fn = route.middleware[i++]; + if ('route' == err) { + pass(req._route_index + 1); + } else if (err) { + next(err); + } else if (fn) { + fn(req, res, nextMiddleware); + } else { + route.call(self, req, res, function(err){ + if (err) { + next(err); + } else { + pass(req._route_index + 1); + } + }); + } + })(); + // More params + } else { + param(); + } + } catch (err) { + next(err); + } + })(); + } else if ('OPTIONS' == req.method) { + options(req, res, routes); + } else { + next(); + } + })(); + }; + + router.remove = function(path, method){ + var fns = router.lookup(path, method); + fns.forEach(function(fn){ + routes[fn.method].splice(fn.index, 1); + }); + }; + + router.lookup = function(path, method, ret){ + ret = ret || []; + + // method specific lookup + if (method) { + method = method.toUpperCase(); + if (routes[method]) { + routes[method].forEach(function(route, i){ + if (path == route.orig) { + var fn = route.fn; + fn.regexp = route.path; + fn.keys = route.keys; + fn.path = route.orig; + fn.method = route.method; + fn.index = i; + ret.push(fn); + } + }); + } + // global lookup + } else { + _methods.forEach(function(method){ + router.lookup(path, method, ret); + }); + } + + return ret; + }; + + router.match = function(url, method, ret){ + var ret = ret || [] + , i = 0 + , fn + , req; + + // method specific matches + if (method) { + method = method.toUpperCase(); + req = { url: url, method: method }; + while (fn = match(req, routes, i)) { + i = req._route_index + 1; + ret.push(fn); + } + // global matches + } else { + _methods.forEach(function(method){ + router.match(url, method, ret); + }); + } + + return ret; + }; + + return router; +} + +/** + * Respond to OPTIONS. + * + * @param {ServerRequest} req + * @param {ServerResponse} req + * @param {Array} routes + * @api private + */ + +function options(req, res, routes) { + var pathname = parse(req.url).pathname + , body = optionsFor(pathname, routes).join(','); + res.writeHead(200, { + 'Content-Length': body.length + , 'Allow': body + }); + res.end(body); +} + +/** + * Return OPTIONS array for the given `path`, matching `routes`. + * + * @param {String} path + * @param {Array} routes + * @return {Array} + * @api private + */ + +function optionsFor(path, routes) { + return _methods.filter(function(method){ + var arr = routes[method.toUpperCase()]; + for (var i = 0, len = arr.length; i < len; ++i) { + if (arr[i].path.test(path)) return true; + } + }).map(function(method){ + return method.toUpperCase(); + }); +} + +/** + * Normalize the given path string, + * returning a regular expression. + * + * An empty array should be passed, + * which will contain the placeholder + * key names. For example "/user/:id" will + * then contain ["id"]. + * + * @param {String} path + * @param {Array} keys + * @return {RegExp} + * @api private + */ + +function normalizePath(path, keys) { + path = path + .concat('/?') + .replace(/\/\(/g, '(?:/') + .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){ + keys.push(key); + slash = slash || ''; + return '' + + (optional ? '' : slash) + + '(?:' + + (optional ? slash : '') + + (format || '') + (capture || '([^/]+?)') + ')' + + (optional || ''); + }) + .replace(/([\/.])/g, '\\$1') + .replace(/\*/g, '(.+)'); + return new RegExp('^' + path + '$', 'i'); +} + +/** + * Attempt to match the given request to + * one of the routes. When successful + * a route function is returned. + * + * @param {ServerRequest} req + * @param {Object} routes + * @return {Function} + * @api private + */ + +function match(req, routes, i) { + var captures + , method = req.method + , i = i || 0; + if ('HEAD' == method) method = 'GET'; + if (routes = routes[method]) { + var url = parse(req.url) + , pathname = url.pathname; + for (var len = routes.length; i < len; ++i) { + var route = routes[i] + , fn = route.fn + , path = route.path + , keys = fn.keys = route.keys; + if (captures = path.exec(pathname)) { + fn.method = method; + fn.params = []; + for (var j = 1, len = captures.length; j < len; ++j) { + var key = keys[j-1], + val = typeof captures[j] === 'string' + ? decodeURIComponent(captures[j]) + : captures[j]; + if (key) { + fn.params[key] = val; + } else { + fn.params.push(val); + } + } + req._route_index = i; + return fn; + } + } + } +} diff --git a/node_modules/express/node_modules/connect/lib/middleware/session.js b/node_modules/express/node_modules/connect/lib/middleware/session.js new file mode 100644 index 0000000..7fbeb18 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/session.js @@ -0,0 +1,346 @@ + +/*! + * Connect - session + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Session = require('./session/session') + , MemoryStore = require('./session/memory') + , Cookie = require('./session/cookie') + , Store = require('./session/store') + , utils = require('./../utils') + , parse = require('url').parse + , crypto = require('crypto'); + +// environment + +var env = process.env.NODE_ENV; + +/** + * Expose the middleware. + */ + +exports = module.exports = session; + +/** + * Expose constructors. + */ + +exports.Store = Store; +exports.Cookie = Cookie; +exports.Session = Session; +exports.MemoryStore = MemoryStore; + +/** + * Warning message for `MemoryStore` usage in production. + */ + +var warning = 'Warning: connection.session() MemoryStore is not\n' + + 'designed for a production environment, as it will leak\n' + + 'memory, and obviously only work within a single process.'; + +/** + * Default finger-printing function. + */ + +function defaultFingerprint(req) { + var ua = req.headers['user-agent'] || ''; + return ua.replace(/;?\schromeframe\/[\d\.]+/, ''); +}; + +/** + * Paths to ignore. + */ + +exports.ignore = []; + +/** + * Setup session store with the given `options`. + * + * Session data is _not_ saved in the cookie itself, however + * cookies are used, so we must use the [cookieParser()](middleware-cookieParser.html) + * middleware _before_ `session()`. + * + * Examples: + * + * connect.createServer( + * connect.cookieParser() + * , connect.session({ secret: 'keyboard cat' }) + * ); + * + * Options: + * + * - `key` cookie name defaulting to `connect.sid` + * - `store` Session store instance + * - `fingerprint` Custom fingerprint generating function + * - `cookie` Session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: 14400000 }` + * - `secret` Secret string used to compute hash + * + * Ignore Paths: + * + * By default `/favicon.ico` is the only ignored path, all others + * will utilize sessions, to manipulate the paths ignored, use + * `connect.session.ignore.push('/my/path')`. This works for _full_ + * pathnames only, not segments nor substrings. + * + * connect.session.ignore.push('/robots.txt'); + * + * ## req.session + * + * To store or access session data, simply use the request property `req.session`, + * which is (generally) serialized as JSON by the store, so nested objects + * are typically fine. For example below is a user-specific view counter: + * + * connect( + * connect.cookieParser() + * , connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}) + * , connect.favicon() + * , function(req, res, next){ + * var sess = req.session; + * if (sess.views) { + * res.setHeader('Content-Type', 'text/html'); + * res.write('

    views: ' + sess.views + '

    '); + * res.write('

    expires in: ' + (sess.cookie.maxAge / 1000) + 's

    '); + * res.end(); + * sess.views++; + * } else { + * sess.views = 1; + * res.end('welcome to the session demo. refresh!'); + * } + * } + * ).listen(3000); + * + * ## Session#regenerate() + * + * To regenerate the session simply invoke the method, once complete + * a new SID and `Session` instance will be initialized at `req.session`. + * + * req.session.regenerate(function(err){ + * // will have a new session here + * }); + * + * ## Session#destroy() + * + * Destroys the session, removing `req.session`, will be re-generated next request. + * + * req.session.destroy(function(err){ + * // cannot access session here + * }); + * + * ## Session#reload() + * + * Reloads the session data. + * + * req.session.reload(function(err){ + * // session updated + * }); + * + * ## Session#save() + * + * Save the session. + * + * req.session.save(function(err){ + * // session saved + * }); + * + * ## Session#touch() + * + * Updates the `.maxAge`, and `.lastAccess` properties. Typically this is + * not necessary to call, as the session middleware does this for you. + * + * ## Session#cookie + * + * Each session has a unique cookie object accompany it. This allows + * you to alter the session cookie per visitor. For example we can + * set `req.session.cookie.expires` to `false` to enable the cookie + * to remain for only the duration of the user-agent. + * + * ## Session#maxAge + * + * Alternatively `req.session.cookie.maxAge` will return the time + * remaining in milliseconds, which we may also re-assign a new value + * to adjust the `.expires` property appropriately. The following + * are essentially equivalent + * + * var hour = 3600000; + * req.session.cookie.expires = new Date(Date.now() + hour); + * req.session.cookie.maxAge = hour; + * + * For example when `maxAge` is set to `60000` (one minute), and 30 seconds + * has elapsed it will return `30000` until the current request has completed, + * at which time `req.session.touch()` is called to update `req.session.lastAccess`, + * and reset `req.session.maxAge` to its original value. + * + * req.session.cookie.maxAge; + * // => 30000 + * + * Session Store Implementation: + * + * Every session store _must_ implement the following methods + * + * - `.get(sid, callback)` + * - `.set(sid, session, callback)` + * - `.destroy(sid, callback)` + * + * Recommended methods include, but are not limited to: + * + * - `.length(callback)` + * - `.clear(callback)` + * + * For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo. + * + * @param {Object} options + * @return {Function} + * @api public + */ + +function session(options){ + var options = options || {} + , key = options.key || 'connect.sid' + , secret = options.secret + , store = options.store || new MemoryStore + , fingerprint = options.fingerprint || defaultFingerprint + , cookie = options.cookie; + + // notify user that this store is not + // meant for a production environment + if ('production' == env && store instanceof MemoryStore) { + console.warn(warning); + } + + // ensure secret is present + if (!secret) { + throw new Error('connect.session({ secret: "string" }) required for security'); + } + + // session hashing function + store.hash = function(req, base) { + return crypto + .createHmac('sha256', secret) + .update(base + fingerprint(req)) + .digest('base64') + .replace(/=*$/, ''); + }; + + // generates the new session + store.generate = function(req){ + var base = utils.uid(24); + var sessionID = base + '.' + store.hash(req, base); + req.sessionID = sessionID; + req.session = new Session(req); + req.session.cookie = new Cookie(cookie); + }; + + return function session(req, res, next) { + // self-awareness + if (req.session) return next(); + + // parse url + var url = parse(req.url) + , path = url.pathname; + + // ignorable paths + if (~exports.ignore.indexOf(path)) return next(); + + // expose store + req.sessionStore = store; + + // proxy writeHead() to Set-Cookie + var writeHead = res.writeHead; + res.writeHead = function(status, headers){ + if (req.session) { + var cookie = req.session.cookie; + // only send secure session cookies when there is a secure connection. + // proxySecure is a custom attribute to allow for a reverse proxy + // to handle SSL connections and to communicate to connect over HTTP that + // the incoming connection is secure. + var secured = cookie.secure && (req.connection.encrypted || req.connection.proxySecure); + if (secured || !cookie.secure) { + res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID)); + } + } + + res.writeHead = writeHead; + return res.writeHead(status, headers); + }; + + // proxy end() to commit the session + var end = res.end; + res.end = function(data, encoding){ + res.end = end; + if (req.session) { + // HACK: ensure Set-Cookie for implicit writeHead() + if (!res._header) res._implicitHeader(); + req.session.resetMaxAge(); + req.session.save(function(){ + res.end(data, encoding); + }); + } else { + res.end(data, encoding); + } + }; + + // session hashing + function hash(base) { + return store.hash(req, base); + } + + // generate the session + function generate() { + store.generate(req); + } + + // get the sessionID from the cookie + req.sessionID = req.cookies[key]; + + // make a new session if the browser doesn't send a sessionID + if (!req.sessionID) { + generate(); + next(); + return; + } + + // check the fingerprint + var parts = req.sessionID.split('.'); + if (parts[1] != hash(parts[0])) { + generate(); + next(); + return; + } + + // generate the session object + var pause = utils.pause(req); + store.get(req.sessionID, function(err, sess){ + // proxy to resume() events + var _next = next; + next = function(err){ + _next(err); + pause.resume(); + } + + // error handling + if (err) { + if ('ENOENT' == err.code) { + generate(); + next(); + } else { + next(err); + } + // no session + } else if (!sess) { + generate(); + next(); + // populate req.session + } else { + store.createSession(req, sess); + next(); + } + }); + }; +}; diff --git a/node_modules/express/node_modules/connect/lib/middleware/session/cookie.js b/node_modules/express/node_modules/connect/lib/middleware/session/cookie.js new file mode 100644 index 0000000..793c2e9 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/session/cookie.js @@ -0,0 +1,126 @@ + +/*! + * Connect - session - Cookie + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var utils = require('../../utils'); + +/** + * Initialize a new `Cookie` with the given `options`. + * + * @param {Object} options + * @api private + */ + +var Cookie = module.exports = function Cookie(options) { + this.path = '/'; + this.httpOnly = true; + this.maxAge = 14400000; + if (options) utils.merge(this, options); + this.originalMaxAge = undefined == this.originalMaxAge + ? this.maxAge + : this.originalMaxAge; +}; + +/** + * Prototype. + */ + +Cookie.prototype = { + + /** + * Set expires `date`. + * + * @param {Date} date + * @api public + */ + + set expires(date) { + this._expires = date; + this.originalMaxAge = this.maxAge; + }, + + /** + * Get expires `date`. + * + * @return {Date} + * @api public + */ + + get expires() { + return this._expires; + }, + + /** + * Set expires via max-age in `ms`. + * + * @param {Number} ms + * @api public + */ + + set maxAge(ms) { + this.expires = 'number' == typeof ms + ? new Date(Date.now() + ms) + : ms; + }, + + /** + * Get expires max-age in `ms`. + * + * @return {Number} + * @api public + */ + + get maxAge() { + return this.expires instanceof Date + ? this.expires.valueOf() - Date.now() + : this.expires; + }, + + /** + * Return cookie data object. + * + * @return {Object} + * @api private + */ + + get data() { + return { + originalMaxAge: this.originalMaxAge + , expires: this._expires + , secure: this.secure + , httpOnly: this.httpOnly + , domain: this.domain + , path: this.path + } + }, + + /** + * Return a serialized cookie string. + * + * @return {String} + * @api public + */ + + serialize: function(name, val){ + return utils.serializeCookie(name, val, this.data); + }, + + /** + * Return JSON representation of this cookie. + * + * @return {Object} + * @api private + */ + + toJSON: function(){ + return this.data; + } +}; diff --git a/node_modules/express/node_modules/connect/lib/middleware/session/memory.js b/node_modules/express/node_modules/connect/lib/middleware/session/memory.js new file mode 100644 index 0000000..ec569f5 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/session/memory.js @@ -0,0 +1,131 @@ + +/*! + * Connect - session - MemoryStore + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Store = require('./store') + , utils = require('../../utils') + , Session = require('./session'); + +/** + * Initialize a new `MemoryStore`. + * + * @api public + */ + +var MemoryStore = module.exports = function MemoryStore() { + this.sessions = {}; +}; + +/** + * Inherit from `Store.prototype`. + */ + +MemoryStore.prototype.__proto__ = Store.prototype; + +/** + * Attempt to fetch session by the given `sid`. + * + * @param {String} sid + * @param {Function} fn + * @api public + */ + +MemoryStore.prototype.get = function(sid, fn){ + var self = this; + process.nextTick(function(){ + var expires + , sess = self.sessions[sid]; + if (sess) { + sess = JSON.parse(sess); + expires = 'string' == typeof sess.cookie.expires + ? new Date(sess.cookie.expires) + : sess.cookie.expires; + if (!expires || new Date < expires) { + fn(null, sess); + } else { + self.destroy(sid, fn); + } + } else { + fn(); + } + }); +}; + +/** + * Commit the given `sess` object associated with the given `sid`. + * + * @param {String} sid + * @param {Session} sess + * @param {Function} fn + * @api public + */ + +MemoryStore.prototype.set = function(sid, sess, fn){ + var self = this; + process.nextTick(function(){ + self.sessions[sid] = JSON.stringify(sess); + fn && fn(); + }); +}; + +/** + * Destroy the session associated with the given `sid`. + * + * @param {String} sid + * @api public + */ + +MemoryStore.prototype.destroy = function(sid, fn){ + var self = this; + process.nextTick(function(){ + delete self.sessions[sid]; + fn && fn(); + }); +}; + +/** + * Invoke the given callback `fn` with all active sessions. + * + * @param {Function} fn + * @api public + */ + +MemoryStore.prototype.all = function(fn){ + var arr = [] + , keys = Object.keys(this.sessions); + for (var i = 0, len = keys.length; i < len; ++i) { + arr.push(this.sessions[keys[i]]); + } + fn(null, arr); +}; + +/** + * Clear all sessions. + * + * @param {Function} fn + * @api public + */ + +MemoryStore.prototype.clear = function(fn){ + this.sessions = {}; + fn && fn(); +}; + +/** + * Fetch number of sessions. + * + * @param {Function} fn + * @api public + */ + +MemoryStore.prototype.length = function(fn){ + fn(null, Object.keys(this.sessions).length); +}; diff --git a/node_modules/express/node_modules/connect/lib/middleware/session/session.js b/node_modules/express/node_modules/connect/lib/middleware/session/session.js new file mode 100644 index 0000000..4e7e1a6 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/session/session.js @@ -0,0 +1,137 @@ + +/*! + * Connect - session - Session + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var utils = require('../../utils') + , Cookie = require('./cookie'); + +/** + * Create a new `Session` with the given request and `data`. + * + * @param {IncomingRequest} req + * @param {Object} data + * @api private + */ + +var Session = module.exports = function Session(req, data) { + Object.defineProperty(this, 'req', { value: req }); + Object.defineProperty(this, 'id', { value: req.sessionID }); + if ('object' == typeof data) { + utils.merge(this, data); + } else { + this.lastAccess = Date.now(); + } +}; + +/** + * Update `.lastAccess` timestamp, + * and reset `.cookie.maxAge` to prevent + * the cookie from expiring when the + * session is still active. + * + * @return {Session} for chaining + * @api public + */ + +Session.prototype.touch = function(){ + return this + .resetLastAccess() + .resetMaxAge(); +}; + +/** + * Update `.lastAccess` timestamp. + * + * @return {Session} for chaining + * @api public + */ + +Session.prototype.resetLastAccess = function(){ + this.lastAccess = Date.now(); + return this; +}; + +/** + * Reset `.maxAge` to `.originalMaxAge`. + * + * @return {Session} for chaining + * @api public + */ + +Session.prototype.resetMaxAge = function(){ + this.cookie.maxAge = this.cookie.originalMaxAge; + return this; +}; + +/** + * Save the session data with optional callback `fn(err)`. + * + * @param {Function} fn + * @return {Session} for chaining + * @api public + */ + +Session.prototype.save = function(fn){ + this.req.sessionStore.set(this.id, this, fn || function(){}); + return this; +}; + +/** + * Re-loads the session data _without_ altering + * the maxAge or lastAccess properties. Invokes the + * callback `fn(err)`, after which time if no exception + * has occurred the `req.session` property will be + * a new `Session` object, although representing the + * same session. + * + * @param {Function} fn + * @return {Session} for chaining + * @api public + */ + +Session.prototype.reload = function(fn){ + var req = this.req + , store = this.req.sessionStore; + store.get(this.id, function(err, sess){ + if (err) return fn(err); + if (!sess) return fn(new Error('failed to load session')); + store.createSession(req, sess); + fn(); + }); + return this; +}; + +/** + * Destroy `this` session. + * + * @param {Function} fn + * @return {Session} for chaining + * @api public + */ + +Session.prototype.destroy = function(fn){ + delete this.req.session; + this.req.sessionStore.destroy(this.id, fn); + return this; +}; + +/** + * Regenerate this request's session. + * + * @param {Function} fn + * @return {Session} for chaining + * @api public + */ + +Session.prototype.regenerate = function(fn){ + this.req.sessionStore.regenerate(this.req, fn); + return this; +}; diff --git a/node_modules/express/node_modules/connect/lib/middleware/session/store.js b/node_modules/express/node_modules/connect/lib/middleware/session/store.js new file mode 100644 index 0000000..6a3d47d --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/session/store.js @@ -0,0 +1,87 @@ + +/*! + * Connect - session - Store + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , Session = require('./session') + , Cookie = require('./cookie') + , utils = require('../../utils'); + +/** + * Initialize abstract `Store`. + * + * @api private + */ + +var Store = module.exports = function Store(options){}; + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Store.prototype.__proto__ = EventEmitter.prototype; + +/** + * Re-generate the given requests's session. + * + * @param {IncomingRequest} req + * @return {Function} fn + * @api public + */ + +Store.prototype.regenerate = function(req, fn){ + var self = this; + this.destroy(req.sessionID, function(err){ + self.generate(req); + fn(err); + }); +}; + +/** + * Load a `Session` instance via the given `sid` + * and invoke the callback `fn(err, sess)`. + * + * @param {String} sid + * @param {Function} fn + * @api public + */ + +Store.prototype.load = function(sid, fn){ + var self = this; + this.get(sid, function(err, sess){ + if (err) return fn(err); + if (!sess) return fn(); + var req = { sessionID: sid, sessionStore: self }; + sess = self.createSession(req, sess, false); + fn(null, sess); + }); +}; + +/** + * Create session from JSON `sess` data. + * + * @param {IncomingRequest} req + * @param {Object} sess + * @return {Session} + * @api private + */ + +Store.prototype.createSession = function(req, sess, update){ + var expires = sess.cookie.expires + , orig = sess.cookie.originalMaxAge + , update = null == update ? true : false; + sess.cookie = new Cookie(sess.cookie); + if ('string' == typeof expires) sess.cookie.expires = new Date(expires); + sess.cookie.originalMaxAge = orig; + req.session = new Session(req, sess); + if (update) req.session.resetLastAccess(); + return req.session; +}; \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/static.js b/node_modules/express/node_modules/connect/lib/middleware/static.js new file mode 100644 index 0000000..b9c2c86 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/static.js @@ -0,0 +1,225 @@ + +/*! + * Connect - staticProvider + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var fs = require('fs') + , path = require('path') + , join = path.join + , basename = path.basename + , normalize = path.normalize + , utils = require('../utils') + , Buffer = require('buffer').Buffer + , parse = require('url').parse + , mime = require('mime'); + +/** + * Static file server with the given `root` path. + * + * Examples: + * + * var oneDay = 86400000; + * + * connect( + * connect.static(__dirname + '/public') + * ).listen(3000); + * + * connect( + * connect.static(__dirname + '/public', { maxAge: oneDay }) + * ).listen(3000); + * + * Options: + * + * - `maxAge` Browser cache maxAge in milliseconds. defaults to 0 + * - `hidden` Allow transfer of hidden files. defaults to false + * - `redirect` Redirect to trailing "/" when the pathname is a dir + * + * @param {String} root + * @param {Object} options + * @return {Function} + * @api public + */ + +exports = module.exports = function static(root, options){ + options = options || {}; + + // root required + if (!root) throw new Error('static() root path required'); + options.root = root; + + return function static(req, res, next) { + options.path = req.url; + options.getOnly = true; + send(req, res, next, options); + }; +}; + +/** + * Expose mime module. + */ + +exports.mime = mime; + +/** + * Respond with 416 "Requested Range Not Satisfiable" + * + * @param {ServerResponse} res + * @api private + */ + +function invalidRange(res) { + var body = 'Requested Range Not Satisfiable'; + res.setHeader('Content-Type', 'text/plain'); + res.setHeader('Content-Length', body.length); + res.statusCode = 416; + res.end(body); +} + +/** + * Attempt to tranfer the requseted file to `res`. + * + * @param {ServerRequest} + * @param {ServerResponse} + * @param {Function} next + * @param {Object} options + * @api private + */ + +var send = exports.send = function(req, res, next, options){ + options = options || {}; + if (!options.path) throw new Error('path required'); + + // setup + var maxAge = options.maxAge || 0 + , ranges = req.headers.range + , head = 'HEAD' == req.method + , get = 'GET' == req.method + , root = options.root ? normalize(options.root) : null + , redirect = false === options.redirect ? false : true + , getOnly = options.getOnly + , fn = options.callback + , hidden = options.hidden + , done; + + // replace next() with callback when available + if (fn) next = fn; + + // ignore non-GET requests + if (getOnly && !get && !head) return next(); + + // parse url + var url = parse(options.path) + , path = decodeURIComponent(url.pathname) + , type; + + // null byte(s) + if (~path.indexOf('\0')) return utils.badRequest(res); + + // when root is not given, consider .. malicious + if (!root && ~path.indexOf('..')) return utils.forbidden(res); + + // join / normalize from optional root dir + path = normalize(join(root, path)); + + // malicious path + if (root && 0 != path.indexOf(root)) return fn + ? fn(new Error('Forbidden')) + : utils.forbidden(res); + + // index.html support + if (normalize('/') == path[path.length - 1]) path += 'index.html'; + + // "hidden" file + if (!hidden && '.' == basename(path)[0]) return next(); + + fs.stat(path, function(err, stat){ + // mime type + type = mime.lookup(path); + + // ignore ENOENT + if (err) { + if (fn) return fn(err); + return 'ENOENT' == err.code + ? next() + : next(err); + // redirect directory in case index.html is present + } else if (stat.isDirectory()) { + if (!redirect) return next(); + res.statusCode = 301; + res.setHeader('Location', url.pathname + '/'); + res.end('Redirecting to ' + url.pathname + '/'); + return; + } + + // header fields + if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString()); + if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (maxAge / 1000)); + if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString()); + if (!res.getHeader('ETag')) res.setHeader('ETag', utils.etag(stat)); + if (!res.getHeader('content-type')) { + var charset = mime.charsets.lookup(type); + res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); + } + res.setHeader('Accept-Ranges', 'bytes'); + + // conditional GET support + if (utils.conditionalGET(req)) { + if (!utils.modified(req, res)) { + req.emit('static'); + return utils.notModified(res); + } + } + + var opts = {}; + var chunkSize = stat.size; + + // we have a Range request + if (ranges) { + ranges = utils.parseRange(stat.size, ranges); + // valid + if (ranges) { + // TODO: stream options + // TODO: multiple support + opts.start = ranges[0].start; + opts.end = ranges[0].end; + chunkSize = opts.end - opts.start + 1; + res.statusCode = 206; + res.setHeader('Content-Range', 'bytes ' + + opts.start + + '-' + + opts.end + + '/' + + stat.size); + // invalid + } else { + return fn + ? fn(new Error('Requested Range Not Satisfiable')) + : invalidRange(res); + } + } + + res.setHeader('Content-Length', chunkSize); + + // transfer + if (head) return res.end(); + + // stream + var stream = fs.createReadStream(path, opts); + req.emit('static', stream); + stream.pipe(res); + + // callback + if (fn) { + function callback(err) { done || fn(err); done = true } + req.on('close', callback); + stream.on('end', callback); + } + }); +}; diff --git a/node_modules/express/node_modules/connect/lib/middleware/staticCache.js b/node_modules/express/node_modules/connect/lib/middleware/staticCache.js new file mode 100644 index 0000000..9ea8eb7 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/staticCache.js @@ -0,0 +1,175 @@ + +/*! + * Connect - staticCache + * Copyright(c) 2011 Sencha Inc. + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var http = require('http') + , utils = require('../utils') + , Cache = require('../cache') + , url = require('url') + , fs = require('fs'); + +/** + * Enables a memory cache layer on top of + * the `static()` middleware, serving popular + * static files. + * + * By default a maximum of 128 objects are + * held in cache, with a max of 256k each, + * totalling ~32mb. + * + * A Least-Recently-Used (LRU) cache algo + * is implemented through the `Cache` object, + * simply rotating cache objects as they are + * hit. This means that increasingly popular + * objects maintain their positions while + * others get shoved out of the stack and + * garbage collected. + * + * Benchmarks: + * + * static(): 2700 rps + * node-static: 5300 rps + * static() + staticCache(): 7500 rps + * + * Options: + * + * - `maxObjects` max cache objects [128] + * - `maxLength` max cache object length 256kb + * + * @param {Type} name + * @return {Type} + * @api public + */ + +module.exports = function staticCache(options){ + var options = options || {} + , cache = new Cache(options.maxObjects || 128) + , maxlen = options.maxLength || 1024 * 256; + + return function staticCache(req, res, next){ + var path = url.parse(req.url).pathname + , ranges = req.headers.range + , hit = cache.get(path) + , hitCC + , uaCC + , header + , age; + + // cache static + req.on('static', function(stream){ + var headers = res._headers + , cc = utils.parseCacheControl(headers['cache-control'] || '') + , contentLength = headers['content-length'] + , hit; + + // ignore larger files + if (!contentLength || contentLength > maxlen) return; + + // dont cache items we shouldn't be + if ( cc['no-cache'] + || cc['no-store'] + || cc['private'] + || cc['must-revalidate']) return; + + // if already in cache then validate + if (hit = cache.get(path)){ + if (headers.etag == hit[0].etag) { + hit[0].date = new Date; + return; + } else { + cache.remove(path); + } + } + + // validation notifiactions don't contain a steam + if (null == stream) return; + + // add the cache object + var arr = cache.add(path); + arr.push(headers); + + // store the chunks + stream.on('data', function(chunk){ + arr.push(chunk); + }); + + // flag it as complete + stream.on('end', function(){ + arr.complete = true; + }); + }); + + // cache hit, doesnt support range requests + if (hit && hit.complete && !ranges) { + header = utils.merge({}, hit[0]); + header.Age = age = (new Date - new Date(header.date)) / 1000 | 0; + header.date = new Date().toUTCString(); + + // parse cache-controls + hitCC = utils.parseCacheControl(header['cache-control'] || ''); + uaCC = utils.parseCacheControl(req.headers['cache-control'] || ''); + + // check if we must revalidate(bypass) + if (hitCC['no-cache'] || uaCC['no-cache']) return next(); + + // check freshness of entity + if (isStale(hitCC, age) || isStale(uaCC, age)) return next(); + + // conditional GET support + if (utils.conditionalGET(req)) { + if (!utils.modified(req, res, header)) { + header['content-length'] = 0; + res.writeHead(304, header); + return res.end(); + } + } + + // HEAD support + if ('HEAD' == req.method) { + header['content-length'] = 0; + res.writeHead(200, header); + return res.end(); + } + + // respond with cache + res.writeHead(200, header); + + // backpressure + function write(i) { + var buf = hit[i]; + if (!buf) return res.end(); + if (false === res.write(buf)) { + res.once('drain', function(){ + write(++i); + }); + } else { + write(++i); + } + } + + return write(1); + } + + next(); + } +}; + +/** + * Check if cache item is stale + * + * @param {Object} cc + * @param {Number} age + * @return {Boolean} + * @api private + */ + +function isStale(cc, age) { + return cc['max-age'] && cc['max-age'] <= age; +} \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/middleware/vhost.js b/node_modules/express/node_modules/connect/lib/middleware/vhost.js new file mode 100644 index 0000000..913d756 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/middleware/vhost.js @@ -0,0 +1,44 @@ + +/*! + * Connect - vhost + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Setup vhost for the given `hostname` and `server`. + * + * Examples: + * + * connect( + * connect.vhost('foo.com', + * connect.createServer(...middleware...) + * ), + * connect.vhost('bar.com', + * connect.createServer(...middleware...) + * ) + * ); + * + * @param {String} hostname + * @param {Server} server + * @return {Function} + * @api public + */ + +module.exports = function vhost(hostname, server){ + if (!hostname) throw new Error('vhost hostname required'); + if (!server) throw new Error('vhost server required'); + var regexp = new RegExp('^' + hostname.replace(/[*]/g, '(.*?)') + '$'); + if (server.onvhost) server.onvhost(hostname); + return function vhost(req, res, next){ + if (!req.headers.host) return next(); + var host = req.headers.host.split(':')[0]; + if (req.subdomains = regexp.exec(host)) { + req.subdomains = req.subdomains[0].split('.').slice(0, -1); + server.emit("request", req, res); + } else { + next(); + } + }; +}; diff --git a/node_modules/express/node_modules/connect/lib/patch.js b/node_modules/express/node_modules/connect/lib/patch.js new file mode 100644 index 0000000..a6ff297 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/patch.js @@ -0,0 +1,79 @@ + +/*! + * Connect + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var http = require('http') + , res = http.OutgoingMessage.prototype; + +// original setHeader() + +var setHeader = res.setHeader; + +// original _renderHeaders() + +var _renderHeaders = res._renderHeaders; + +if (res._hasConnectPatch) return; + +/** + * Provide a public "header sent" flag + * until node does. + * + * @return {Boolean} + * @api public + */ + +res.__defineGetter__('headerSent', function(){ + return this._headerSent; +}); + +/** + * Set header `field` to `val`, special-casing + * the `Set-Cookie` field for multiple support. + * + * @param {String} field + * @param {String} val + * @api public + */ + +res.setHeader = function(field, val){ + var key = field.toLowerCase() + , prev; + + // special-case Set-Cookie + if (this._headers && 'set-cookie' == key) { + if (prev = this.getHeader(field)) { + val = Array.isArray(prev) + ? prev.concat(val) + : [prev, val]; + } + // charset + } else if ('content-type' == key && this.charset) { + val += '; charset=' + this.charset; + } + + return setHeader.call(this, field, val); +}; + +/** + * Proxy `res.end()` to expose a 'header' event, + * allowing arbitrary augmentation before the header + * fields are written to the socket. + * + * NOTE: this _only_ supports node's progressive header + * field API aka `res.setHeader()`. + */ + +res._renderHeaders = function(){ + this.emit('header'); + return _renderHeaders.call(this); +}; + +res._hasConnectPatch = true; diff --git a/node_modules/express/node_modules/connect/lib/public/directory.html b/node_modules/express/node_modules/connect/lib/public/directory.html new file mode 100644 index 0000000..15164bb --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/public/directory.html @@ -0,0 +1,75 @@ + + + listing directory {directory} + + + + + +
    +

    {linked-path}

    + {files} +
    + + \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/public/error.html b/node_modules/express/node_modules/connect/lib/public/error.html new file mode 100644 index 0000000..34e0df5 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/public/error.html @@ -0,0 +1,13 @@ + + + {error} + + + +
    +

    {title}

    +

    500 {error}

    +
      {stack}
    +
    + + \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/lib/public/favicon.ico b/node_modules/express/node_modules/connect/lib/public/favicon.ico new file mode 100644 index 0000000..895fc96 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/favicon.ico differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page.png b/node_modules/express/node_modules/connect/lib/public/icons/page.png new file mode 100755 index 0000000..03ddd79 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_add.png b/node_modules/express/node_modules/connect/lib/public/icons/page_add.png new file mode 100755 index 0000000..d5bfa07 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_add.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_attach.png b/node_modules/express/node_modules/connect/lib/public/icons/page_attach.png new file mode 100755 index 0000000..89ee2da Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_attach.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_code.png b/node_modules/express/node_modules/connect/lib/public/icons/page_code.png new file mode 100755 index 0000000..f7ea904 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_code.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_copy.png b/node_modules/express/node_modules/connect/lib/public/icons/page_copy.png new file mode 100755 index 0000000..195dc6d Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_copy.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_delete.png b/node_modules/express/node_modules/connect/lib/public/icons/page_delete.png new file mode 100755 index 0000000..3141467 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_delete.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_edit.png b/node_modules/express/node_modules/connect/lib/public/icons/page_edit.png new file mode 100755 index 0000000..046811e Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_edit.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_error.png b/node_modules/express/node_modules/connect/lib/public/icons/page_error.png new file mode 100755 index 0000000..f07f449 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_error.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_excel.png b/node_modules/express/node_modules/connect/lib/public/icons/page_excel.png new file mode 100755 index 0000000..eb6158e Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_excel.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_find.png b/node_modules/express/node_modules/connect/lib/public/icons/page_find.png new file mode 100755 index 0000000..2f19388 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_find.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_gear.png b/node_modules/express/node_modules/connect/lib/public/icons/page_gear.png new file mode 100755 index 0000000..8e83281 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_gear.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_go.png b/node_modules/express/node_modules/connect/lib/public/icons/page_go.png new file mode 100755 index 0000000..80fe1ed Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_go.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_green.png b/node_modules/express/node_modules/connect/lib/public/icons/page_green.png new file mode 100755 index 0000000..de8e003 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_green.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_key.png b/node_modules/express/node_modules/connect/lib/public/icons/page_key.png new file mode 100755 index 0000000..d6626cb Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_key.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_lightning.png b/node_modules/express/node_modules/connect/lib/public/icons/page_lightning.png new file mode 100755 index 0000000..7e56870 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_lightning.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_link.png b/node_modules/express/node_modules/connect/lib/public/icons/page_link.png new file mode 100755 index 0000000..312eab0 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_link.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_paintbrush.png b/node_modules/express/node_modules/connect/lib/public/icons/page_paintbrush.png new file mode 100755 index 0000000..246a2f0 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_paintbrush.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_paste.png b/node_modules/express/node_modules/connect/lib/public/icons/page_paste.png new file mode 100755 index 0000000..968f073 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_paste.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_red.png b/node_modules/express/node_modules/connect/lib/public/icons/page_red.png new file mode 100755 index 0000000..0b18247 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_red.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_refresh.png b/node_modules/express/node_modules/connect/lib/public/icons/page_refresh.png new file mode 100755 index 0000000..cf347c7 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_refresh.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_save.png b/node_modules/express/node_modules/connect/lib/public/icons/page_save.png new file mode 100755 index 0000000..caea546 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_save.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white.png new file mode 100755 index 0000000..8b8b1ca Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_acrobat.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_acrobat.png new file mode 100755 index 0000000..8f8095e Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_acrobat.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_actionscript.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_actionscript.png new file mode 100755 index 0000000..159b240 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_actionscript.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_add.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_add.png new file mode 100755 index 0000000..aa23dde Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_add.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_c.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_c.png new file mode 100755 index 0000000..34a05cc Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_c.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_camera.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_camera.png new file mode 100755 index 0000000..f501a59 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_camera.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_cd.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_cd.png new file mode 100755 index 0000000..848bdaf Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_cd.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_code.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_code.png new file mode 100755 index 0000000..0c76bd1 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_code.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_code_red.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_code_red.png new file mode 100755 index 0000000..87a6914 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_code_red.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_coldfusion.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_coldfusion.png new file mode 100755 index 0000000..c66011f Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_coldfusion.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_compressed.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_compressed.png new file mode 100755 index 0000000..2b6b100 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_compressed.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_copy.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_copy.png new file mode 100755 index 0000000..a9f31a2 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_copy.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_cplusplus.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_cplusplus.png new file mode 100755 index 0000000..a87cf84 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_cplusplus.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_csharp.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_csharp.png new file mode 100755 index 0000000..ffb8fc9 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_csharp.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_cup.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_cup.png new file mode 100755 index 0000000..0a7d6f4 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_cup.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_database.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_database.png new file mode 100755 index 0000000..bddba1f Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_database.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_delete.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_delete.png new file mode 100755 index 0000000..af1ecaf Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_delete.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_dvd.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_dvd.png new file mode 100755 index 0000000..4cc537a Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_dvd.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_edit.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_edit.png new file mode 100755 index 0000000..b93e776 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_edit.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_error.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_error.png new file mode 100755 index 0000000..9fc5a0a Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_error.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_excel.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_excel.png new file mode 100755 index 0000000..b977d7e Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_excel.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_find.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_find.png new file mode 100755 index 0000000..5818436 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_find.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_flash.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_flash.png new file mode 100755 index 0000000..5769120 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_flash.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_freehand.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_freehand.png new file mode 100755 index 0000000..8d719df Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_freehand.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_gear.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_gear.png new file mode 100755 index 0000000..106f5aa Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_gear.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_get.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_get.png new file mode 100755 index 0000000..e4a1ecb Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_get.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_go.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_go.png new file mode 100755 index 0000000..7e62a92 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_go.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_h.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_h.png new file mode 100755 index 0000000..e902abb Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_h.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_horizontal.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_horizontal.png new file mode 100755 index 0000000..1d2d0a4 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_horizontal.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_key.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_key.png new file mode 100755 index 0000000..d616484 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_key.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_lightning.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_lightning.png new file mode 100755 index 0000000..7215d1e Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_lightning.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_link.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_link.png new file mode 100755 index 0000000..bf7bd1c Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_link.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_magnify.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_magnify.png new file mode 100755 index 0000000..f6b74cc Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_magnify.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_medal.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_medal.png new file mode 100755 index 0000000..d3fffb6 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_medal.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_office.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_office.png new file mode 100755 index 0000000..a65bcb3 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_office.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_paint.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_paint.png new file mode 100755 index 0000000..23a37b8 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_paint.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_paintbrush.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_paintbrush.png new file mode 100755 index 0000000..f907e44 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_paintbrush.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_paste.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_paste.png new file mode 100755 index 0000000..5b2cbb3 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_paste.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_php.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_php.png new file mode 100755 index 0000000..7868a25 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_php.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_picture.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_picture.png new file mode 100755 index 0000000..134b669 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_picture.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_powerpoint.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_powerpoint.png new file mode 100755 index 0000000..c4eff03 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_powerpoint.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_put.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_put.png new file mode 100755 index 0000000..884ffd6 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_put.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_ruby.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_ruby.png new file mode 100755 index 0000000..f59b7c4 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_ruby.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_stack.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_stack.png new file mode 100755 index 0000000..44084ad Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_stack.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_star.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_star.png new file mode 100755 index 0000000..3a1441c Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_star.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_swoosh.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_swoosh.png new file mode 100755 index 0000000..e770829 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_swoosh.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_text.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_text.png new file mode 100755 index 0000000..813f712 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_text.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_text_width.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_text_width.png new file mode 100755 index 0000000..d9cf132 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_text_width.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_tux.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_tux.png new file mode 100755 index 0000000..52699bf Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_tux.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_vector.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_vector.png new file mode 100755 index 0000000..4a05955 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_vector.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_visualstudio.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_visualstudio.png new file mode 100755 index 0000000..a0a433d Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_visualstudio.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_width.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_width.png new file mode 100755 index 0000000..1eb8809 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_width.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_word.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_word.png new file mode 100755 index 0000000..ae8ecbf Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_word.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_world.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_world.png new file mode 100755 index 0000000..6ed2490 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_world.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_wrench.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_wrench.png new file mode 100755 index 0000000..fecadd0 Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_wrench.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_white_zip.png b/node_modules/express/node_modules/connect/lib/public/icons/page_white_zip.png new file mode 100755 index 0000000..fd4bbcc Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_white_zip.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_word.png b/node_modules/express/node_modules/connect/lib/public/icons/page_word.png new file mode 100755 index 0000000..834cdfa Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_word.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/icons/page_world.png b/node_modules/express/node_modules/connect/lib/public/icons/page_world.png new file mode 100755 index 0000000..b8895dd Binary files /dev/null and b/node_modules/express/node_modules/connect/lib/public/icons/page_world.png differ diff --git a/node_modules/express/node_modules/connect/lib/public/style.css b/node_modules/express/node_modules/connect/lib/public/style.css new file mode 100644 index 0000000..32b6507 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/public/style.css @@ -0,0 +1,141 @@ +body { + margin: 0; + padding: 80px 100px; + font: 13px "Helvetica Neue", "Lucida Grande", "Arial"; + background: #ECE9E9 -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ECE9E9)); + background: #ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9); + background-repeat: no-repeat; + color: #555; + -webkit-font-smoothing: antialiased; +} +h1, h2, h3 { + margin: 0; + font-size: 22px; + color: #343434; +} +h1 em, h2 em { + padding: 0 5px; + font-weight: normal; +} +h1 { + font-size: 60px; +} +h2 { + margin-top: 10px; +} +h3 { + margin: 5px 0 10px 0; + padding-bottom: 5px; + border-bottom: 1px solid #eee; + font-size: 18px; +} +ul { + margin: 0; + padding: 0; +} +ul li { + margin: 5px 0; + padding: 3px 8px; + list-style: none; +} +ul li:hover { + cursor: pointer; + color: #2e2e2e; +} +ul li .path { + padding-left: 5px; + font-weight: bold; +} +ul li .line { + padding-right: 5px; + font-style: italic; +} +ul li:first-child .path { + padding-left: 0; +} +p { + line-height: 1.5; +} +a { + color: #555; + text-decoration: none; +} +a:hover { + color: #303030; +} +#stacktrace { + margin-top: 15px; +} +.directory h1 { + margin-bottom: 15px; + font-size: 18px; +} +ul#files { + width: 100%; + height: 500px; +} +ul#files li { + padding: 0; +} +ul#files li img { + position: absolute; + top: 5px; + left: 5px; +} +ul#files li a { + position: relative; + display: block; + margin: 1px; + width: 30%; + height: 25px; + line-height: 25px; + text-indent: 8px; + float: left; + border: 1px solid transparent; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + overflow: hidden; + text-overflow: ellipsis; +} +ul#files li a.icon { + text-indent: 25px; +} +ul#files li a:focus, +ul#files li a:hover { + outline: none; + background: rgba(255,255,255,0.65); + border: 1px solid #ececec; +} +ul#files li a.highlight { + -webkit-transition: background .4s ease-in-out; + background: #ffff4f; + border-color: #E9DC51; +} +#search { + display: block; + position: fixed; + top: 20px; + right: 20px; + width: 90px; + -webkit-transition: width ease 0.2s, opacity ease 0.4s; + -moz-transition: width ease 0.2s, opacity ease 0.4s; + -webkit-border-radius: 32px; + -moz-border-radius: 32px; + -webkit-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03); + -moz-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03); + -webkit-font-smoothing: antialiased; + text-align: left; + font: 13px "Helvetica Neue", Arial, sans-serif; + padding: 4px 10px; + border: none; + background: transparent; + margin-bottom: 0; + outline: none; + opacity: 0.7; + color: #888; +} +#search:focus { + width: 120px; + opacity: 1.0; +} diff --git a/node_modules/express/node_modules/connect/lib/utils.js b/node_modules/express/node_modules/connect/lib/utils.js new file mode 100644 index 0000000..d0bc172 --- /dev/null +++ b/node_modules/express/node_modules/connect/lib/utils.js @@ -0,0 +1,451 @@ + +/*! + * Connect - utils + * Copyright(c) 2010 Sencha Inc. + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var crypto = require('crypto') + , Path = require('path') + , fs = require('fs'); + +/** + * Flatten the given `arr`. + * + * @param {Array} arr + * @return {Array} + * @api private + */ + +exports.flatten = function(arr, ret){ + var ret = ret || [] + , len = arr.length; + for (var i = 0; i < len; ++i) { + if (Array.isArray(arr[i])) { + exports.flatten(arr[i], ret); + } else { + ret.push(arr[i]); + } + } + return ret; +}; + +/** + * Return md5 hash of the given string and optional encoding, + * defaulting to hex. + * + * utils.md5('wahoo'); + * // => "e493298061761236c96b02ea6aa8a2ad" + * + * @param {String} str + * @param {String} encoding + * @return {String} + * @api public + */ + +exports.md5 = function(str, encoding){ + return crypto + .createHash('md5') + .update(str) + .digest(encoding || 'hex'); +}; + +/** + * Merge object b with object a. + * + * var a = { foo: 'bar' } + * , b = { bar: 'baz' }; + * + * utils.merge(a, b); + * // => { foo: 'bar', bar: 'baz' } + * + * @param {Object} a + * @param {Object} b + * @return {Object} + * @api public + */ + +exports.merge = function(a, b){ + if (a && b) { + for (var key in b) { + a[key] = b[key]; + } + } + return a; +}; + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api public + */ + +exports.escape = function(html){ + return String(html) + .replace(/&(?!\w+;)/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +}; + + +/** + * Return a unique identifier with the given `len`. + * + * utils.uid(10); + * // => "FDaS435D2z" + * + * @param {Number} len + * @return {String} + * @api public + */ + +exports.uid = function(len) { + var buf = [] + , chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + , charlen = chars.length; + + for (var i = 0; i < len; ++i) { + buf.push(chars[getRandomInt(0, charlen - 1)]); + } + + return buf.join(''); +}; + +/** + * Parse the given cookie string into an object. + * + * @param {String} str + * @return {Object} + * @api public + */ + +exports.parseCookie = function(str){ + var obj = {} + , pairs = str.split(/[;,] */); + for (var i = 0, len = pairs.length; i < len; ++i) { + var pair = pairs[i] + , eqlIndex = pair.indexOf('=') + , key = pair.substr(0, eqlIndex).trim().toLowerCase() + , val = pair.substr(++eqlIndex, pair.length).trim(); + + // quoted values + if ('"' == val[0]) val = val.slice(1, -1); + + // only assign once + if (undefined == obj[key]) { + val = val.replace(/\+/g, ' '); + try { + obj[key] = decodeURIComponent(val); + } catch (err) { + if (err instanceof URIError) { + obj[key] = val; + } else { + throw err; + } + } + } + } + return obj; +}; + +/** + * Serialize the given object into a cookie string. + * + * utils.serializeCookie('name', 'tj', { httpOnly: true }) + * // => "name=tj; httpOnly" + * + * @param {String} name + * @param {String} val + * @param {Object} obj + * @return {String} + * @api public + */ + +exports.serializeCookie = function(name, val, obj){ + var pairs = [name + '=' + encodeURIComponent(val)] + , obj = obj || {}; + + if (obj.domain) pairs.push('domain=' + obj.domain); + if (obj.path) pairs.push('path=' + obj.path); + if (obj.expires) pairs.push('expires=' + obj.expires.toUTCString()); + if (obj.httpOnly) pairs.push('httpOnly'); + if (obj.secure) pairs.push('secure'); + + return pairs.join('; '); +}; + +/** + * Pause `data` and `end` events on the given `obj`. + * Middleware performing async tasks _should_ utilize + * this utility (or similar), to re-emit data once + * the async operation has completed, otherwise these + * events may be lost. + * + * var pause = utils.pause(req); + * fs.readFile(path, function(){ + * next(); + * pause.resume(); + * }); + * + * @param {Object} obj + * @return {Object} + * @api public + */ + +exports.pause = function(obj){ + var onData + , onEnd + , events = []; + + // buffer data + obj.on('data', onData = function(data, encoding){ + events.push(['data', data, encoding]); + }); + + // buffer end + obj.on('end', onEnd = function(data, encoding){ + events.push(['end', data, encoding]); + }); + + return { + end: function(){ + obj.removeListener('data', onData); + obj.removeListener('end', onEnd); + }, + resume: function(){ + this.end(); + for (var i = 0, len = events.length; i < len; ++i) { + obj.emit.apply(obj, events[i]); + } + } + }; +}; + +/** + * Check `req` and `res` to see if it has been modified. + * + * @param {IncomingMessage} req + * @param {ServerResponse} res + * @return {Boolean} + * @api public + */ + +exports.modified = function(req, res, headers) { + var headers = headers || res._headers || {} + , modifiedSince = req.headers['if-modified-since'] + , lastModified = headers['last-modified'] + , noneMatch = req.headers['if-none-match'] + , etag = headers['etag']; + + if (noneMatch) noneMatch = noneMatch.split(/ *, */); + + // check If-None-Match + if (noneMatch && etag && ~noneMatch.indexOf(etag)) { + return false; + } + + // check If-Modified-Since + if (modifiedSince && lastModified) { + modifiedSince = new Date(modifiedSince); + lastModified = new Date(lastModified); + // Ignore invalid dates + if (!isNaN(modifiedSince.getTime())) { + if (lastModified <= modifiedSince) return false; + } + } + + return true; +}; + +/** + * Strip `Content-*` headers from `res`. + * + * @param {ServerResponse} res + * @api public + */ + +exports.removeContentHeaders = function(res){ + Object.keys(res._headers).forEach(function(field){ + if (0 == field.indexOf('content')) { + res.removeHeader(field); + } + }); +}; + +/** + * Check if `req` is a conditional GET request. + * + * @param {IncomingMessage} req + * @return {Boolean} + * @api public + */ + +exports.conditionalGET = function(req) { + return req.headers['if-modified-since'] + || req.headers['if-none-match']; +}; + +/** + * Respond with 403 "Forbidden". + * + * @param {ServerResponse} res + * @api public + */ + +exports.forbidden = function(res) { + var body = 'Forbidden'; + res.setHeader('Content-Type', 'text/plain'); + res.setHeader('Content-Length', body.length); + res.statusCode = 403; + res.end(body); +}; + +/** + * Respond with 401 "Unauthorized". + * + * @param {ServerResponse} res + * @param {String} realm + * @api public + */ + +exports.unauthorized = function(res, realm) { + res.statusCode = 401; + res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"'); + res.end('Unauthorized'); +}; + +/** + * Respond with 400 "Bad Request". + * + * @param {ServerResponse} res + * @api public + */ + +exports.badRequest = function(res) { + res.statusCode = 400; + res.end('Bad Request'); +}; + +/** + * Respond with 304 "Not Modified". + * + * @param {ServerResponse} res + * @param {Object} headers + * @api public + */ + +exports.notModified = function(res) { + exports.removeContentHeaders(res); + res.statusCode = 304; + res.end(); +}; + +/** + * Return an ETag in the form of `"-"` + * from the given `stat`. + * + * @param {Object} stat + * @return {String} + * @api public + */ + +exports.etag = function(stat) { + return '"' + stat.size + '-' + Number(stat.mtime) + '"'; +}; + +/** + * Parse "Range" header `str` relative to the given file `size`. + * + * @param {Number} size + * @param {String} str + * @return {Array} + * @api public + */ + +exports.parseRange = function(size, str){ + var valid = true; + var arr = str.substr(6).split(',').map(function(range){ + var range = range.split('-') + , start = parseInt(range[0], 10) + , end = parseInt(range[1], 10); + + // -500 + if (isNaN(start)) { + start = size - end; + end = size - 1; + // 500- + } else if (isNaN(end)) { + end = size - 1; + } + + // Invalid + if (isNaN(start) || isNaN(end) || start > end) valid = false; + + return { start: start, end: end }; + }); + return valid ? arr : undefined; +}; + +/** + * Parse the given Cache-Control `str`. + * + * @param {String} str + * @return {Object} + * @api public + */ + +exports.parseCacheControl = function(str){ + var directives = str.split(',') + , obj = {}; + + for(var i = 0, len = directives.length; i < len; i++) { + var parts = directives[i].split('=') + , key = parts.shift().trim() + , val = parseInt(parts.shift(), 10); + + obj[key] = isNaN(val) ? true : val; + } + + return obj; +}; + + +/** + * Convert array-like object to an `Array`. + * + * node-bench measured "16.5 times faster than Array.prototype.slice.call()" + * + * @param {Object} obj + * @return {Array} + * @api public + */ + +var toArray = exports.toArray = function(obj){ + var len = obj.length + , arr = new Array(len); + for (var i = 0; i < len; ++i) { + arr[i] = obj[i]; + } + return arr; +}; + +/** + * Retrun a random int, used by `utils.uid()` + * + * @param {Number} min + * @param {Number} max + * @return {Number} + * @api private + */ + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/.gitignore b/node_modules/express/node_modules/connect/node_modules/formidable/.gitignore new file mode 100644 index 0000000..b72f74f --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/.gitignore @@ -0,0 +1,4 @@ +/test/tmp +*.upload +*.un~ +/node_modules diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/.npmignore b/node_modules/express/node_modules/connect/node_modules/formidable/.npmignore new file mode 100644 index 0000000..4fbabb3 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/.npmignore @@ -0,0 +1,4 @@ +/test/tmp/ +*.upload +*.un~ +*.http diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/.travis.yml b/node_modules/express/node_modules/connect/node_modules/formidable/.travis.yml new file mode 100644 index 0000000..f1d0f13 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.4 + - 0.6 diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/Makefile b/node_modules/express/node_modules/connect/node_modules/formidable/Makefile new file mode 100644 index 0000000..8945872 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/Makefile @@ -0,0 +1,14 @@ +SHELL := /bin/bash + +test: + @./test/run.js + +build: npm test + +npm: + npm install . + +clean: + rm test/tmp/* + +.PHONY: test clean build diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/Readme.md b/node_modules/express/node_modules/connect/node_modules/formidable/Readme.md new file mode 100644 index 0000000..ca3c00a --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/Readme.md @@ -0,0 +1,286 @@ +# Formidable + +[![Build Status](https://secure.travis-ci.org/felixge/node-formidable.png)](http://travis-ci.org/felixge/node-formidable) + +## Purpose + +A node.js module for parsing form data, especially file uploads. + +## Current status + +This module was developed for [Transloadit](http://transloadit.com/), a service focused on uploading +and encoding images and videos. It has been battle-tested against hundreds of GB of file uploads from +a large variety of clients and is considered production-ready. + +## Features + +* Fast (~500mb/sec), non-buffering multipart parser +* Automatically writing file uploads to disk +* Low memory footprint +* Graceful error handling +* Very high test coverage + +## Changelog + +### v1.0.6 + +* Do not default to the default to the field name for file uploads where + filename="". + +### v1.0.5 + +* Support filename="" in multipart parts +* Explain unexpected end() errors in parser better + +**Note:** Starting with this version, formidable emits 'file' events for empty +file input fields. Previously those were incorrectly emitted as regular file +input fields with value = "". + +### v1.0.4 + +* Detect a good default tmp directory regardless of platform. (#88) + +### v1.0.3 + +* Fix problems with utf8 characters (#84) / semicolons in filenames (#58) +* Small performance improvements +* New test suite and fixture system + +### v1.0.2 + +* Exclude node\_modules folder from git +* Implement new `'aborted'` event +* Fix files in example folder to work with recent node versions +* Make gently a devDependency + +[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.1...v1.0.2) + +### v1.0.1 + +* Fix package.json to refer to proper main directory. (#68, Dean Landolt) + +[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.0...v1.0.1) + +### v1.0.0 + +* Add support for multipart boundaries that are quoted strings. (Jeff Craig) + +This marks the beginning of development on version 2.0 which will include +several architectural improvements. + +[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.11...v1.0.0) + +### v0.9.11 + +* Emit `'progress'` event when receiving data, regardless of parsing it. (Tim Koschützki) +* Use [W3C FileAPI Draft](http://dev.w3.org/2006/webapi/FileAPI/) properties for File class + +**Important:** The old property names of the File class will be removed in a +future release. + +[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.10...v0.9.11) + +### Older releases + +These releases were done before starting to maintain the above Changelog: + +* [v0.9.10](https://github.com/felixge/node-formidable/compare/v0.9.9...v0.9.10) +* [v0.9.9](https://github.com/felixge/node-formidable/compare/v0.9.8...v0.9.9) +* [v0.9.8](https://github.com/felixge/node-formidable/compare/v0.9.7...v0.9.8) +* [v0.9.7](https://github.com/felixge/node-formidable/compare/v0.9.6...v0.9.7) +* [v0.9.6](https://github.com/felixge/node-formidable/compare/v0.9.5...v0.9.6) +* [v0.9.5](https://github.com/felixge/node-formidable/compare/v0.9.4...v0.9.5) +* [v0.9.4](https://github.com/felixge/node-formidable/compare/v0.9.3...v0.9.4) +* [v0.9.3](https://github.com/felixge/node-formidable/compare/v0.9.2...v0.9.3) +* [v0.9.2](https://github.com/felixge/node-formidable/compare/v0.9.1...v0.9.2) +* [v0.9.1](https://github.com/felixge/node-formidable/compare/v0.9.0...v0.9.1) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.1.0](https://github.com/felixge/node-formidable/commits/v0.1.0) + +## Installation + +Via [npm](http://github.com/isaacs/npm): + + npm install formidable@latest + +Manually: + + git clone git://github.com/felixge/node-formidable.git formidable + vim my.js + # var formidable = require('./formidable'); + +Note: Formidable requires [gently](http://github.com/felixge/node-gently) to run the unit tests, but you won't need it for just using the library. + +## Example + +Parse an incoming file upload. + + var formidable = require('formidable'), + http = require('http'), + + sys = require('sys'); + + http.createServer(function(req, res) { + if (req.url == '/upload' && req.method.toLowerCase() == 'post') { + // parse a file upload + var form = new formidable.IncomingForm(); + form.parse(req, function(err, fields, files) { + res.writeHead(200, {'content-type': 'text/plain'}); + res.write('received upload:\n\n'); + res.end(sys.inspect({fields: fields, files: files})); + }); + return; + } + + // show a file upload form + res.writeHead(200, {'content-type': 'text/html'}); + res.end( + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    ' + ); + }).listen(80); + +## API + +### formidable.IncomingForm + +#### new formidable.IncomingForm() + +Creates a new incoming form. + +#### incomingForm.encoding = 'utf-8' + +The encoding to use for incoming form fields. + +#### incomingForm.uploadDir = process.env.TMP || '/tmp' || process.cwd() + +The directory for placing file uploads in. You can move them later on using +`fs.rename()`. The default directory is picked at module load time depending on +the first existing directory from those listed above. + +#### incomingForm.keepExtensions = false + +If you want the files written to `incomingForm.uploadDir` to include the extensions of the original files, set this property to `true`. + +#### incomingForm.type + +Either 'multipart' or 'urlencoded' depending on the incoming request. + +#### incomingForm.maxFieldsSize = 2 * 1024 * 1024 + +Limits the amount of memory a field (not file) can allocate in bytes. +If this value is exceeded, an `'error'` event is emitted. The default +size is 2MB. + +#### incomingForm.bytesReceived + +The amount of bytes received for this form so far. + +#### incomingForm.bytesExpected + +The expected number of bytes in this form. + +#### incomingForm.parse(request, [cb]) + +Parses an incoming node.js `request` containing form data. If `cb` is provided, all fields an files are collected and passed to the callback: + + incomingForm.parse(req, function(err, fields, files) { + // ... + }); + +#### incomingForm.onPart(part) + +You may overwrite this method if you are interested in directly accessing the multipart stream. Doing so will disable any `'field'` / `'file'` events processing which would occur otherwise, making you fully responsible for handling the processing. + + incomingForm.onPart = function(part) { + part.addListener('data', function() { + // ... + }); + } + +If you want to use formidable to only handle certain parts for you, you can do so: + + incomingForm.onPart = function(part) { + if (!part.filename) { + // let formidable handle all non-file parts + incomingForm.handlePart(part); + } + } + +Check the code in this method for further inspiration. + +#### Event: 'progress' (bytesReceived, bytesExpected) + +Emitted after each incoming chunk of data that has been parsed. Can be used to roll your own progress bar. + +#### Event: 'field' (name, value) + +Emitted whenever a field / value pair has been received. + +#### Event: 'fileBegin' (name, file) + +Emitted whenever a new file is detected in the upload stream. Use this even if +you want to stream the file to somewhere else while buffering the upload on +the file system. + +#### Event: 'file' (name, file) + +Emitted whenever a field / file pair has been received. `file` is an instance of `File`. + +#### Event: 'error' (err) + +Emitted when there is an error processing the incoming form. A request that experiences an error is automatically paused, you will have to manually call `request.resume()` if you want the request to continue firing `'data'` events. + +#### Event: 'aborted' + +Emitted when the request was aborted by the user. Right now this can be due to a 'timeout' or 'close' event on the socket. In the future there will be a separate 'timeout' event (needs a change in the node core). + +#### Event: 'end' () + +Emitted when the entire request has been received, and all contained files have finished flushing to disk. This is a great place for you to send your response. + +### formidable.File + +#### file.size = 0 + +The size of the uploaded file in bytes. If the file is still being uploaded (see `'fileBegin'` event), this property says how many bytes of the file have been written to disk yet. + +#### file.path = null + +The path this file is being written to. You can modify this in the `'fileBegin'` event in +case you are unhappy with the way formidable generates a temporary path for your files. + +#### file.name = null + +The name this file had according to the uploading client. + +#### file.type = null + +The mime type of this file, according to the uploading client. + +#### file.lastModifiedDate = null + +A date object (or `null`) containing the time this file was last written to. Mostly +here for compatibility with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/). + +## License + +Formidable is licensed under the MIT license. + +## Ports + +* [multipart-parser](http://github.com/FooBarWidget/multipart-parser): a C++ parser based on formidable + +## Credits + +* [Ryan Dahl](http://twitter.com/ryah) for his work on [http-parser](http://github.com/ry/http-parser) which heavily inspired multipart_parser.js diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/TODO b/node_modules/express/node_modules/connect/node_modules/formidable/TODO new file mode 100644 index 0000000..e1107f2 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/TODO @@ -0,0 +1,3 @@ +- Better bufferMaxSize handling approach +- Add tests for JSON parser pull request and merge it +- Implement QuerystringParser the same way as MultipartParser diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/benchmark/bench-multipart-parser.js b/node_modules/express/node_modules/connect/node_modules/formidable/benchmark/bench-multipart-parser.js new file mode 100644 index 0000000..bff41f1 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/benchmark/bench-multipart-parser.js @@ -0,0 +1,70 @@ +require('../test/common'); +var multipartParser = require('../lib/multipart_parser'), + MultipartParser = multipartParser.MultipartParser, + parser = new MultipartParser(), + Buffer = require('buffer').Buffer, + boundary = '-----------------------------168072824752491622650073', + mb = 100, + buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), + callbacks = + { partBegin: -1, + partEnd: -1, + headerField: -1, + headerValue: -1, + partData: -1, + end: -1, + }; + + +parser.initWithBoundary(boundary); +parser.onHeaderField = function() { + callbacks.headerField++; +}; + +parser.onHeaderValue = function() { + callbacks.headerValue++; +}; + +parser.onPartBegin = function() { + callbacks.partBegin++; +}; + +parser.onPartData = function() { + callbacks.partData++; +}; + +parser.onPartEnd = function() { + callbacks.partEnd++; +}; + +parser.onEnd = function() { + callbacks.end++; +}; + +var start = +new Date(), + nparsed = parser.write(buffer), + duration = +new Date - start, + mbPerSec = (mb / (duration / 1000)).toFixed(2); + +console.log(mbPerSec+' mb/sec'); + +assert.equal(nparsed, buffer.length); + +function createMultipartBuffer(boundary, size) { + var head = + '--'+boundary+'\r\n' + + 'content-disposition: form-data; name="field1"\r\n' + + '\r\n' + , tail = '\r\n--'+boundary+'--\r\n' + , buffer = new Buffer(size); + + buffer.write(head, 'ascii', 0); + buffer.write(tail, 'ascii', buffer.length - tail.length); + return buffer; +} + +process.on('exit', function() { + for (var k in callbacks) { + assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); + } +}); diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/example/post.js b/node_modules/express/node_modules/connect/node_modules/formidable/example/post.js new file mode 100644 index 0000000..f6c15a6 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/example/post.js @@ -0,0 +1,43 @@ +require('../test/common'); +var http = require('http'), + util = require('util'), + formidable = require('formidable'), + server; + +server = http.createServer(function(req, res) { + if (req.url == '/') { + res.writeHead(200, {'content-type': 'text/html'}); + res.end( + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    ' + ); + } else if (req.url == '/post') { + var form = new formidable.IncomingForm(), + fields = []; + + form + .on('error', function(err) { + res.writeHead(200, {'content-type': 'text/plain'}); + res.end('error:\n\n'+util.inspect(err)); + }) + .on('field', function(field, value) { + console.log(field, value); + fields.push([field, value]); + }) + .on('end', function() { + console.log('-> post done'); + res.writeHead(200, {'content-type': 'text/plain'}); + res.end('received fields:\n\n '+util.inspect(fields)); + }); + form.parse(req); + } else { + res.writeHead(404, {'content-type': 'text/plain'}); + res.end('404'); + } +}); +server.listen(TEST_PORT); + +console.log('listening on http://localhost:'+TEST_PORT+'/'); diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/example/upload.js b/node_modules/express/node_modules/connect/node_modules/formidable/example/upload.js new file mode 100644 index 0000000..050cdd9 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/example/upload.js @@ -0,0 +1,48 @@ +require('../test/common'); +var http = require('http'), + util = require('util'), + formidable = require('formidable'), + server; + +server = http.createServer(function(req, res) { + if (req.url == '/') { + res.writeHead(200, {'content-type': 'text/html'}); + res.end( + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    ' + ); + } else if (req.url == '/upload') { + var form = new formidable.IncomingForm(), + files = [], + fields = []; + + form.uploadDir = TEST_TMP; + + form + .on('field', function(field, value) { + console.log(field, value); + fields.push([field, value]); + }) + .on('file', function(field, file) { + console.log(field, file); + files.push([field, file]); + }) + .on('end', function() { + console.log('-> upload done'); + res.writeHead(200, {'content-type': 'text/plain'}); + res.write('received fields:\n\n '+util.inspect(fields)); + res.write('\n\n'); + res.end('received files:\n\n '+util.inspect(files)); + }); + form.parse(req); + } else { + res.writeHead(404, {'content-type': 'text/plain'}); + res.end('404'); + } +}); +server.listen(TEST_PORT); + +console.log('listening on http://localhost:'+TEST_PORT+'/'); diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/index.js b/node_modules/express/node_modules/connect/node_modules/formidable/index.js new file mode 100644 index 0000000..be41032 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/formidable'); \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/lib/file.js b/node_modules/express/node_modules/connect/node_modules/formidable/lib/file.js new file mode 100644 index 0000000..6dc8720 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/lib/file.js @@ -0,0 +1,61 @@ +if (global.GENTLY) require = GENTLY.hijack(require); + +var util = require('./util'), + WriteStream = require('fs').WriteStream, + EventEmitter = require('events').EventEmitter; + +function File(properties) { + EventEmitter.call(this); + + this.size = 0; + this.path = null; + this.name = null; + this.type = null; + this.lastModifiedDate = null; + + this._writeStream = null; + + for (var key in properties) { + this[key] = properties[key]; + } + + this._backwardsCompatibility(); +} +module.exports = File; +util.inherits(File, EventEmitter); + +// @todo Next release: Show error messages when accessing these +File.prototype._backwardsCompatibility = function() { + var self = this; + this.__defineGetter__('length', function() { + return self.size; + }); + this.__defineGetter__('filename', function() { + return self.name; + }); + this.__defineGetter__('mime', function() { + return self.type; + }); +}; + +File.prototype.open = function() { + this._writeStream = new WriteStream(this.path); +}; + +File.prototype.write = function(buffer, cb) { + var self = this; + this._writeStream.write(buffer, function() { + self.lastModifiedDate = new Date(); + self.size += buffer.length; + self.emit('progress', self.size); + cb(); + }); +}; + +File.prototype.end = function(cb) { + var self = this; + this._writeStream.end(function() { + self.emit('end'); + cb(); + }); +}; diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/lib/incoming_form.js b/node_modules/express/node_modules/connect/node_modules/formidable/lib/incoming_form.js new file mode 100644 index 0000000..7b1ddaf --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/lib/incoming_form.js @@ -0,0 +1,377 @@ +if (global.GENTLY) require = GENTLY.hijack(require); + +var fs = require('fs'); +var util = require('./util'), + path = require('path'), + File = require('./file'), + MultipartParser = require('./multipart_parser').MultipartParser, + QuerystringParser = require('./querystring_parser').QuerystringParser, + StringDecoder = require('string_decoder').StringDecoder, + EventEmitter = require('events').EventEmitter; + +function IncomingForm() { + if (!(this instanceof IncomingForm)) return new IncomingForm; + EventEmitter.call(this); + + this.error = null; + this.ended = false; + + this.maxFieldsSize = 2 * 1024 * 1024; + this.keepExtensions = false; + this.uploadDir = IncomingForm.UPLOAD_DIR; + this.encoding = 'utf-8'; + this.headers = null; + this.type = null; + + this.bytesReceived = null; + this.bytesExpected = null; + + this._parser = null; + this._flushing = 0; + this._fieldsSize = 0; +}; +util.inherits(IncomingForm, EventEmitter); +exports.IncomingForm = IncomingForm; + +IncomingForm.UPLOAD_DIR = (function() { + var dirs = [process.env.TMP, '/tmp', process.cwd()]; + for (var i = 0; i < dirs.length; i++) { + var dir = dirs[i]; + var isDirectory = false; + + try { + isDirectory = fs.statSync(dir).isDirectory(); + } catch (e) {} + + if (isDirectory) return dir; + } +})(); + +IncomingForm.prototype.parse = function(req, cb) { + this.pause = function() { + try { + req.pause(); + } catch (err) { + // the stream was destroyed + if (!this.ended) { + // before it was completed, crash & burn + this._error(err); + } + return false; + } + return true; + }; + + this.resume = function() { + try { + req.resume(); + } catch (err) { + // the stream was destroyed + if (!this.ended) { + // before it was completed, crash & burn + this._error(err); + } + return false; + } + + return true; + }; + + this.writeHeaders(req.headers); + + var self = this; + req + .on('error', function(err) { + self._error(err); + }) + .on('aborted', function() { + self.emit('aborted'); + }) + .on('data', function(buffer) { + self.write(buffer); + }) + .on('end', function() { + if (self.error) { + return; + } + + var err = self._parser.end(); + if (err) { + self._error(err); + } + }); + + if (cb) { + var fields = {}, files = {}; + this + .on('field', function(name, value) { + fields[name] = value; + }) + .on('file', function(name, file) { + files[name] = file; + }) + .on('error', function(err) { + cb(err, fields, files); + }) + .on('end', function() { + cb(null, fields, files); + }); + } + + return this; +}; + +IncomingForm.prototype.writeHeaders = function(headers) { + this.headers = headers; + this._parseContentLength(); + this._parseContentType(); +}; + +IncomingForm.prototype.write = function(buffer) { + if (!this._parser) { + this._error(new Error('unintialized parser')); + return; + } + + this.bytesReceived += buffer.length; + this.emit('progress', this.bytesReceived, this.bytesExpected); + + var bytesParsed = this._parser.write(buffer); + if (bytesParsed !== buffer.length) { + this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed')); + } + + return bytesParsed; +}; + +IncomingForm.prototype.pause = function() { + // this does nothing, unless overwritten in IncomingForm.parse + return false; +}; + +IncomingForm.prototype.resume = function() { + // this does nothing, unless overwritten in IncomingForm.parse + return false; +}; + +IncomingForm.prototype.onPart = function(part) { + // this method can be overwritten by the user + this.handlePart(part); +}; + +IncomingForm.prototype.handlePart = function(part) { + var self = this; + + if (part.filename === undefined) { + var value = '' + , decoder = new StringDecoder(this.encoding); + + part.on('data', function(buffer) { + self._fieldsSize += buffer.length; + if (self._fieldsSize > self.maxFieldsSize) { + self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data')); + return; + } + value += decoder.write(buffer); + }); + + part.on('end', function() { + self.emit('field', part.name, value); + }); + return; + } + + this._flushing++; + + var file = new File({ + path: this._uploadPath(part.filename), + name: part.filename, + type: part.mime, + }); + + this.emit('fileBegin', part.name, file); + + file.open(); + + part.on('data', function(buffer) { + self.pause(); + file.write(buffer, function() { + self.resume(); + }); + }); + + part.on('end', function() { + file.end(function() { + self._flushing--; + self.emit('file', part.name, file); + self._maybeEnd(); + }); + }); +}; + +IncomingForm.prototype._parseContentType = function() { + if (!this.headers['content-type']) { + this._error(new Error('bad content-type header, no content-type')); + return; + } + + if (this.headers['content-type'].match(/urlencoded/i)) { + this._initUrlencoded(); + return; + } + + if (this.headers['content-type'].match(/multipart/i)) { + var m; + if (m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i)) { + this._initMultipart(m[1] || m[2]); + } else { + this._error(new Error('bad content-type header, no multipart boundary')); + } + return; + } + + this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type'])); +}; + +IncomingForm.prototype._error = function(err) { + if (this.error) { + return; + } + + this.error = err; + this.pause(); + this.emit('error', err); +}; + +IncomingForm.prototype._parseContentLength = function() { + if (this.headers['content-length']) { + this.bytesReceived = 0; + this.bytesExpected = parseInt(this.headers['content-length'], 10); + } +}; + +IncomingForm.prototype._newParser = function() { + return new MultipartParser(); +}; + +IncomingForm.prototype._initMultipart = function(boundary) { + this.type = 'multipart'; + + var parser = new MultipartParser(), + self = this, + headerField, + headerValue, + part; + + parser.initWithBoundary(boundary); + + parser.onPartBegin = function() { + part = new EventEmitter(); + part.headers = {}; + part.name = null; + part.filename = null; + part.mime = null; + headerField = ''; + headerValue = ''; + }; + + parser.onHeaderField = function(b, start, end) { + headerField += b.toString(self.encoding, start, end); + }; + + parser.onHeaderValue = function(b, start, end) { + headerValue += b.toString(self.encoding, start, end); + }; + + parser.onHeaderEnd = function() { + headerField = headerField.toLowerCase(); + part.headers[headerField] = headerValue; + + var m; + if (headerField == 'content-disposition') { + if (m = headerValue.match(/name="([^"]+)"/i)) { + part.name = m[1]; + } + + part.filename = self._fileName(headerValue); + } else if (headerField == 'content-type') { + part.mime = headerValue; + } + + headerField = ''; + headerValue = ''; + }; + + parser.onHeadersEnd = function() { + self.onPart(part); + }; + + parser.onPartData = function(b, start, end) { + part.emit('data', b.slice(start, end)); + }; + + parser.onPartEnd = function() { + part.emit('end'); + }; + + parser.onEnd = function() { + self.ended = true; + self._maybeEnd(); + }; + + this._parser = parser; +}; + +IncomingForm.prototype._fileName = function(headerValue) { + var m = headerValue.match(/filename="(.*?)"($|; )/i) + if (!m) return; + + var filename = m[1].substr(m[1].lastIndexOf('\\') + 1); + filename = filename.replace(/%22/g, '"'); + filename = filename.replace(/&#([\d]{4});/g, function(m, code) { + return String.fromCharCode(code); + }); + return filename; +}; + +IncomingForm.prototype._initUrlencoded = function() { + this.type = 'urlencoded'; + + var parser = new QuerystringParser() + , self = this; + + parser.onField = function(key, val) { + self.emit('field', key, val); + }; + + parser.onEnd = function() { + self.ended = true; + self._maybeEnd(); + }; + + this._parser = parser; +}; + +IncomingForm.prototype._uploadPath = function(filename) { + var name = ''; + for (var i = 0; i < 32; i++) { + name += Math.floor(Math.random() * 16).toString(16); + } + + if (this.keepExtensions) { + var ext = path.extname(filename); + ext = ext.replace(/(\.[a-z0-9]+).*/, '$1') + + name += ext; + } + + return path.join(this.uploadDir, name); +}; + +IncomingForm.prototype._maybeEnd = function() { + if (!this.ended || this._flushing) { + return; + } + + this.emit('end'); +}; diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/lib/index.js b/node_modules/express/node_modules/connect/node_modules/formidable/lib/index.js new file mode 100644 index 0000000..7a6e3e1 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/lib/index.js @@ -0,0 +1,3 @@ +var IncomingForm = require('./incoming_form').IncomingForm; +IncomingForm.IncomingForm = IncomingForm; +module.exports = IncomingForm; diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/lib/multipart_parser.js b/node_modules/express/node_modules/connect/node_modules/formidable/lib/multipart_parser.js new file mode 100644 index 0000000..9ca567c --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/lib/multipart_parser.js @@ -0,0 +1,312 @@ +var Buffer = require('buffer').Buffer, + s = 0, + S = + { PARSER_UNINITIALIZED: s++, + START: s++, + START_BOUNDARY: s++, + HEADER_FIELD_START: s++, + HEADER_FIELD: s++, + HEADER_VALUE_START: s++, + HEADER_VALUE: s++, + HEADER_VALUE_ALMOST_DONE: s++, + HEADERS_ALMOST_DONE: s++, + PART_DATA_START: s++, + PART_DATA: s++, + PART_END: s++, + END: s++, + }, + + f = 1, + F = + { PART_BOUNDARY: f, + LAST_BOUNDARY: f *= 2, + }, + + LF = 10, + CR = 13, + SPACE = 32, + HYPHEN = 45, + COLON = 58, + A = 97, + Z = 122, + + lower = function(c) { + return c | 0x20; + }; + +for (var s in S) { + exports[s] = S[s]; +} + +function MultipartParser() { + this.boundary = null; + this.boundaryChars = null; + this.lookbehind = null; + this.state = S.PARSER_UNINITIALIZED; + + this.index = null; + this.flags = 0; +}; +exports.MultipartParser = MultipartParser; + +MultipartParser.stateToString = function(stateNumber) { + for (var state in S) { + var number = S[state]; + if (number === stateNumber) return state; + } +}; + +MultipartParser.prototype.initWithBoundary = function(str) { + this.boundary = new Buffer(str.length+4); + this.boundary.write('\r\n--', 'ascii', 0); + this.boundary.write(str, 'ascii', 4); + this.lookbehind = new Buffer(this.boundary.length+8); + this.state = S.START; + + this.boundaryChars = {}; + for (var i = 0; i < this.boundary.length; i++) { + this.boundaryChars[this.boundary[i]] = true; + } +}; + +MultipartParser.prototype.write = function(buffer) { + var self = this, + i = 0, + len = buffer.length, + prevIndex = this.index, + index = this.index, + state = this.state, + flags = this.flags, + lookbehind = this.lookbehind, + boundary = this.boundary, + boundaryChars = this.boundaryChars, + boundaryLength = this.boundary.length, + boundaryEnd = boundaryLength - 1, + bufferLength = buffer.length, + c, + cl, + + mark = function(name) { + self[name+'Mark'] = i; + }, + clear = function(name) { + delete self[name+'Mark']; + }, + callback = function(name, buffer, start, end) { + if (start !== undefined && start === end) { + return; + } + + var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1); + if (callbackSymbol in self) { + self[callbackSymbol](buffer, start, end); + } + }, + dataCallback = function(name, clear) { + var markSymbol = name+'Mark'; + if (!(markSymbol in self)) { + return; + } + + if (!clear) { + callback(name, buffer, self[markSymbol], buffer.length); + self[markSymbol] = 0; + } else { + callback(name, buffer, self[markSymbol], i); + delete self[markSymbol]; + } + }; + + for (i = 0; i < len; i++) { + c = buffer[i]; + switch (state) { + case S.PARSER_UNINITIALIZED: + return i; + case S.START: + index = 0; + state = S.START_BOUNDARY; + case S.START_BOUNDARY: + if (index == boundary.length - 2) { + if (c != CR) { + return i; + } + index++; + break; + } else if (index - 1 == boundary.length - 2) { + if (c != LF) { + return i; + } + index = 0; + callback('partBegin'); + state = S.HEADER_FIELD_START; + break; + } + + if (c != boundary[index+2]) { + return i; + } + index++; + break; + case S.HEADER_FIELD_START: + state = S.HEADER_FIELD; + mark('headerField'); + index = 0; + case S.HEADER_FIELD: + if (c == CR) { + clear('headerField'); + state = S.HEADERS_ALMOST_DONE; + break; + } + + index++; + if (c == HYPHEN) { + break; + } + + if (c == COLON) { + if (index == 1) { + // empty header field + return i; + } + dataCallback('headerField', true); + state = S.HEADER_VALUE_START; + break; + } + + cl = lower(c); + if (cl < A || cl > Z) { + return i; + } + break; + case S.HEADER_VALUE_START: + if (c == SPACE) { + break; + } + + mark('headerValue'); + state = S.HEADER_VALUE; + case S.HEADER_VALUE: + if (c == CR) { + dataCallback('headerValue', true); + callback('headerEnd'); + state = S.HEADER_VALUE_ALMOST_DONE; + } + break; + case S.HEADER_VALUE_ALMOST_DONE: + if (c != LF) { + return i; + } + state = S.HEADER_FIELD_START; + break; + case S.HEADERS_ALMOST_DONE: + if (c != LF) { + return i; + } + + callback('headersEnd'); + state = S.PART_DATA_START; + break; + case S.PART_DATA_START: + state = S.PART_DATA + mark('partData'); + case S.PART_DATA: + prevIndex = index; + + if (index == 0) { + // boyer-moore derrived algorithm to safely skip non-boundary data + i += boundaryEnd; + while (i < bufferLength && !(buffer[i] in boundaryChars)) { + i += boundaryLength; + } + i -= boundaryEnd; + c = buffer[i]; + } + + if (index < boundary.length) { + if (boundary[index] == c) { + if (index == 0) { + dataCallback('partData', true); + } + index++; + } else { + index = 0; + } + } else if (index == boundary.length) { + index++; + if (c == CR) { + // CR = part boundary + flags |= F.PART_BOUNDARY; + } else if (c == HYPHEN) { + // HYPHEN = end boundary + flags |= F.LAST_BOUNDARY; + } else { + index = 0; + } + } else if (index - 1 == boundary.length) { + if (flags & F.PART_BOUNDARY) { + index = 0; + if (c == LF) { + // unset the PART_BOUNDARY flag + flags &= ~F.PART_BOUNDARY; + callback('partEnd'); + callback('partBegin'); + state = S.HEADER_FIELD_START; + break; + } + } else if (flags & F.LAST_BOUNDARY) { + if (c == HYPHEN) { + callback('partEnd'); + callback('end'); + state = S.END; + } else { + index = 0; + } + } else { + index = 0; + } + } + + if (index > 0) { + // when matching a possible boundary, keep a lookbehind reference + // in case it turns out to be a false lead + lookbehind[index-1] = c; + } else if (prevIndex > 0) { + // if our boundary turned out to be rubbish, the captured lookbehind + // belongs to partData + callback('partData', lookbehind, 0, prevIndex); + prevIndex = 0; + mark('partData'); + + // reconsider the current character even so it interrupted the sequence + // it could be the beginning of a new sequence + i--; + } + + break; + case S.END: + break; + default: + return i; + } + } + + dataCallback('headerField'); + dataCallback('headerValue'); + dataCallback('partData'); + + this.index = index; + this.state = state; + this.flags = flags; + + return len; +}; + +MultipartParser.prototype.end = function() { + if (this.state != S.END) { + return new Error('MultipartParser.end(): stream ended unexpectedly: ' + this.explain()); + } +}; + +MultipartParser.prototype.explain = function() { + return 'state = ' + MultipartParser.stateToString(this.state); +}; diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/lib/querystring_parser.js b/node_modules/express/node_modules/connect/node_modules/formidable/lib/querystring_parser.js new file mode 100644 index 0000000..63f109e --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/lib/querystring_parser.js @@ -0,0 +1,25 @@ +if (global.GENTLY) require = GENTLY.hijack(require); + +// This is a buffering parser, not quite as nice as the multipart one. +// If I find time I'll rewrite this to be fully streaming as well +var querystring = require('querystring'); + +function QuerystringParser() { + this.buffer = ''; +}; +exports.QuerystringParser = QuerystringParser; + +QuerystringParser.prototype.write = function(buffer) { + this.buffer += buffer.toString('ascii'); + return buffer.length; +}; + +QuerystringParser.prototype.end = function() { + var fields = querystring.parse(this.buffer); + for (var field in fields) { + this.onField(field, fields[field]); + } + this.buffer = ''; + + this.onEnd(); +}; \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/lib/util.js b/node_modules/express/node_modules/connect/node_modules/formidable/lib/util.js new file mode 100644 index 0000000..e9493e9 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/lib/util.js @@ -0,0 +1,6 @@ +// Backwards compatibility ... +try { + module.exports = require('util'); +} catch (e) { + module.exports = require('sys'); +} diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/package.json b/node_modules/express/node_modules/connect/node_modules/formidable/package.json new file mode 100644 index 0000000..01fffa7 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/package.json @@ -0,0 +1,22 @@ +{ + "name": "formidable", + "version": "1.0.8", + "dependencies": {}, + "devDependencies": { + "gently": "0.8.0", + "findit": "0.1.1", + "hashish": "0.0.4", + "urun": "0.0.4", + "utest": "0.0.3" + }, + "directories": { + "lib": "./lib" + }, + "main": "./lib/index", + "scripts": { + "test": "make test" + }, + "engines": { + "node": "*" + } +} \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/common.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/common.js new file mode 100644 index 0000000..eb432ad --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/common.js @@ -0,0 +1,19 @@ +var mysql = require('..'); +var path = require('path'); + +var root = path.join(__dirname, '../'); +exports.dir = { + root : root, + lib : root + '/lib', + fixture : root + '/test/fixture', + tmp : root + '/test/tmp', +}; + +exports.port = 13532; + +exports.formidable = require('..'); +exports.assert = require('assert'); + +exports.require = function(lib) { + return require(exports.dir.lib + '/' + lib); +}; diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/file/funkyfilename.txt b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/file/funkyfilename.txt new file mode 100644 index 0000000..e7a4785 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/file/funkyfilename.txt @@ -0,0 +1 @@ +I am a text file with a funky name! diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/file/plain.txt b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/file/plain.txt new file mode 100644 index 0000000..9b6903e --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/file/plain.txt @@ -0,0 +1 @@ +I am a plain text file diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/no-filename/generic.http b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/no-filename/generic.http new file mode 100644 index 0000000..e0dee27 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/no-filename/generic.http @@ -0,0 +1,13 @@ +POST /upload HTTP/1.1 +Host: localhost:8080 +Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG +Content-Length: 1000 + +------WebKitFormBoundarytyE4wkKlZ5CQJVTG +Content-Disposition: form-data; name="upload"; filename="" +Content-Type: text/plain + +I am a plain text file + +------WebKitFormBoundarytyE4wkKlZ5CQJVTG-- + diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/info.md b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/info.md new file mode 100644 index 0000000..3c9dbe3 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/info.md @@ -0,0 +1,3 @@ +* Opera does not allow submitting this file, it shows a warning to the + user that the file could not be found instead. Tested in 9.8, 11.51 on OSX. + Reported to Opera on 08.09.2011 (tracking email DSK-346009@bugs.opera.com). diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-chrome-13.http b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-chrome-13.http new file mode 100644 index 0000000..4ef3917 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-chrome-13.http @@ -0,0 +1,26 @@ +POST /upload HTTP/1.1 +Host: localhost:8080 +Connection: keep-alive +Referer: http://localhost:8080/ +Content-Length: 383 +Cache-Control: max-age=0 +Origin: http://localhost:8080 +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1 +Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytyE4wkKlZ5CQJVTG +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Accept-Encoding: gzip,deflate,sdch +Accept-Language: en-US,en;q=0.8 +Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 +Cookie: jqCookieJar_tablesorter=%7B%22showListTable%22%3A%5B%5B5%2C1%5D%2C%5B1%2C0%5D%5D%7D + +------WebKitFormBoundarytyE4wkKlZ5CQJVTG +Content-Disposition: form-data; name="title" + +Weird filename +------WebKitFormBoundarytyE4wkKlZ5CQJVTG +Content-Disposition: form-data; name="upload"; filename=": \ ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" +Content-Type: text/plain + +I am a text file with a funky name! + +------WebKitFormBoundarytyE4wkKlZ5CQJVTG-- diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http new file mode 100644 index 0000000..bf49f85 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http @@ -0,0 +1,24 @@ +POST /upload HTTP/1.1 +Host: localhost:8080 +User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Accept-Language: en-us,en;q=0.5 +Accept-Encoding: gzip,deflate +Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 +Keep-Alive: 115 +Connection: keep-alive +Referer: http://localhost:8080/ +Content-Type: multipart/form-data; boundary=---------------------------9849436581144108930470211272 +Content-Length: 438 + +-----------------------------9849436581144108930470211272 +Content-Disposition: form-data; name="title" + +Weird filename +-----------------------------9849436581144108930470211272 +Content-Disposition: form-data; name="upload"; filename=": \ ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" +Content-Type: text/plain + +I am a text file with a funky name! + +-----------------------------9849436581144108930470211272-- diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-safari-5.http b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-safari-5.http new file mode 100644 index 0000000..ff158a4 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-safari-5.http @@ -0,0 +1,23 @@ +POST /upload HTTP/1.1 +Host: localhost:8080 +Origin: http://localhost:8080 +Content-Length: 383 +User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1 +Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQJZ1gvhvdgfisJPJ +Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 +Referer: http://localhost:8080/ +Accept-Language: en-us +Accept-Encoding: gzip, deflate +Connection: keep-alive + +------WebKitFormBoundaryQJZ1gvhvdgfisJPJ +Content-Disposition: form-data; name="title" + +Weird filename +------WebKitFormBoundaryQJZ1gvhvdgfisJPJ +Content-Disposition: form-data; name="upload"; filename=": \ ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" +Content-Type: text/plain + +I am a text file with a funky name! + +------WebKitFormBoundaryQJZ1gvhvdgfisJPJ-- diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-chrome-12.http b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-chrome-12.http new file mode 100644 index 0000000..f0fc533 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-chrome-12.http @@ -0,0 +1,24 @@ +POST /upload HTTP/1.1 +Host: 192.168.56.1:8080 +Connection: keep-alive +Referer: http://192.168.56.1:8080/ +Content-Length: 344 +Cache-Control: max-age=0 +Origin: http://192.168.56.1:8080 +User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30 +Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryEvqBNplR3ByrwQPa +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Accept-Encoding: gzip,deflate,sdch +Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4 +Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 + +------WebKitFormBoundaryEvqBNplR3ByrwQPa +Content-Disposition: form-data; name="title" + +Weird filename +------WebKitFormBoundaryEvqBNplR3ByrwQPa +Content-Disposition: form-data; name="upload"; filename=" ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" +Content-Type: text/plain + + +------WebKitFormBoundaryEvqBNplR3ByrwQPa-- diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-7.http b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-7.http new file mode 100644 index 0000000..2e2c61c --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-7.http @@ -0,0 +1,22 @@ +POST /upload HTTP/1.1 +Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, */* +Referer: http://192.168.56.1:8080/ +Accept-Language: de +User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1) +Content-Type: multipart/form-data; boundary=---------------------------7db1fe232017c +Accept-Encoding: gzip, deflate +Host: 192.168.56.1:8080 +Content-Length: 368 +Connection: Keep-Alive +Cache-Control: no-cache + +-----------------------------7db1fe232017c +Content-Disposition: form-data; name="title" + +Weird filename +-----------------------------7db1fe232017c +Content-Disposition: form-data; name="upload"; filename=" ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" +Content-Type: application/octet-stream + + +-----------------------------7db1fe232017c-- diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-8.http b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-8.http new file mode 100644 index 0000000..e2b94fa --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-8.http @@ -0,0 +1,22 @@ +POST /upload HTTP/1.1 +Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, */* +Referer: http://192.168.56.1:8080/ +Accept-Language: de +User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0) +Content-Type: multipart/form-data; boundary=---------------------------7db3a8372017c +Accept-Encoding: gzip, deflate +Host: 192.168.56.1:8080 +Content-Length: 368 +Connection: Keep-Alive +Cache-Control: no-cache + +-----------------------------7db3a8372017c +Content-Disposition: form-data; name="title" + +Weird filename +-----------------------------7db3a8372017c +Content-Disposition: form-data; name="upload"; filename=" ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" +Content-Type: application/octet-stream + + +-----------------------------7db3a8372017c-- diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-safari-5.http b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-safari-5.http new file mode 100644 index 0000000..6379ac0 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-safari-5.http @@ -0,0 +1,22 @@ +POST /upload HTTP/1.1 +Host: 192.168.56.1:8080 +Referer: http://192.168.56.1:8080/ +Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 +Accept-Language: en-US +Origin: http://192.168.56.1:8080 +User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4 +Accept-Encoding: gzip, deflate +Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykmaWSUbu697WN9TM +Content-Length: 344 +Connection: keep-alive + +------WebKitFormBoundarykmaWSUbu697WN9TM +Content-Disposition: form-data; name="title" + +Weird filename +------WebKitFormBoundarykmaWSUbu697WN9TM +Content-Disposition: form-data; name="upload"; filename=" ? % * | %22 < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt" +Content-Type: text/plain + + +------WebKitFormBoundarykmaWSUbu697WN9TM-- diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/js/no-filename.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/js/no-filename.js new file mode 100644 index 0000000..0bae449 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/js/no-filename.js @@ -0,0 +1,3 @@ +module.exports['generic.http'] = [ + {type: 'file', name: 'upload', filename: '', fixture: 'plain.txt'}, +]; diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/js/special-chars-in-filename.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/js/special-chars-in-filename.js new file mode 100644 index 0000000..eb76fdc --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/js/special-chars-in-filename.js @@ -0,0 +1,21 @@ +var properFilename = 'funkyfilename.txt'; + +function expect(filename) { + return [ + {type: 'field', name: 'title', value: 'Weird filename'}, + {type: 'file', name: 'upload', filename: filename, fixture: properFilename}, + ]; +}; + +var webkit = " ? % * | \" < > . ? ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"; +var ffOrIe = " ? % * | \" < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"; + +module.exports = { + 'osx-chrome-13.http' : expect(webkit), + 'osx-firefox-3.6.http' : expect(ffOrIe), + 'osx-safari-5.http' : expect(webkit), + 'xp-chrome-12.http' : expect(webkit), + 'xp-ie-7.http' : expect(ffOrIe), + 'xp-ie-8.http' : expect(ffOrIe), + 'xp-safari-5.http' : expect(webkit), +}; diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/multipart.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/multipart.js new file mode 100644 index 0000000..a476169 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/multipart.js @@ -0,0 +1,72 @@ +exports['rfc1867'] = + { boundary: 'AaB03x', + raw: + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="field1"\r\n'+ + '\r\n'+ + 'Joe Blow\r\nalmost tricked you!\r\n'+ + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n'+ + 'Content-Type: text/plain\r\n'+ + '\r\n'+ + '... contents of file1.txt ...\r\r\n'+ + '--AaB03x--\r\n', + parts: + [ { headers: { + 'content-disposition': 'form-data; name="field1"', + }, + data: 'Joe Blow\r\nalmost tricked you!', + }, + { headers: { + 'content-disposition': 'form-data; name="pics"; filename="file1.txt"', + 'Content-Type': 'text/plain', + }, + data: '... contents of file1.txt ...\r', + } + ] + }; + +exports['noTrailing\r\n'] = + { boundary: 'AaB03x', + raw: + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="field1"\r\n'+ + '\r\n'+ + 'Joe Blow\r\nalmost tricked you!\r\n'+ + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n'+ + 'Content-Type: text/plain\r\n'+ + '\r\n'+ + '... contents of file1.txt ...\r\r\n'+ + '--AaB03x--', + parts: + [ { headers: { + 'content-disposition': 'form-data; name="field1"', + }, + data: 'Joe Blow\r\nalmost tricked you!', + }, + { headers: { + 'content-disposition': 'form-data; name="pics"; filename="file1.txt"', + 'Content-Type': 'text/plain', + }, + data: '... contents of file1.txt ...\r', + } + ] + }; + +exports['emptyHeader'] = + { boundary: 'AaB03x', + raw: + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="field1"\r\n'+ + ': foo\r\n'+ + '\r\n'+ + 'Joe Blow\r\nalmost tricked you!\r\n'+ + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n'+ + 'Content-Type: text/plain\r\n'+ + '\r\n'+ + '... contents of file1.txt ...\r\r\n'+ + '--AaB03x--\r\n', + expectError: true, + }; diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/integration/test-fixtures.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/integration/test-fixtures.js new file mode 100644 index 0000000..66ad259 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/integration/test-fixtures.js @@ -0,0 +1,89 @@ +var hashish = require('hashish'); +var fs = require('fs'); +var findit = require('findit'); +var path = require('path'); +var http = require('http'); +var net = require('net'); +var assert = require('assert'); + +var common = require('../common'); +var formidable = common.formidable; + +var server = http.createServer(); +server.listen(common.port, findFixtures); + +function findFixtures() { + var fixtures = []; + findit + .sync(common.dir.fixture + '/js') + .forEach(function(jsPath) { + if (!/\.js$/.test(jsPath)) return; + + var group = path.basename(jsPath, '.js'); + hashish.forEach(require(jsPath), function(fixture, name) { + fixtures.push({ + name : group + '/' + name, + fixture : fixture, + }); + }); + }); + + testNext(fixtures); +} + +function testNext(fixtures) { + var fixture = fixtures.shift(); + if (!fixture) return server.close(); + + var name = fixture.name; + var fixture = fixture.fixture; + + uploadFixture(name, function(err, parts) { + if (err) throw err; + + fixture.forEach(function(expectedPart, i) { + var parsedPart = parts[i]; + assert.equal(parsedPart.type, expectedPart.type); + assert.equal(parsedPart.name, expectedPart.name); + + if (parsedPart.type === 'file') { + var filename = parsedPart.value.name; + assert.equal(filename, expectedPart.filename); + } + }); + + testNext(fixtures); + }); +}; + +function uploadFixture(name, cb) { + server.once('request', function(req, res) { + var form = new formidable.IncomingForm(); + form.uploadDir = common.dir.tmp; + form.parse(req); + + function callback() { + var realCallback = cb; + cb = function() {}; + realCallback.apply(null, arguments); + } + + var parts = []; + form + .on('error', callback) + .on('fileBegin', function(name, value) { + parts.push({type: 'file', name: name, value: value}); + }) + .on('field', function(name, value) { + parts.push({type: 'field', name: name, value: value}); + }) + .on('end', function() { + callback(null, parts); + }); + }); + + var socket = net.createConnection(common.port); + var file = fs.createReadStream(common.dir.fixture + '/http/' + name); + + file.pipe(socket); +} diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/common.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/common.js new file mode 100644 index 0000000..2b98598 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/common.js @@ -0,0 +1,24 @@ +var path = require('path'), + fs = require('fs'); + +try { + global.Gently = require('gently'); +} catch (e) { + throw new Error('this test suite requires node-gently'); +} + +exports.lib = path.join(__dirname, '../../lib'); + +global.GENTLY = new Gently(); + +global.assert = require('assert'); +global.TEST_PORT = 13532; +global.TEST_FIXTURES = path.join(__dirname, '../fixture'); +global.TEST_TMP = path.join(__dirname, '../tmp'); + +// Stupid new feature in node that complains about gently attaching too many +// listeners to process 'exit'. This is a workaround until I can think of a +// better way to deal with this. +if (process.setMaxListeners) { + process.setMaxListeners(10000); +} diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/integration/test-multipart-parser.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/integration/test-multipart-parser.js new file mode 100644 index 0000000..75232aa --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/integration/test-multipart-parser.js @@ -0,0 +1,80 @@ +var common = require('../common'); +var CHUNK_LENGTH = 10, + multipartParser = require(common.lib + '/multipart_parser'), + MultipartParser = multipartParser.MultipartParser, + parser = new MultipartParser(), + fixtures = require(TEST_FIXTURES + '/multipart'), + Buffer = require('buffer').Buffer; + +Object.keys(fixtures).forEach(function(name) { + var fixture = fixtures[name], + buffer = new Buffer(Buffer.byteLength(fixture.raw, 'binary')), + offset = 0, + chunk, + nparsed, + + parts = [], + part = null, + headerField, + headerValue, + endCalled = ''; + + parser.initWithBoundary(fixture.boundary); + parser.onPartBegin = function() { + part = {headers: {}, data: ''}; + parts.push(part); + headerField = ''; + headerValue = ''; + }; + + parser.onHeaderField = function(b, start, end) { + headerField += b.toString('ascii', start, end); + }; + + parser.onHeaderValue = function(b, start, end) { + headerValue += b.toString('ascii', start, end); + } + + parser.onHeaderEnd = function() { + part.headers[headerField] = headerValue; + headerField = ''; + headerValue = ''; + }; + + parser.onPartData = function(b, start, end) { + var str = b.toString('ascii', start, end); + part.data += b.slice(start, end); + } + + parser.onEnd = function() { + endCalled = true; + } + + buffer.write(fixture.raw, 'binary', 0); + + while (offset < buffer.length) { + if (offset + CHUNK_LENGTH < buffer.length) { + chunk = buffer.slice(offset, offset+CHUNK_LENGTH); + } else { + chunk = buffer.slice(offset, buffer.length); + } + offset = offset + CHUNK_LENGTH; + + nparsed = parser.write(chunk); + if (nparsed != chunk.length) { + if (fixture.expectError) { + return; + } + puts('-- ERROR --'); + p(chunk.toString('ascii')); + throw new Error(chunk.length+' bytes written, but only '+nparsed+' bytes parsed!'); + } + } + + if (fixture.expectError) { + throw new Error('expected parse error did not happen'); + } + + assert.ok(endCalled); + assert.deepEqual(parts, fixture.parts); +}); diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-file.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-file.js new file mode 100644 index 0000000..52ceedb --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-file.js @@ -0,0 +1,104 @@ +var common = require('../common'); +var WriteStreamStub = GENTLY.stub('fs', 'WriteStream'); + +var File = require(common.lib + '/file'), + EventEmitter = require('events').EventEmitter, + file, + gently; + +function test(test) { + gently = new Gently(); + file = new File(); + test(); + gently.verify(test.name); +} + +test(function constructor() { + assert.ok(file instanceof EventEmitter); + assert.strictEqual(file.size, 0); + assert.strictEqual(file.path, null); + assert.strictEqual(file.name, null); + assert.strictEqual(file.type, null); + assert.strictEqual(file.lastModifiedDate, null); + + assert.strictEqual(file._writeStream, null); + + (function testSetProperties() { + var file2 = new File({foo: 'bar'}); + assert.equal(file2.foo, 'bar'); + })(); +}); + +test(function open() { + var WRITE_STREAM; + file.path = '/foo'; + + gently.expect(WriteStreamStub, 'new', function (path) { + WRITE_STREAM = this; + assert.strictEqual(path, file.path); + }); + + file.open(); + assert.strictEqual(file._writeStream, WRITE_STREAM); +}); + +test(function write() { + var BUFFER = {length: 10}, + CB_STUB, + CB = function() { + CB_STUB.apply(this, arguments); + }; + + file._writeStream = {}; + + gently.expect(file._writeStream, 'write', function (buffer, cb) { + assert.strictEqual(buffer, BUFFER); + + gently.expect(file, 'emit', function (event, bytesWritten) { + assert.ok(file.lastModifiedDate instanceof Date); + assert.equal(event, 'progress'); + assert.equal(bytesWritten, file.size); + }); + + CB_STUB = gently.expect(function writeCb() { + assert.equal(file.size, 10); + }); + + cb(); + + gently.expect(file, 'emit', function (event, bytesWritten) { + assert.equal(event, 'progress'); + assert.equal(bytesWritten, file.size); + }); + + CB_STUB = gently.expect(function writeCb() { + assert.equal(file.size, 20); + }); + + cb(); + }); + + file.write(BUFFER, CB); +}); + +test(function end() { + var CB_STUB, + CB = function() { + CB_STUB.apply(this, arguments); + }; + + file._writeStream = {}; + + gently.expect(file._writeStream, 'end', function (cb) { + gently.expect(file, 'emit', function (event) { + assert.equal(event, 'end'); + }); + + CB_STUB = gently.expect(function endCb() { + }); + + cb(); + }); + + file.end(CB); +}); diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-incoming-form.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-incoming-form.js new file mode 100644 index 0000000..de2bd0c --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-incoming-form.js @@ -0,0 +1,715 @@ +var common = require('../common'); +var MultipartParserStub = GENTLY.stub('./multipart_parser', 'MultipartParser'), + QuerystringParserStub = GENTLY.stub('./querystring_parser', 'QuerystringParser'), + EventEmitterStub = GENTLY.stub('events', 'EventEmitter'), + FileStub = GENTLY.stub('./file'); + +var formidable = require(common.lib + '/index'), + IncomingForm = formidable.IncomingForm, + events = require('events'), + fs = require('fs'), + path = require('path'), + Buffer = require('buffer').Buffer, + fixtures = require(TEST_FIXTURES + '/multipart'), + form, + gently; + +function test(test) { + gently = new Gently(); + gently.expect(EventEmitterStub, 'call'); + form = new IncomingForm(); + test(); + gently.verify(test.name); +} + +test(function constructor() { + assert.strictEqual(form.error, null); + assert.strictEqual(form.ended, false); + assert.strictEqual(form.type, null); + assert.strictEqual(form.headers, null); + assert.strictEqual(form.keepExtensions, false); + assert.strictEqual(form.uploadDir, '/tmp'); + assert.strictEqual(form.encoding, 'utf-8'); + assert.strictEqual(form.bytesReceived, null); + assert.strictEqual(form.bytesExpected, null); + assert.strictEqual(form.maxFieldsSize, 2 * 1024 * 1024); + assert.strictEqual(form._parser, null); + assert.strictEqual(form._flushing, 0); + assert.strictEqual(form._fieldsSize, 0); + assert.ok(form instanceof EventEmitterStub); + assert.equal(form.constructor.name, 'IncomingForm'); + + (function testSimpleConstructor() { + gently.expect(EventEmitterStub, 'call'); + var form = IncomingForm(); + assert.ok(form instanceof IncomingForm); + })(); + + (function testSimpleConstructorShortcut() { + gently.expect(EventEmitterStub, 'call'); + var form = formidable(); + assert.ok(form instanceof IncomingForm); + })(); +}); + +test(function parse() { + var REQ = {headers: {}} + , emit = {}; + + gently.expect(form, 'writeHeaders', function(headers) { + assert.strictEqual(headers, REQ.headers); + }); + + var events = ['error', 'aborted', 'data', 'end']; + gently.expect(REQ, 'on', events.length, function(event, fn) { + assert.equal(event, events.shift()); + emit[event] = fn; + return this; + }); + + form.parse(REQ); + + (function testPause() { + gently.expect(REQ, 'pause'); + assert.strictEqual(form.pause(), true); + })(); + + (function testPauseCriticalException() { + form.ended = false; + + var ERR = new Error('dasdsa'); + gently.expect(REQ, 'pause', function() { + throw ERR; + }); + + gently.expect(form, '_error', function(err) { + assert.strictEqual(err, ERR); + }); + + assert.strictEqual(form.pause(), false); + })(); + + (function testPauseHarmlessException() { + form.ended = true; + + var ERR = new Error('dasdsa'); + gently.expect(REQ, 'pause', function() { + throw ERR; + }); + + assert.strictEqual(form.pause(), false); + })(); + + (function testResume() { + gently.expect(REQ, 'resume'); + assert.strictEqual(form.resume(), true); + })(); + + (function testResumeCriticalException() { + form.ended = false; + + var ERR = new Error('dasdsa'); + gently.expect(REQ, 'resume', function() { + throw ERR; + }); + + gently.expect(form, '_error', function(err) { + assert.strictEqual(err, ERR); + }); + + assert.strictEqual(form.resume(), false); + })(); + + (function testResumeHarmlessException() { + form.ended = true; + + var ERR = new Error('dasdsa'); + gently.expect(REQ, 'resume', function() { + throw ERR; + }); + + assert.strictEqual(form.resume(), false); + })(); + + (function testEmitError() { + var ERR = new Error('something bad happened'); + gently.expect(form, '_error',function(err) { + assert.strictEqual(err, ERR); + }); + emit.error(ERR); + })(); + + (function testEmitAborted() { + gently.expect(form, 'emit',function(event) { + assert.equal(event, 'aborted'); + }); + + emit.aborted(); + })(); + + + (function testEmitData() { + var BUFFER = [1, 2, 3]; + gently.expect(form, 'write', function(buffer) { + assert.strictEqual(buffer, BUFFER); + }); + emit.data(BUFFER); + })(); + + (function testEmitEnd() { + form._parser = {}; + + (function testWithError() { + var ERR = new Error('haha'); + gently.expect(form._parser, 'end', function() { + return ERR; + }); + + gently.expect(form, '_error', function(err) { + assert.strictEqual(err, ERR); + }); + + emit.end(); + })(); + + (function testWithoutError() { + gently.expect(form._parser, 'end'); + emit.end(); + })(); + + (function testAfterError() { + form.error = true; + emit.end(); + })(); + })(); + + (function testWithCallback() { + gently.expect(EventEmitterStub, 'call'); + var form = new IncomingForm(), + REQ = {headers: {}}, + parseCalled = 0; + + gently.expect(form, 'writeHeaders'); + gently.expect(REQ, 'on', 4, function() { + return this; + }); + + gently.expect(form, 'on', 4, function(event, fn) { + if (event == 'field') { + fn('field1', 'foo'); + fn('field1', 'bar'); + fn('field2', 'nice'); + } + + if (event == 'file') { + fn('file1', '1'); + fn('file1', '2'); + fn('file2', '3'); + } + + if (event == 'end') { + fn(); + } + return this; + }); + + form.parse(REQ, gently.expect(function parseCbOk(err, fields, files) { + assert.deepEqual(fields, {field1: 'bar', field2: 'nice'}); + assert.deepEqual(files, {file1: '2', file2: '3'}); + })); + + gently.expect(form, 'writeHeaders'); + gently.expect(REQ, 'on', 4, function() { + return this; + }); + + var ERR = new Error('test'); + gently.expect(form, 'on', 3, function(event, fn) { + if (event == 'field') { + fn('foo', 'bar'); + } + + if (event == 'error') { + fn(ERR); + gently.expect(form, 'on'); + } + return this; + }); + + form.parse(REQ, gently.expect(function parseCbErr(err, fields, files) { + assert.strictEqual(err, ERR); + assert.deepEqual(fields, {foo: 'bar'}); + })); + })(); +}); + +test(function pause() { + assert.strictEqual(form.pause(), false); +}); + +test(function resume() { + assert.strictEqual(form.resume(), false); +}); + + +test(function writeHeaders() { + var HEADERS = {}; + gently.expect(form, '_parseContentLength'); + gently.expect(form, '_parseContentType'); + + form.writeHeaders(HEADERS); + assert.strictEqual(form.headers, HEADERS); +}); + +test(function write() { + var parser = {}, + BUFFER = [1, 2, 3]; + + form._parser = parser; + form.bytesExpected = 523423; + + (function testBasic() { + gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) { + assert.equal(event, 'progress'); + assert.equal(bytesReceived, BUFFER.length); + assert.equal(bytesExpected, form.bytesExpected); + }); + + gently.expect(parser, 'write', function(buffer) { + assert.strictEqual(buffer, BUFFER); + return buffer.length; + }); + + assert.equal(form.write(BUFFER), BUFFER.length); + assert.equal(form.bytesReceived, BUFFER.length); + })(); + + (function testParserError() { + gently.expect(form, 'emit'); + + gently.expect(parser, 'write', function(buffer) { + assert.strictEqual(buffer, BUFFER); + return buffer.length - 1; + }); + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/parser error/i)); + }); + + assert.equal(form.write(BUFFER), BUFFER.length - 1); + assert.equal(form.bytesReceived, BUFFER.length + BUFFER.length); + })(); + + (function testUninitialized() { + delete form._parser; + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/unintialized parser/i)); + }); + form.write(BUFFER); + })(); +}); + +test(function parseContentType() { + var HEADERS = {}; + + form.headers = {'content-type': 'application/x-www-form-urlencoded'}; + gently.expect(form, '_initUrlencoded'); + form._parseContentType(); + + // accept anything that has 'urlencoded' in it + form.headers = {'content-type': 'broken-client/urlencoded-stupid'}; + gently.expect(form, '_initUrlencoded'); + form._parseContentType(); + + var BOUNDARY = '---------------------------57814261102167618332366269'; + form.headers = {'content-type': 'multipart/form-data; boundary='+BOUNDARY}; + + gently.expect(form, '_initMultipart', function(boundary) { + assert.equal(boundary, BOUNDARY); + }); + form._parseContentType(); + + (function testQuotedBoundary() { + form.headers = {'content-type': 'multipart/form-data; boundary="' + BOUNDARY + '"'}; + + gently.expect(form, '_initMultipart', function(boundary) { + assert.equal(boundary, BOUNDARY); + }); + form._parseContentType(); + })(); + + (function testNoBoundary() { + form.headers = {'content-type': 'multipart/form-data'}; + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/no multipart boundary/i)); + }); + form._parseContentType(); + })(); + + (function testNoContentType() { + form.headers = {}; + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/no content-type/i)); + }); + form._parseContentType(); + })(); + + (function testUnknownContentType() { + form.headers = {'content-type': 'invalid'}; + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/unknown content-type/i)); + }); + form._parseContentType(); + })(); +}); + +test(function parseContentLength() { + var HEADERS = {}; + + form.headers = {}; + form._parseContentLength(); + assert.strictEqual(form.bytesExpected, null); + + form.headers['content-length'] = '8'; + form._parseContentLength(); + assert.strictEqual(form.bytesReceived, 0); + assert.strictEqual(form.bytesExpected, 8); + + // JS can be evil, lets make sure we are not + form.headers['content-length'] = '08'; + form._parseContentLength(); + assert.strictEqual(form.bytesExpected, 8); +}); + +test(function _initMultipart() { + var BOUNDARY = '123', + PARSER; + + gently.expect(MultipartParserStub, 'new', function() { + PARSER = this; + }); + + gently.expect(MultipartParserStub.prototype, 'initWithBoundary', function(boundary) { + assert.equal(boundary, BOUNDARY); + }); + + form._initMultipart(BOUNDARY); + assert.equal(form.type, 'multipart'); + assert.strictEqual(form._parser, PARSER); + + (function testRegularField() { + var PART; + gently.expect(EventEmitterStub, 'new', function() { + PART = this; + }); + + gently.expect(form, 'onPart', function(part) { + assert.strictEqual(part, PART); + assert.deepEqual + ( part.headers + , { 'content-disposition': 'form-data; name="field1"' + , 'foo': 'bar' + } + ); + assert.equal(part.name, 'field1'); + + var strings = ['hello', ' world']; + gently.expect(part, 'emit', 2, function(event, b) { + assert.equal(event, 'data'); + assert.equal(b.toString(), strings.shift()); + }); + + gently.expect(part, 'emit', function(event, b) { + assert.equal(event, 'end'); + }); + }); + + PARSER.onPartBegin(); + PARSER.onHeaderField(new Buffer('content-disposition'), 0, 10); + PARSER.onHeaderField(new Buffer('content-disposition'), 10, 19); + PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 0, 14); + PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 14, 24); + PARSER.onHeaderEnd(); + PARSER.onHeaderField(new Buffer('foo'), 0, 3); + PARSER.onHeaderValue(new Buffer('bar'), 0, 3); + PARSER.onHeaderEnd(); + PARSER.onHeadersEnd(); + PARSER.onPartData(new Buffer('hello world'), 0, 5); + PARSER.onPartData(new Buffer('hello world'), 5, 11); + PARSER.onPartEnd(); + })(); + + (function testFileField() { + var PART; + gently.expect(EventEmitterStub, 'new', function() { + PART = this; + }); + + gently.expect(form, 'onPart', function(part) { + assert.deepEqual + ( part.headers + , { 'content-disposition': 'form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"' + , 'content-type': 'text/plain' + } + ); + assert.equal(part.name, 'field2'); + assert.equal(part.filename, 'Sun"et.jpg'); + assert.equal(part.mime, 'text/plain'); + + gently.expect(part, 'emit', function(event, b) { + assert.equal(event, 'data'); + assert.equal(b.toString(), '... contents of file1.txt ...'); + }); + + gently.expect(part, 'emit', function(event, b) { + assert.equal(event, 'end'); + }); + }); + + PARSER.onPartBegin(); + PARSER.onHeaderField(new Buffer('content-disposition'), 0, 19); + PARSER.onHeaderValue(new Buffer('form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"'), 0, 85); + PARSER.onHeaderEnd(); + PARSER.onHeaderField(new Buffer('Content-Type'), 0, 12); + PARSER.onHeaderValue(new Buffer('text/plain'), 0, 10); + PARSER.onHeaderEnd(); + PARSER.onHeadersEnd(); + PARSER.onPartData(new Buffer('... contents of file1.txt ...'), 0, 29); + PARSER.onPartEnd(); + })(); + + (function testEnd() { + gently.expect(form, '_maybeEnd'); + PARSER.onEnd(); + assert.ok(form.ended); + })(); +}); + +test(function _fileName() { + // TODO + return; +}); + +test(function _initUrlencoded() { + var PARSER; + + gently.expect(QuerystringParserStub, 'new', function() { + PARSER = this; + }); + + form._initUrlencoded(); + assert.equal(form.type, 'urlencoded'); + assert.strictEqual(form._parser, PARSER); + + (function testOnField() { + var KEY = 'KEY', VAL = 'VAL'; + gently.expect(form, 'emit', function(field, key, val) { + assert.equal(field, 'field'); + assert.equal(key, KEY); + assert.equal(val, VAL); + }); + + PARSER.onField(KEY, VAL); + })(); + + (function testOnEnd() { + gently.expect(form, '_maybeEnd'); + + PARSER.onEnd(); + assert.equal(form.ended, true); + })(); +}); + +test(function _error() { + var ERR = new Error('bla'); + + gently.expect(form, 'pause'); + gently.expect(form, 'emit', function(event, err) { + assert.equal(event, 'error'); + assert.strictEqual(err, ERR); + }); + + form._error(ERR); + assert.strictEqual(form.error, ERR); + + // make sure _error only does its thing once + form._error(ERR); +}); + +test(function onPart() { + var PART = {}; + gently.expect(form, 'handlePart', function(part) { + assert.strictEqual(part, PART); + }); + + form.onPart(PART); +}); + +test(function handlePart() { + (function testUtf8Field() { + var PART = new events.EventEmitter(); + PART.name = 'my_field'; + + gently.expect(form, 'emit', function(event, field, value) { + assert.equal(event, 'field'); + assert.equal(field, 'my_field'); + assert.equal(value, 'hello world: €'); + }); + + form.handlePart(PART); + PART.emit('data', new Buffer('hello')); + PART.emit('data', new Buffer(' world: ')); + PART.emit('data', new Buffer([0xE2])); + PART.emit('data', new Buffer([0x82, 0xAC])); + PART.emit('end'); + })(); + + (function testBinaryField() { + var PART = new events.EventEmitter(); + PART.name = 'my_field2'; + + gently.expect(form, 'emit', function(event, field, value) { + assert.equal(event, 'field'); + assert.equal(field, 'my_field2'); + assert.equal(value, 'hello world: '+new Buffer([0xE2, 0x82, 0xAC]).toString('binary')); + }); + + form.encoding = 'binary'; + form.handlePart(PART); + PART.emit('data', new Buffer('hello')); + PART.emit('data', new Buffer(' world: ')); + PART.emit('data', new Buffer([0xE2])); + PART.emit('data', new Buffer([0x82, 0xAC])); + PART.emit('end'); + })(); + + (function testFieldSize() { + form.maxFieldsSize = 8; + var PART = new events.EventEmitter(); + PART.name = 'my_field'; + + gently.expect(form, '_error', function(err) { + assert.equal(err.message, 'maxFieldsSize exceeded, received 9 bytes of field data'); + }); + + form.handlePart(PART); + form._fieldsSize = 1; + PART.emit('data', new Buffer(7)); + PART.emit('data', new Buffer(1)); + })(); + + (function testFilePart() { + var PART = new events.EventEmitter(), + FILE = new events.EventEmitter(), + PATH = '/foo/bar'; + + PART.name = 'my_file'; + PART.filename = 'sweet.txt'; + PART.mime = 'sweet.txt'; + + gently.expect(form, '_uploadPath', function(filename) { + assert.equal(filename, PART.filename); + return PATH; + }); + + gently.expect(FileStub, 'new', function(properties) { + assert.equal(properties.path, PATH); + assert.equal(properties.name, PART.filename); + assert.equal(properties.type, PART.mime); + FILE = this; + + gently.expect(form, 'emit', function (event, field, file) { + assert.equal(event, 'fileBegin'); + assert.strictEqual(field, PART.name); + assert.strictEqual(file, FILE); + }); + + gently.expect(FILE, 'open'); + }); + + form.handlePart(PART); + assert.equal(form._flushing, 1); + + var BUFFER; + gently.expect(form, 'pause'); + gently.expect(FILE, 'write', function(buffer, cb) { + assert.strictEqual(buffer, BUFFER); + gently.expect(form, 'resume'); + // @todo handle cb(new Err) + cb(); + }); + + PART.emit('data', BUFFER = new Buffer('test')); + + gently.expect(FILE, 'end', function(cb) { + gently.expect(form, 'emit', function(event, field, file) { + assert.equal(event, 'file'); + assert.strictEqual(file, FILE); + }); + + gently.expect(form, '_maybeEnd'); + + cb(); + assert.equal(form._flushing, 0); + }); + + PART.emit('end'); + })(); +}); + +test(function _uploadPath() { + (function testUniqueId() { + var UUID_A, UUID_B; + gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) { + assert.equal(uploadDir, form.uploadDir); + UUID_A = uuid; + }); + form._uploadPath(); + + gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) { + UUID_B = uuid; + }); + form._uploadPath(); + + assert.notEqual(UUID_A, UUID_B); + })(); + + (function testFileExtension() { + form.keepExtensions = true; + var FILENAME = 'foo.jpg', + EXT = '.bar'; + + gently.expect(GENTLY.hijacked.path, 'extname', function(filename) { + assert.equal(filename, FILENAME); + gently.restore(path, 'extname'); + + return EXT; + }); + + gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, name) { + assert.equal(path.extname(name), EXT); + }); + form._uploadPath(FILENAME); + })(); +}); + +test(function _maybeEnd() { + gently.expect(form, 'emit', 0); + form._maybeEnd(); + + form.ended = true; + form._flushing = 1; + form._maybeEnd(); + + gently.expect(form, 'emit', function(event) { + assert.equal(event, 'end'); + }); + + form.ended = true; + form._flushing = 0; + form._maybeEnd(); +}); diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-multipart-parser.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-multipart-parser.js new file mode 100644 index 0000000..d8dc968 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-multipart-parser.js @@ -0,0 +1,50 @@ +var common = require('../common'); +var multipartParser = require(common.lib + '/multipart_parser'), + MultipartParser = multipartParser.MultipartParser, + events = require('events'), + Buffer = require('buffer').Buffer, + parser; + +function test(test) { + parser = new MultipartParser(); + test(); +} + +test(function constructor() { + assert.equal(parser.boundary, null); + assert.equal(parser.state, 0); + assert.equal(parser.flags, 0); + assert.equal(parser.boundaryChars, null); + assert.equal(parser.index, null); + assert.equal(parser.lookbehind, null); + assert.equal(parser.constructor.name, 'MultipartParser'); +}); + +test(function initWithBoundary() { + var boundary = 'abc'; + parser.initWithBoundary(boundary); + assert.deepEqual(Array.prototype.slice.call(parser.boundary), [13, 10, 45, 45, 97, 98, 99]); + assert.equal(parser.state, multipartParser.START); + + assert.deepEqual(parser.boundaryChars, {10: true, 13: true, 45: true, 97: true, 98: true, 99: true}); +}); + +test(function parserError() { + var boundary = 'abc', + buffer = new Buffer(5); + + parser.initWithBoundary(boundary); + buffer.write('--ad', 'ascii', 0); + assert.equal(parser.write(buffer), 3); +}); + +test(function end() { + (function testError() { + assert.equal(parser.end().message, 'MultipartParser.end(): stream ended unexpectedly: ' + parser.explain()); + })(); + + (function testRegular() { + parser.state = multipartParser.END; + assert.strictEqual(parser.end(), undefined); + })(); +}); diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-querystring-parser.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-querystring-parser.js new file mode 100644 index 0000000..54d3e2d --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-querystring-parser.js @@ -0,0 +1,45 @@ +var common = require('../common'); +var QuerystringParser = require(common.lib + '/querystring_parser').QuerystringParser, + Buffer = require('buffer').Buffer, + gently, + parser; + +function test(test) { + gently = new Gently(); + parser = new QuerystringParser(); + test(); + gently.verify(test.name); +} + +test(function constructor() { + assert.equal(parser.buffer, ''); + assert.equal(parser.constructor.name, 'QuerystringParser'); +}); + +test(function write() { + var a = new Buffer('a=1'); + assert.equal(parser.write(a), a.length); + + var b = new Buffer('&b=2'); + parser.write(b); + assert.equal(parser.buffer, a + b); +}); + +test(function end() { + var FIELDS = {a: ['b', {c: 'd'}], e: 'f'}; + + gently.expect(GENTLY.hijacked.querystring, 'parse', function(str) { + assert.equal(str, parser.buffer); + return FIELDS; + }); + + gently.expect(parser, 'onField', Object.keys(FIELDS).length, function(key, val) { + assert.deepEqual(FIELDS[key], val); + }); + + gently.expect(parser, 'onEnd'); + + parser.buffer = 'my buffer'; + parser.end(); + assert.equal(parser.buffer, ''); +}); diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/system/test-multi-video-upload.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/system/test-multi-video-upload.js new file mode 100644 index 0000000..fcfdb94 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/system/test-multi-video-upload.js @@ -0,0 +1,72 @@ +var common = require('../common'); +var BOUNDARY = '---------------------------10102754414578508781458777923', + FIXTURE = TEST_FIXTURES+'/multi_video.upload', + fs = require('fs'), + util = require(common.lib + '/util'), + http = require('http'), + formidable = require(common.lib + '/index'), + server = http.createServer(); + +server.on('request', function(req, res) { + var form = new formidable.IncomingForm(), + uploads = {}; + + form.uploadDir = TEST_TMP; + form.parse(req); + + form + .on('fileBegin', function(field, file) { + assert.equal(field, 'upload'); + + var tracker = {file: file, progress: [], ended: false}; + uploads[file.filename] = tracker; + file + .on('progress', function(bytesReceived) { + tracker.progress.push(bytesReceived); + assert.equal(bytesReceived, file.length); + }) + .on('end', function() { + tracker.ended = true; + }); + }) + .on('field', function(field, value) { + assert.equal(field, 'title'); + assert.equal(value, ''); + }) + .on('file', function(field, file) { + assert.equal(field, 'upload'); + assert.strictEqual(uploads[file.filename].file, file); + }) + .on('end', function() { + assert.ok(uploads['shortest_video.flv']); + assert.ok(uploads['shortest_video.flv'].ended); + assert.ok(uploads['shortest_video.flv'].progress.length > 3); + assert.equal(uploads['shortest_video.flv'].progress.slice(-1), uploads['shortest_video.flv'].file.length); + assert.ok(uploads['shortest_video.mp4']); + assert.ok(uploads['shortest_video.mp4'].ended); + assert.ok(uploads['shortest_video.mp4'].progress.length > 3); + + server.close(); + res.writeHead(200); + res.end('good'); + }); +}); + +server.listen(TEST_PORT, function() { + var client = http.createClient(TEST_PORT), + stat = fs.statSync(FIXTURE), + headers = { + 'content-type': 'multipart/form-data; boundary='+BOUNDARY, + 'content-length': stat.size, + } + request = client.request('POST', '/', headers), + fixture = new fs.ReadStream(FIXTURE); + + fixture + .on('data', function(b) { + request.write(b); + }) + .on('end', function() { + request.end(); + }); +}); diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/run.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/run.js new file mode 100755 index 0000000..50b2361 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/run.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('urun')(__dirname) diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/test/unit/test-incoming-form.js b/node_modules/express/node_modules/connect/node_modules/formidable/test/unit/test-incoming-form.js new file mode 100644 index 0000000..bcf61d7 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/test/unit/test-incoming-form.js @@ -0,0 +1,63 @@ +var common = require('../common'); +var test = require('utest'); +var assert = common.assert; +var IncomingForm = common.require('incoming_form').IncomingForm; +var path = require('path'); + +var from; +test('IncomingForm', { + before: function() { + form = new IncomingForm(); + }, + + '#_fileName with regular characters': function() { + var filename = 'foo.txt'; + assert.equal(form._fileName(makeHeader(filename)), 'foo.txt'); + }, + + '#_fileName with unescaped quote': function() { + var filename = 'my".txt'; + assert.equal(form._fileName(makeHeader(filename)), 'my".txt'); + }, + + '#_fileName with escaped quote': function() { + var filename = 'my%22.txt'; + assert.equal(form._fileName(makeHeader(filename)), 'my".txt'); + }, + + '#_fileName with bad quote and additional sub-header': function() { + var filename = 'my".txt'; + var header = makeHeader(filename) + '; foo="bar"'; + assert.equal(form._fileName(header), filename); + }, + + '#_fileName with semicolon': function() { + var filename = 'my;.txt'; + assert.equal(form._fileName(makeHeader(filename)), 'my;.txt'); + }, + + '#_fileName with utf8 character': function() { + var filename = 'my☃.txt'; + assert.equal(form._fileName(makeHeader(filename)), 'my☃.txt'); + }, + + '#_uploadPath strips harmful characters from extension when keepExtensions': function() { + form.keepExtensions = true; + + var ext = path.extname(form._uploadPath('fine.jpg?foo=bar')); + assert.equal(ext, '.jpg'); + + var ext = path.extname(form._uploadPath('fine?foo=bar')); + assert.equal(ext, ''); + + var ext = path.extname(form._uploadPath('super.cr2+dsad')); + assert.equal(ext, '.cr2'); + + var ext = path.extname(form._uploadPath('super.bar')); + assert.equal(ext, '.bar'); + }, +}); + +function makeHeader(filename) { + return 'Content-Disposition: form-data; name="upload"; filename="' + filename + '"'; +} diff --git a/node_modules/express/node_modules/connect/node_modules/formidable/tool/record.js b/node_modules/express/node_modules/connect/node_modules/formidable/tool/record.js new file mode 100644 index 0000000..9f1cef8 --- /dev/null +++ b/node_modules/express/node_modules/connect/node_modules/formidable/tool/record.js @@ -0,0 +1,47 @@ +var http = require('http'); +var fs = require('fs'); +var connections = 0; + +var server = http.createServer(function(req, res) { + var socket = req.socket; + console.log('Request: %s %s -> %s', req.method, req.url, socket.filename); + + req.on('end', function() { + if (req.url !== '/') { + res.end(JSON.stringify({ + method: req.method, + url: req.url, + filename: socket.filename, + })); + return; + } + + res.writeHead(200, {'content-type': 'text/html'}); + res.end( + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    ' + ); + }); +}); + +server.on('connection', function(socket) { + connections++; + + socket.id = connections; + socket.filename = 'connection-' + socket.id + '.http'; + socket.file = fs.createWriteStream(socket.filename); + socket.pipe(socket.file); + + console.log('--> %s', socket.filename); + socket.on('close', function() { + console.log('<-- %s', socket.filename); + }); +}); + +var port = process.env.PORT || 8080; +server.listen(port, function() { + console.log('Recording connections on port %s', port); +}); diff --git a/node_modules/express/node_modules/connect/package.json b/node_modules/express/node_modules/connect/package.json new file mode 100644 index 0000000..f9c43d5 --- /dev/null +++ b/node_modules/express/node_modules/connect/package.json @@ -0,0 +1,25 @@ +{ + "name": "connect", + "version": "1.8.5", + "description": "High performance middleware framework", + "keywords": ["framework", "web", "middleware", "connect", "rack"], + "repository": "git://github.com/senchalabs/connect.git", + "author": "TJ Holowaychuk (http://tjholowaychuk.com)", + "repository": "git://github.com/senchalabs/connect", + "dependencies": { + "qs": ">= 0.4.0", + "mime": ">= 0.0.1", + "formidable": "1.0.x" + }, + "devDependencies": { + "expresso": "0.9.2", + "koala": "0.1.2", + "less": "1.1.1", + "sass": "0.5.0", + "markdown": "0.2.1", + "ejs": "0.4.3", + "should": "0.3.2" + }, + "main": "index", + "engines": { "node": ">= 0.4.1 < 0.7.0" } +} \ No newline at end of file diff --git a/node_modules/express/node_modules/connect/test.js b/node_modules/express/node_modules/connect/test.js new file mode 100644 index 0000000..a1e1d55 --- /dev/null +++ b/node_modules/express/node_modules/connect/test.js @@ -0,0 +1,52 @@ + +var connect = require('./') + , http = require('http') + , RedisStore = require('connect-redis')(connect); + +var app = connect(); +app.use(connect.cookieParser('fucj')); +app.use(connect.session({store:new RedisStore})); +app.use(function(req, res, next){ + req.session.views = (req.session.views || 0) + 1; + res.writeHead(200, {"Content-Type": "text/plain"}); + res.end("You've viewed this page "+req.session.views+" times."); +}) + +http.createServer(app).listen(3000); + + +// var set = RedisStore.prototype.set; +// +// function slow(sid){ +// console.log('%s saving', sid); +// var args = arguments; +// setTimeout(function(self){ +// console.log('%s saved', sid); +// set.apply(self, args); +// }, 2000, this); +// }; +// +// http.createServer(connect() +// .use(connect.logger('dev')) +// .use(connect.cookieParser('keyboard cat')) +// .use(connect.session({ store: new RedisStore })) +// .use(function(req, res, next){ +// var sess = req.session; +// switch (req.url) { +// case '/foo.js': +// console.log('%s foo.js sid', sess.id); +// RedisStore.prototype.set = set; +// res.end('data'); +// break; +// default: +// console.log('%s html sid', sess.id); +// RedisStore.prototype.set = slow; +// res.setHeader('Content-Type', 'html'); +// res.write(''); +// setTimeout(function(){ +// res.end(''); +// }, 1000); +// } +// })).listen(3000); +// +// console.log('port 3000'); \ No newline at end of file diff --git a/node_modules/express/node_modules/mime/LICENSE b/node_modules/express/node_modules/mime/LICENSE new file mode 100644 index 0000000..451fc45 --- /dev/null +++ b/node_modules/express/node_modules/mime/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010 Benjamin Thomas, Robert Kieffer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/express/node_modules/mime/README.md b/node_modules/express/node_modules/mime/README.md new file mode 100644 index 0000000..a157de1 --- /dev/null +++ b/node_modules/express/node_modules/mime/README.md @@ -0,0 +1,50 @@ +# mime + +Support for mapping between file extensions and MIME types. This module uses the latest version of the Apache "mime.types" file (maps over 620 types to 800+ extensions). It is also trivially easy to add your own types and extensions, should you need to do that. + +## Install + +Install with [npm](http://github.com/isaacs/npm): + + npm install mime + +## API - Queries + +### mime.lookup(path) +Get the mime type associated with a file. This is method is case-insensitive. Everything in path up to and including the last '/' or '.' is ignored, so you can pass it paths, filenames, or extensions, like so: + + var mime = require('mime'); + + mime.lookup('/path/to/file.txt'); // => 'text/plain' + mime.lookup('file.txt'); // => 'text/plain' + mime.lookup('.txt'); // => 'text/plain' + mime.lookup('htm'); // => 'text/html' + +### mime.extension(type) - lookup the default extension for type + + mime.extension('text/html'); // => 'html' + mime.extension('application/octet-stream'); // => 'bin' + +### mime.charsets.lookup() - map mime-type to charset + + mime.charsets.lookup('text/plain'); // => 'UTF-8' + +(The logic for charset lookups is pretty rudimentary. Feel free to suggest improvements.) + +## API - Customizing + +The following APIs allow you to add your own type mappings within your project. If you feel a type should be included as part of node-mime, see [requesting new types](https://github.com/bentomas/node-mime/wiki/Requesting-New-Types). +### mime.define() - Add custom mime/extension mappings + + mime.define({ + 'text/x-some-format': ['x-sf', 'x-sft', 'x-sfml'], + 'application/x-my-type': ['x-mt', 'x-mtt'], + // etc ... + }); + + mime.lookup('x-sft'); // => 'text/x-some-format' + mime.extension('text/x-some-format'); // => 'x-sf' + +### mime.load(filepath) - Load mappings from an Apache ".types" format file + + mime.load('./my_project.types'); diff --git a/node_modules/express/node_modules/mime/mime.js b/node_modules/express/node_modules/mime/mime.js new file mode 100644 index 0000000..5fac753 --- /dev/null +++ b/node_modules/express/node_modules/mime/mime.js @@ -0,0 +1,92 @@ +var path = require('path'), + fs = require('fs'); + +var mime = module.exports = { + /** Map of extension to mime type */ + types: {}, + + /** Map of mime type to extension */ + extensions :{}, + + /** + * Define mimetype -> extension mappings. Each key is a mime-type that maps + * to an array of extensions associated with the type. The first extension is + * used as the default extension for the type. + * + * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']}); + * + * @param map (Object) type definitions + */ + define: function(map) { + for (var type in map) { + var exts = map[type]; + + for (var i = 0; i < exts.length; i++) { + mime.types[exts[i]] = type; + } + + // Default extension is the first one we encounter + if (!mime.extensions[type]) { + mime.extensions[type] = exts[0]; + } + } + }, + + /** + * Load an Apache2-style ".types" file + * + * This may be called multiple times (it's expected). Where files declare + * overlapping types/extensions, the last file wins. + * + * @param file (String) path of file to load. + */ + load: function(file) { + // Read file and split into lines + var map = {}, + content = fs.readFileSync(file, 'ascii'), + lines = content.split(/[\r\n]+/); + + lines.forEach(function(line, lineno) { + // Clean up whitespace/comments, and split into fields + var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/); + map[fields.shift()] = fields; + }); + + mime.define(map); + }, + + /** + * Lookup a mime type based on extension + */ + lookup: function(path, fallback) { + var ext = path.replace(/.*[\.\/]/, '').toLowerCase(); + return mime.types[ext] || fallback || mime.default_type; + }, + + /** + * Return file extension associated with a mime type + */ + extension: function(mimeType) { + return mime.extensions[mimeType]; + }, + + /** + * Lookup a charset based on mime type. + */ + charsets: { + lookup: function (mimeType, fallback) { + // Assume text types are utf8. Modify mime logic as needed. + return (/^text\//).test(mimeType) ? 'UTF-8' : fallback; + } + } +}; + +// Load our local copy of +// http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types +mime.load(path.join(__dirname, 'types/mime.types')); + +// Overlay enhancements submitted by the node.js community +mime.load(path.join(__dirname, 'types/node.types')); + +// Set the default type +mime.default_type = mime.types.bin; diff --git a/node_modules/express/node_modules/mime/package.json b/node_modules/express/node_modules/mime/package.json new file mode 100644 index 0000000..85277b0 --- /dev/null +++ b/node_modules/express/node_modules/mime/package.json @@ -0,0 +1,22 @@ +{ + "author": { + "name": "Robert Kieffer", + "url": "http://github.com/broofa", + "email": "robert@broofa.com" + }, + "contributors": [ + { + "name": "Benjamin Thomas", + "url": "http://github.com/bentomas", + "email": "benjamin@benjaminthomas.org" + } + ], + "dependencies": {}, + "description": "A comprehensive library for mime-type mapping", + "devDependencies": {"async_testing": ""}, + "keywords": ["util", "mime"], + "main": "mime.js", + "name": "mime", + "repository": {"url": "http://github.com/bentomas/node-mime", "type": "git"}, + "version": "1.2.4" +} diff --git a/node_modules/express/node_modules/mime/test.js b/node_modules/express/node_modules/mime/test.js new file mode 100644 index 0000000..b904895 --- /dev/null +++ b/node_modules/express/node_modules/mime/test.js @@ -0,0 +1,79 @@ +/** + * Requires the async_testing module + * + * Usage: node test.js + */ +var mime = require('./mime'); +exports["test mime lookup"] = function(test) { + // easy + test.equal('text/plain', mime.lookup('text.txt')); + + // hidden file or multiple periods + test.equal('text/plain', mime.lookup('.text.txt')); + + // just an extension + test.equal('text/plain', mime.lookup('.txt')); + + // just an extension without a dot + test.equal('text/plain', mime.lookup('txt')); + + // default + test.equal('application/octet-stream', mime.lookup('text.nope')); + + // fallback + test.equal('fallback', mime.lookup('text.fallback', 'fallback')); + + test.finish(); +}; + +exports["test extension lookup"] = function(test) { + // easy + test.equal('txt', mime.extension(mime.types.text)); + test.equal('html', mime.extension(mime.types.htm)); + test.equal('bin', mime.extension('application/octet-stream')); + + test.finish(); +}; + +exports["test mime lookup uppercase"] = function(test) { + // easy + test.equal('text/plain', mime.lookup('TEXT.TXT')); + + // just an extension + test.equal('text/plain', mime.lookup('.TXT')); + + // just an extension without a dot + test.equal('text/plain', mime.lookup('TXT')); + + // default + test.equal('application/octet-stream', mime.lookup('TEXT.NOPE')); + + // fallback + test.equal('fallback', mime.lookup('TEXT.FALLBACK', 'fallback')); + + test.finish(); +}; + +exports["test custom types"] = function(test) { + test.equal('application/octet-stream', mime.lookup('file.buffer')); + test.equal('audio/mp4', mime.lookup('file.m4a')); + + test.finish(); +}; + +exports["test charset lookup"] = function(test) { + // easy + test.equal('UTF-8', mime.charsets.lookup('text/plain')); + + // none + test.ok(typeof mime.charsets.lookup(mime.types.js) == 'undefined'); + + // fallback + test.equal('fallback', mime.charsets.lookup('application/octet-stream', 'fallback')); + + test.finish(); +}; + +if (module == require.main) { + require('async_testing').run(__filename, process.ARGV); +} diff --git a/node_modules/express/node_modules/mime/types/mime.types b/node_modules/express/node_modules/mime/types/mime.types new file mode 100644 index 0000000..6a90929 --- /dev/null +++ b/node_modules/express/node_modules/mime/types/mime.types @@ -0,0 +1,1479 @@ +# This file maps Internet media types to unique file extension(s). +# Although created for httpd, this file is used by many software systems +# and has been placed in the public domain for unlimited redisribution. +# +# The table below contains both registered and (common) unregistered types. +# A type that has no unique extension can be ignored -- they are listed +# here to guide configurations toward known types and to make it easier to +# identify "new" types. File extensions are also commonly used to indicate +# content languages and encodings, so choose them carefully. +# +# Internet media types should be registered as described in RFC 4288. +# The registry is at . +# +# MIME type (lowercased) Extensions +# ============================================ ========== +# application/1d-interleaved-parityfec +# application/3gpp-ims+xml +# application/activemessage +application/andrew-inset ez +# application/applefile +application/applixware aw +application/atom+xml atom +application/atomcat+xml atomcat +# application/atomicmail +application/atomsvc+xml atomsvc +# application/auth-policy+xml +# application/batch-smtp +# application/beep+xml +# application/cals-1840 +application/ccxml+xml ccxml +application/cdmi-capability cdmia +application/cdmi-container cdmic +application/cdmi-domain cdmid +application/cdmi-object cdmio +application/cdmi-queue cdmiq +# application/cea-2018+xml +# application/cellml+xml +# application/cfw +# application/cnrp+xml +# application/commonground +# application/conference-info+xml +# application/cpl+xml +# application/csta+xml +# application/cstadata+xml +application/cu-seeme cu +# application/cybercash +application/davmount+xml davmount +# application/dca-rft +# application/dec-dx +# application/dialog-info+xml +# application/dicom +# application/dns +# application/dskpp+xml +application/dssc+der dssc +application/dssc+xml xdssc +# application/dvcs +application/ecmascript ecma +# application/edi-consent +# application/edi-x12 +# application/edifact +application/emma+xml emma +# application/epp+xml +application/epub+zip epub +# application/eshop +# application/example +application/exi exi +# application/fastinfoset +# application/fastsoap +# application/fits +application/font-tdpfr pfr +# application/framework-attributes+xml +# application/h224 +# application/held+xml +# application/http +application/hyperstudio stk +# application/ibe-key-request+xml +# application/ibe-pkg-reply+xml +# application/ibe-pp-data +# application/iges +# application/im-iscomposing+xml +# application/index +# application/index.cmd +# application/index.obj +# application/index.response +# application/index.vnd +# application/iotp +application/ipfix ipfix +# application/ipp +# application/isup +application/java-archive jar +application/java-serialized-object ser +application/java-vm class +application/javascript js +application/json json +# application/kpml-request+xml +# application/kpml-response+xml +application/lost+xml lostxml +application/mac-binhex40 hqx +application/mac-compactpro cpt +# application/macwriteii +application/mads+xml mads +application/marc mrc +application/marcxml+xml mrcx +application/mathematica ma nb mb +# application/mathml-content+xml +# application/mathml-presentation+xml +application/mathml+xml mathml +# application/mbms-associated-procedure-description+xml +# application/mbms-deregister+xml +# application/mbms-envelope+xml +# application/mbms-msk+xml +# application/mbms-msk-response+xml +# application/mbms-protection-description+xml +# application/mbms-reception-report+xml +# application/mbms-register+xml +# application/mbms-register-response+xml +# application/mbms-user-service-description+xml +application/mbox mbox +# application/media_control+xml +application/mediaservercontrol+xml mscml +application/metalink4+xml meta4 +application/mets+xml mets +# application/mikey +application/mods+xml mods +# application/moss-keys +# application/moss-signature +# application/mosskey-data +# application/mosskey-request +application/mp21 m21 mp21 +application/mp4 mp4s +# application/mpeg4-generic +# application/mpeg4-iod +# application/mpeg4-iod-xmt +# application/msc-ivr+xml +# application/msc-mixer+xml +application/msword doc dot +application/mxf mxf +# application/nasdata +# application/news-checkgroups +# application/news-groupinfo +# application/news-transmission +# application/nss +# application/ocsp-request +# application/ocsp-response +application/octet-stream bin dms lha lrf lzh so iso dmg dist distz pkg bpk dump elc deploy +application/oda oda +application/oebps-package+xml opf +application/ogg ogx +application/onenote onetoc onetoc2 onetmp onepkg +# application/parityfec +application/patch-ops-error+xml xer +application/pdf pdf +application/pgp-encrypted pgp +# application/pgp-keys +application/pgp-signature asc sig +application/pics-rules prf +# application/pidf+xml +# application/pidf-diff+xml +application/pkcs10 p10 +application/pkcs7-mime p7m p7c +application/pkcs7-signature p7s +application/pkcs8 p8 +application/pkix-attr-cert ac +application/pkix-cert cer +application/pkix-crl crl +application/pkix-pkipath pkipath +application/pkixcmp pki +application/pls+xml pls +# application/poc-settings+xml +application/postscript ai eps ps +# application/prs.alvestrand.titrax-sheet +application/prs.cww cww +# application/prs.nprend +# application/prs.plucker +# application/prs.rdf-xml-crypt +# application/prs.xsf+xml +application/pskc+xml pskcxml +# application/qsig +application/rdf+xml rdf +application/reginfo+xml rif +application/relax-ng-compact-syntax rnc +# application/remote-printing +application/resource-lists+xml rl +application/resource-lists-diff+xml rld +# application/riscos +# application/rlmi+xml +application/rls-services+xml rs +application/rsd+xml rsd +application/rss+xml rss +application/rtf rtf +# application/rtx +# application/samlassertion+xml +# application/samlmetadata+xml +application/sbml+xml sbml +application/scvp-cv-request scq +application/scvp-cv-response scs +application/scvp-vp-request spq +application/scvp-vp-response spp +application/sdp sdp +# application/set-payment +application/set-payment-initiation setpay +# application/set-registration +application/set-registration-initiation setreg +# application/sgml +# application/sgml-open-catalog +application/shf+xml shf +# application/sieve +# application/simple-filter+xml +# application/simple-message-summary +# application/simplesymbolcontainer +# application/slate +# application/smil +application/smil+xml smi smil +# application/soap+fastinfoset +# application/soap+xml +application/sparql-query rq +application/sparql-results+xml srx +# application/spirits-event+xml +application/srgs gram +application/srgs+xml grxml +application/sru+xml sru +application/ssml+xml ssml +# application/tamp-apex-update +# application/tamp-apex-update-confirm +# application/tamp-community-update +# application/tamp-community-update-confirm +# application/tamp-error +# application/tamp-sequence-adjust +# application/tamp-sequence-adjust-confirm +# application/tamp-status-query +# application/tamp-status-response +# application/tamp-update +# application/tamp-update-confirm +application/tei+xml tei teicorpus +application/thraud+xml tfi +# application/timestamp-query +# application/timestamp-reply +application/timestamped-data tsd +# application/tve-trigger +# application/ulpfec +# application/vemmi +# application/vividence.scriptfile +# application/vnd.3gpp.bsf+xml +application/vnd.3gpp.pic-bw-large plb +application/vnd.3gpp.pic-bw-small psb +application/vnd.3gpp.pic-bw-var pvb +# application/vnd.3gpp.sms +# application/vnd.3gpp2.bcmcsinfo+xml +# application/vnd.3gpp2.sms +application/vnd.3gpp2.tcap tcap +application/vnd.3m.post-it-notes pwn +application/vnd.accpac.simply.aso aso +application/vnd.accpac.simply.imp imp +application/vnd.acucobol acu +application/vnd.acucorp atc acutc +application/vnd.adobe.air-application-installer-package+zip air +application/vnd.adobe.fxp fxp fxpl +# application/vnd.adobe.partial-upload +application/vnd.adobe.xdp+xml xdp +application/vnd.adobe.xfdf xfdf +# application/vnd.aether.imp +# application/vnd.ah-barcode +application/vnd.ahead.space ahead +application/vnd.airzip.filesecure.azf azf +application/vnd.airzip.filesecure.azs azs +application/vnd.amazon.ebook azw +application/vnd.americandynamics.acc acc +application/vnd.amiga.ami ami +# application/vnd.amundsen.maze+xml +application/vnd.android.package-archive apk +application/vnd.anser-web-certificate-issue-initiation cii +application/vnd.anser-web-funds-transfer-initiation fti +application/vnd.antix.game-component atx +application/vnd.apple.installer+xml mpkg +application/vnd.apple.mpegurl m3u8 +# application/vnd.arastra.swi +application/vnd.aristanetworks.swi swi +application/vnd.audiograph aep +# application/vnd.autopackage +# application/vnd.avistar+xml +application/vnd.blueice.multipass mpm +# application/vnd.bluetooth.ep.oob +application/vnd.bmi bmi +application/vnd.businessobjects rep +# application/vnd.cab-jscript +# application/vnd.canon-cpdl +# application/vnd.canon-lips +# application/vnd.cendio.thinlinc.clientconf +application/vnd.chemdraw+xml cdxml +application/vnd.chipnuts.karaoke-mmd mmd +application/vnd.cinderella cdy +# application/vnd.cirpack.isdn-ext +application/vnd.claymore cla +application/vnd.cloanto.rp9 rp9 +application/vnd.clonk.c4group c4g c4d c4f c4p c4u +application/vnd.cluetrust.cartomobile-config c11amc +application/vnd.cluetrust.cartomobile-config-pkg c11amz +# application/vnd.commerce-battelle +application/vnd.commonspace csp +application/vnd.contact.cmsg cdbcmsg +application/vnd.cosmocaller cmc +application/vnd.crick.clicker clkx +application/vnd.crick.clicker.keyboard clkk +application/vnd.crick.clicker.palette clkp +application/vnd.crick.clicker.template clkt +application/vnd.crick.clicker.wordbank clkw +application/vnd.criticaltools.wbs+xml wbs +application/vnd.ctc-posml pml +# application/vnd.ctct.ws+xml +# application/vnd.cups-pdf +# application/vnd.cups-postscript +application/vnd.cups-ppd ppd +# application/vnd.cups-raster +# application/vnd.cups-raw +application/vnd.curl.car car +application/vnd.curl.pcurl pcurl +# application/vnd.cybank +application/vnd.data-vision.rdz rdz +application/vnd.dece.data uvf uvvf uvd uvvd +application/vnd.dece.ttml+xml uvt uvvt +application/vnd.dece.unspecified uvx uvvx +application/vnd.denovo.fcselayout-link fe_launch +# application/vnd.dir-bi.plate-dl-nosuffix +application/vnd.dna dna +application/vnd.dolby.mlp mlp +# application/vnd.dolby.mobile.1 +# application/vnd.dolby.mobile.2 +application/vnd.dpgraph dpg +application/vnd.dreamfactory dfac +application/vnd.dvb.ait ait +# application/vnd.dvb.dvbj +# application/vnd.dvb.esgcontainer +# application/vnd.dvb.ipdcdftnotifaccess +# application/vnd.dvb.ipdcesgaccess +# application/vnd.dvb.ipdcesgaccess2 +# application/vnd.dvb.ipdcesgpdd +# application/vnd.dvb.ipdcroaming +# application/vnd.dvb.iptv.alfec-base +# application/vnd.dvb.iptv.alfec-enhancement +# application/vnd.dvb.notif-aggregate-root+xml +# application/vnd.dvb.notif-container+xml +# application/vnd.dvb.notif-generic+xml +# application/vnd.dvb.notif-ia-msglist+xml +# application/vnd.dvb.notif-ia-registration-request+xml +# application/vnd.dvb.notif-ia-registration-response+xml +# application/vnd.dvb.notif-init+xml +# application/vnd.dvb.pfr +application/vnd.dvb.service svc +# application/vnd.dxr +application/vnd.dynageo geo +# application/vnd.easykaraoke.cdgdownload +# application/vnd.ecdis-update +application/vnd.ecowin.chart mag +# application/vnd.ecowin.filerequest +# application/vnd.ecowin.fileupdate +# application/vnd.ecowin.series +# application/vnd.ecowin.seriesrequest +# application/vnd.ecowin.seriesupdate +# application/vnd.emclient.accessrequest+xml +application/vnd.enliven nml +application/vnd.epson.esf esf +application/vnd.epson.msf msf +application/vnd.epson.quickanime qam +application/vnd.epson.salt slt +application/vnd.epson.ssf ssf +# application/vnd.ericsson.quickcall +application/vnd.eszigno3+xml es3 et3 +# application/vnd.etsi.aoc+xml +# application/vnd.etsi.cug+xml +# application/vnd.etsi.iptvcommand+xml +# application/vnd.etsi.iptvdiscovery+xml +# application/vnd.etsi.iptvprofile+xml +# application/vnd.etsi.iptvsad-bc+xml +# application/vnd.etsi.iptvsad-cod+xml +# application/vnd.etsi.iptvsad-npvr+xml +# application/vnd.etsi.iptvservice+xml +# application/vnd.etsi.iptvsync+xml +# application/vnd.etsi.iptvueprofile+xml +# application/vnd.etsi.mcid+xml +# application/vnd.etsi.overload-control-policy-dataset+xml +# application/vnd.etsi.sci+xml +# application/vnd.etsi.simservs+xml +# application/vnd.etsi.tsl+xml +# application/vnd.etsi.tsl.der +# application/vnd.eudora.data +application/vnd.ezpix-album ez2 +application/vnd.ezpix-package ez3 +# application/vnd.f-secure.mobile +application/vnd.fdf fdf +application/vnd.fdsn.mseed mseed +application/vnd.fdsn.seed seed dataless +# application/vnd.ffsns +# application/vnd.fints +application/vnd.flographit gph +application/vnd.fluxtime.clip ftc +# application/vnd.font-fontforge-sfd +application/vnd.framemaker fm frame maker book +application/vnd.frogans.fnc fnc +application/vnd.frogans.ltf ltf +application/vnd.fsc.weblaunch fsc +application/vnd.fujitsu.oasys oas +application/vnd.fujitsu.oasys2 oa2 +application/vnd.fujitsu.oasys3 oa3 +application/vnd.fujitsu.oasysgp fg5 +application/vnd.fujitsu.oasysprs bh2 +# application/vnd.fujixerox.art-ex +# application/vnd.fujixerox.art4 +# application/vnd.fujixerox.hbpl +application/vnd.fujixerox.ddd ddd +application/vnd.fujixerox.docuworks xdw +application/vnd.fujixerox.docuworks.binder xbd +# application/vnd.fut-misnet +application/vnd.fuzzysheet fzs +application/vnd.genomatix.tuxedo txd +# application/vnd.geocube+xml +application/vnd.geogebra.file ggb +application/vnd.geogebra.tool ggt +application/vnd.geometry-explorer gex gre +application/vnd.geonext gxt +application/vnd.geoplan g2w +application/vnd.geospace g3w +# application/vnd.globalplatform.card-content-mgt +# application/vnd.globalplatform.card-content-mgt-response +application/vnd.gmx gmx +application/vnd.google-earth.kml+xml kml +application/vnd.google-earth.kmz kmz +application/vnd.grafeq gqf gqs +# application/vnd.gridmp +application/vnd.groove-account gac +application/vnd.groove-help ghf +application/vnd.groove-identity-message gim +application/vnd.groove-injector grv +application/vnd.groove-tool-message gtm +application/vnd.groove-tool-template tpl +application/vnd.groove-vcard vcg +application/vnd.hal+xml hal +application/vnd.handheld-entertainment+xml zmm +application/vnd.hbci hbci +# application/vnd.hcl-bireports +application/vnd.hhe.lesson-player les +application/vnd.hp-hpgl hpgl +application/vnd.hp-hpid hpid +application/vnd.hp-hps hps +application/vnd.hp-jlyt jlt +application/vnd.hp-pcl pcl +application/vnd.hp-pclxl pclxl +# application/vnd.httphone +application/vnd.hydrostatix.sof-data sfd-hdstx +application/vnd.hzn-3d-crossword x3d +# application/vnd.ibm.afplinedata +# application/vnd.ibm.electronic-media +application/vnd.ibm.minipay mpy +application/vnd.ibm.modcap afp listafp list3820 +application/vnd.ibm.rights-management irm +application/vnd.ibm.secure-container sc +application/vnd.iccprofile icc icm +application/vnd.igloader igl +application/vnd.immervision-ivp ivp +application/vnd.immervision-ivu ivu +# application/vnd.informedcontrol.rms+xml +# application/vnd.informix-visionary +# application/vnd.infotech.project +# application/vnd.infotech.project+xml +application/vnd.insors.igm igm +application/vnd.intercon.formnet xpw xpx +application/vnd.intergeo i2g +# application/vnd.intertrust.digibox +# application/vnd.intertrust.nncp +application/vnd.intu.qbo qbo +application/vnd.intu.qfx qfx +# application/vnd.iptc.g2.conceptitem+xml +# application/vnd.iptc.g2.knowledgeitem+xml +# application/vnd.iptc.g2.newsitem+xml +# application/vnd.iptc.g2.packageitem+xml +application/vnd.ipunplugged.rcprofile rcprofile +application/vnd.irepository.package+xml irp +application/vnd.is-xpr xpr +application/vnd.isac.fcs fcs +application/vnd.jam jam +# application/vnd.japannet-directory-service +# application/vnd.japannet-jpnstore-wakeup +# application/vnd.japannet-payment-wakeup +# application/vnd.japannet-registration +# application/vnd.japannet-registration-wakeup +# application/vnd.japannet-setstore-wakeup +# application/vnd.japannet-verification +# application/vnd.japannet-verification-wakeup +application/vnd.jcp.javame.midlet-rms rms +application/vnd.jisp jisp +application/vnd.joost.joda-archive joda +application/vnd.kahootz ktz ktr +application/vnd.kde.karbon karbon +application/vnd.kde.kchart chrt +application/vnd.kde.kformula kfo +application/vnd.kde.kivio flw +application/vnd.kde.kontour kon +application/vnd.kde.kpresenter kpr kpt +application/vnd.kde.kspread ksp +application/vnd.kde.kword kwd kwt +application/vnd.kenameaapp htke +application/vnd.kidspiration kia +application/vnd.kinar kne knp +application/vnd.koan skp skd skt skm +application/vnd.kodak-descriptor sse +application/vnd.las.las+xml lasxml +# application/vnd.liberty-request+xml +application/vnd.llamagraphics.life-balance.desktop lbd +application/vnd.llamagraphics.life-balance.exchange+xml lbe +application/vnd.lotus-1-2-3 123 +application/vnd.lotus-approach apr +application/vnd.lotus-freelance pre +application/vnd.lotus-notes nsf +application/vnd.lotus-organizer org +application/vnd.lotus-screencam scm +application/vnd.lotus-wordpro lwp +application/vnd.macports.portpkg portpkg +# application/vnd.marlin.drm.actiontoken+xml +# application/vnd.marlin.drm.conftoken+xml +# application/vnd.marlin.drm.license+xml +# application/vnd.marlin.drm.mdcf +application/vnd.mcd mcd +application/vnd.medcalcdata mc1 +application/vnd.mediastation.cdkey cdkey +# application/vnd.meridian-slingshot +application/vnd.mfer mwf +application/vnd.mfmp mfm +application/vnd.micrografx.flo flo +application/vnd.micrografx.igx igx +application/vnd.mif mif +# application/vnd.minisoft-hp3000-save +# application/vnd.mitsubishi.misty-guard.trustweb +application/vnd.mobius.daf daf +application/vnd.mobius.dis dis +application/vnd.mobius.mbk mbk +application/vnd.mobius.mqy mqy +application/vnd.mobius.msl msl +application/vnd.mobius.plc plc +application/vnd.mobius.txf txf +application/vnd.mophun.application mpn +application/vnd.mophun.certificate mpc +# application/vnd.motorola.flexsuite +# application/vnd.motorola.flexsuite.adsi +# application/vnd.motorola.flexsuite.fis +# application/vnd.motorola.flexsuite.gotap +# application/vnd.motorola.flexsuite.kmr +# application/vnd.motorola.flexsuite.ttc +# application/vnd.motorola.flexsuite.wem +# application/vnd.motorola.iprm +application/vnd.mozilla.xul+xml xul +application/vnd.ms-artgalry cil +# application/vnd.ms-asf +application/vnd.ms-cab-compressed cab +application/vnd.ms-excel xls xlm xla xlc xlt xlw +application/vnd.ms-excel.addin.macroenabled.12 xlam +application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb +application/vnd.ms-excel.sheet.macroenabled.12 xlsm +application/vnd.ms-excel.template.macroenabled.12 xltm +application/vnd.ms-fontobject eot +application/vnd.ms-htmlhelp chm +application/vnd.ms-ims ims +application/vnd.ms-lrm lrm +# application/vnd.ms-office.activex+xml +application/vnd.ms-officetheme thmx +application/vnd.ms-pki.seccat cat +application/vnd.ms-pki.stl stl +# application/vnd.ms-playready.initiator+xml +application/vnd.ms-powerpoint ppt pps pot +application/vnd.ms-powerpoint.addin.macroenabled.12 ppam +application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm +application/vnd.ms-powerpoint.slide.macroenabled.12 sldm +application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm +application/vnd.ms-powerpoint.template.macroenabled.12 potm +application/vnd.ms-project mpp mpt +# application/vnd.ms-tnef +# application/vnd.ms-wmdrm.lic-chlg-req +# application/vnd.ms-wmdrm.lic-resp +# application/vnd.ms-wmdrm.meter-chlg-req +# application/vnd.ms-wmdrm.meter-resp +application/vnd.ms-word.document.macroenabled.12 docm +application/vnd.ms-word.template.macroenabled.12 dotm +application/vnd.ms-works wps wks wcm wdb +application/vnd.ms-wpl wpl +application/vnd.ms-xpsdocument xps +application/vnd.mseq mseq +# application/vnd.msign +# application/vnd.multiad.creator +# application/vnd.multiad.creator.cif +# application/vnd.music-niff +application/vnd.musician mus +application/vnd.muvee.style msty +# application/vnd.ncd.control +# application/vnd.ncd.reference +# application/vnd.nervana +# application/vnd.netfpx +application/vnd.neurolanguage.nlu nlu +application/vnd.noblenet-directory nnd +application/vnd.noblenet-sealer nns +application/vnd.noblenet-web nnw +# application/vnd.nokia.catalogs +# application/vnd.nokia.conml+wbxml +# application/vnd.nokia.conml+xml +# application/vnd.nokia.isds-radio-presets +# application/vnd.nokia.iptv.config+xml +# application/vnd.nokia.landmark+wbxml +# application/vnd.nokia.landmark+xml +# application/vnd.nokia.landmarkcollection+xml +# application/vnd.nokia.n-gage.ac+xml +application/vnd.nokia.n-gage.data ngdat +application/vnd.nokia.n-gage.symbian.install n-gage +# application/vnd.nokia.ncd +# application/vnd.nokia.pcd+wbxml +# application/vnd.nokia.pcd+xml +application/vnd.nokia.radio-preset rpst +application/vnd.nokia.radio-presets rpss +application/vnd.novadigm.edm edm +application/vnd.novadigm.edx edx +application/vnd.novadigm.ext ext +# application/vnd.ntt-local.file-transfer +# application/vnd.ntt-local.sip-ta_remote +# application/vnd.ntt-local.sip-ta_tcp_stream +application/vnd.oasis.opendocument.chart odc +application/vnd.oasis.opendocument.chart-template otc +application/vnd.oasis.opendocument.database odb +application/vnd.oasis.opendocument.formula odf +application/vnd.oasis.opendocument.formula-template odft +application/vnd.oasis.opendocument.graphics odg +application/vnd.oasis.opendocument.graphics-template otg +application/vnd.oasis.opendocument.image odi +application/vnd.oasis.opendocument.image-template oti +application/vnd.oasis.opendocument.presentation odp +application/vnd.oasis.opendocument.presentation-template otp +application/vnd.oasis.opendocument.spreadsheet ods +application/vnd.oasis.opendocument.spreadsheet-template ots +application/vnd.oasis.opendocument.text odt +application/vnd.oasis.opendocument.text-master odm +application/vnd.oasis.opendocument.text-template ott +application/vnd.oasis.opendocument.text-web oth +# application/vnd.obn +# application/vnd.oipf.contentaccessdownload+xml +# application/vnd.oipf.contentaccessstreaming+xml +# application/vnd.oipf.cspg-hexbinary +# application/vnd.oipf.dae.svg+xml +# application/vnd.oipf.dae.xhtml+xml +# application/vnd.oipf.mippvcontrolmessage+xml +# application/vnd.oipf.pae.gem +# application/vnd.oipf.spdiscovery+xml +# application/vnd.oipf.spdlist+xml +# application/vnd.oipf.ueprofile+xml +# application/vnd.oipf.userprofile+xml +application/vnd.olpc-sugar xo +# application/vnd.oma-scws-config +# application/vnd.oma-scws-http-request +# application/vnd.oma-scws-http-response +# application/vnd.oma.bcast.associated-procedure-parameter+xml +# application/vnd.oma.bcast.drm-trigger+xml +# application/vnd.oma.bcast.imd+xml +# application/vnd.oma.bcast.ltkm +# application/vnd.oma.bcast.notification+xml +# application/vnd.oma.bcast.provisioningtrigger +# application/vnd.oma.bcast.sgboot +# application/vnd.oma.bcast.sgdd+xml +# application/vnd.oma.bcast.sgdu +# application/vnd.oma.bcast.simple-symbol-container +# application/vnd.oma.bcast.smartcard-trigger+xml +# application/vnd.oma.bcast.sprov+xml +# application/vnd.oma.bcast.stkm +# application/vnd.oma.cab-address-book+xml +# application/vnd.oma.cab-pcc+xml +# application/vnd.oma.dcd +# application/vnd.oma.dcdc +application/vnd.oma.dd2+xml dd2 +# application/vnd.oma.drm.risd+xml +# application/vnd.oma.group-usage-list+xml +# application/vnd.oma.poc.detailed-progress-report+xml +# application/vnd.oma.poc.final-report+xml +# application/vnd.oma.poc.groups+xml +# application/vnd.oma.poc.invocation-descriptor+xml +# application/vnd.oma.poc.optimized-progress-report+xml +# application/vnd.oma.push +# application/vnd.oma.scidm.messages+xml +# application/vnd.oma.xcap-directory+xml +# application/vnd.omads-email+xml +# application/vnd.omads-file+xml +# application/vnd.omads-folder+xml +# application/vnd.omaloc-supl-init +application/vnd.openofficeorg.extension oxt +# application/vnd.openxmlformats-officedocument.custom-properties+xml +# application/vnd.openxmlformats-officedocument.customxmlproperties+xml +# application/vnd.openxmlformats-officedocument.drawing+xml +# application/vnd.openxmlformats-officedocument.drawingml.chart+xml +# application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml +# application/vnd.openxmlformats-officedocument.extended-properties+xml +# application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml +# application/vnd.openxmlformats-officedocument.presentationml.comments+xml +# application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml +# application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml +# application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml +application/vnd.openxmlformats-officedocument.presentationml.presentation pptx +# application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.presprops+xml +application/vnd.openxmlformats-officedocument.presentationml.slide sldx +# application/vnd.openxmlformats-officedocument.presentationml.slide+xml +# application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml +# application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml +application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx +# application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml +# application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml +# application/vnd.openxmlformats-officedocument.presentationml.tags+xml +application/vnd.openxmlformats-officedocument.presentationml.template potx +# application/vnd.openxmlformats-officedocument.presentationml.template.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx +# application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml +application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx +# application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml +# application/vnd.openxmlformats-officedocument.theme+xml +# application/vnd.openxmlformats-officedocument.themeoverride+xml +# application/vnd.openxmlformats-officedocument.vmldrawing +# application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml +application/vnd.openxmlformats-officedocument.wordprocessingml.document docx +# application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml +application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx +# application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml +# application/vnd.openxmlformats-package.core-properties+xml +# application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml +# application/vnd.openxmlformats-package.relationships+xml +# application/vnd.quobject-quoxdocument +# application/vnd.osa.netdeploy +application/vnd.osgeo.mapguide.package mgp +# application/vnd.osgi.bundle +application/vnd.osgi.dp dp +# application/vnd.otps.ct-kip+xml +application/vnd.palm pdb pqa oprc +# application/vnd.paos.xml +application/vnd.pawaafile paw +application/vnd.pg.format str +application/vnd.pg.osasli ei6 +# application/vnd.piaccess.application-licence +application/vnd.picsel efif +application/vnd.pmi.widget wg +# application/vnd.poc.group-advertisement+xml +application/vnd.pocketlearn plf +application/vnd.powerbuilder6 pbd +# application/vnd.powerbuilder6-s +# application/vnd.powerbuilder7 +# application/vnd.powerbuilder7-s +# application/vnd.powerbuilder75 +# application/vnd.powerbuilder75-s +# application/vnd.preminet +application/vnd.previewsystems.box box +application/vnd.proteus.magazine mgz +application/vnd.publishare-delta-tree qps +application/vnd.pvi.ptid1 ptid +# application/vnd.pwg-multiplexed +# application/vnd.pwg-xhtml-print+xml +# application/vnd.qualcomm.brew-app-res +application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb +# application/vnd.radisys.moml+xml +# application/vnd.radisys.msml+xml +# application/vnd.radisys.msml-audit+xml +# application/vnd.radisys.msml-audit-conf+xml +# application/vnd.radisys.msml-audit-conn+xml +# application/vnd.radisys.msml-audit-dialog+xml +# application/vnd.radisys.msml-audit-stream+xml +# application/vnd.radisys.msml-conf+xml +# application/vnd.radisys.msml-dialog+xml +# application/vnd.radisys.msml-dialog-base+xml +# application/vnd.radisys.msml-dialog-fax-detect+xml +# application/vnd.radisys.msml-dialog-fax-sendrecv+xml +# application/vnd.radisys.msml-dialog-group+xml +# application/vnd.radisys.msml-dialog-speech+xml +# application/vnd.radisys.msml-dialog-transform+xml +# application/vnd.rainstor.data +# application/vnd.rapid +application/vnd.realvnc.bed bed +application/vnd.recordare.musicxml mxl +application/vnd.recordare.musicxml+xml musicxml +# application/vnd.renlearn.rlprint +application/vnd.rig.cryptonote cryptonote +application/vnd.rim.cod cod +application/vnd.rn-realmedia rm +application/vnd.route66.link66+xml link66 +# application/vnd.ruckus.download +# application/vnd.s3sms +application/vnd.sailingtracker.track st +# application/vnd.sbm.cid +# application/vnd.sbm.mid2 +# application/vnd.scribus +# application/vnd.sealed.3df +# application/vnd.sealed.csf +# application/vnd.sealed.doc +# application/vnd.sealed.eml +# application/vnd.sealed.mht +# application/vnd.sealed.net +# application/vnd.sealed.ppt +# application/vnd.sealed.tiff +# application/vnd.sealed.xls +# application/vnd.sealedmedia.softseal.html +# application/vnd.sealedmedia.softseal.pdf +application/vnd.seemail see +application/vnd.sema sema +application/vnd.semd semd +application/vnd.semf semf +application/vnd.shana.informed.formdata ifm +application/vnd.shana.informed.formtemplate itp +application/vnd.shana.informed.interchange iif +application/vnd.shana.informed.package ipk +application/vnd.simtech-mindmapper twd twds +application/vnd.smaf mmf +# application/vnd.smart.notebook +application/vnd.smart.teacher teacher +# application/vnd.software602.filler.form+xml +# application/vnd.software602.filler.form-xml-zip +application/vnd.solent.sdkm+xml sdkm sdkd +application/vnd.spotfire.dxp dxp +application/vnd.spotfire.sfs sfs +# application/vnd.sss-cod +# application/vnd.sss-dtf +# application/vnd.sss-ntf +application/vnd.stardivision.calc sdc +application/vnd.stardivision.draw sda +application/vnd.stardivision.impress sdd +application/vnd.stardivision.math smf +application/vnd.stardivision.writer sdw vor +application/vnd.stardivision.writer-global sgl +application/vnd.stepmania.stepchart sm +# application/vnd.street-stream +application/vnd.sun.xml.calc sxc +application/vnd.sun.xml.calc.template stc +application/vnd.sun.xml.draw sxd +application/vnd.sun.xml.draw.template std +application/vnd.sun.xml.impress sxi +application/vnd.sun.xml.impress.template sti +application/vnd.sun.xml.math sxm +application/vnd.sun.xml.writer sxw +application/vnd.sun.xml.writer.global sxg +application/vnd.sun.xml.writer.template stw +# application/vnd.sun.wadl+xml +application/vnd.sus-calendar sus susp +application/vnd.svd svd +# application/vnd.swiftview-ics +application/vnd.symbian.install sis sisx +application/vnd.syncml+xml xsm +application/vnd.syncml.dm+wbxml bdm +application/vnd.syncml.dm+xml xdm +# application/vnd.syncml.dm.notification +# application/vnd.syncml.ds.notification +application/vnd.tao.intent-module-archive tao +application/vnd.tmobile-livetv tmo +application/vnd.trid.tpt tpt +application/vnd.triscape.mxs mxs +application/vnd.trueapp tra +# application/vnd.truedoc +# application/vnd.ubisoft.webplayer +application/vnd.ufdl ufd ufdl +application/vnd.uiq.theme utz +application/vnd.umajin umj +application/vnd.unity unityweb +application/vnd.uoml+xml uoml +# application/vnd.uplanet.alert +# application/vnd.uplanet.alert-wbxml +# application/vnd.uplanet.bearer-choice +# application/vnd.uplanet.bearer-choice-wbxml +# application/vnd.uplanet.cacheop +# application/vnd.uplanet.cacheop-wbxml +# application/vnd.uplanet.channel +# application/vnd.uplanet.channel-wbxml +# application/vnd.uplanet.list +# application/vnd.uplanet.list-wbxml +# application/vnd.uplanet.listcmd +# application/vnd.uplanet.listcmd-wbxml +# application/vnd.uplanet.signal +application/vnd.vcx vcx +# application/vnd.vd-study +# application/vnd.vectorworks +# application/vnd.verimatrix.vcas +# application/vnd.vidsoft.vidconference +application/vnd.visio vsd vst vss vsw +application/vnd.visionary vis +# application/vnd.vividence.scriptfile +application/vnd.vsf vsf +# application/vnd.wap.sic +# application/vnd.wap.slc +application/vnd.wap.wbxml wbxml +application/vnd.wap.wmlc wmlc +application/vnd.wap.wmlscriptc wmlsc +application/vnd.webturbo wtb +# application/vnd.wfa.wsc +# application/vnd.wmc +# application/vnd.wmf.bootstrap +# application/vnd.wolfram.mathematica +# application/vnd.wolfram.mathematica.package +application/vnd.wolfram.player nbp +application/vnd.wordperfect wpd +application/vnd.wqd wqd +# application/vnd.wrq-hp3000-labelled +application/vnd.wt.stf stf +# application/vnd.wv.csp+wbxml +# application/vnd.wv.csp+xml +# application/vnd.wv.ssp+xml +application/vnd.xara xar +application/vnd.xfdl xfdl +# application/vnd.xfdl.webform +# application/vnd.xmi+xml +# application/vnd.xmpie.cpkg +# application/vnd.xmpie.dpkg +# application/vnd.xmpie.plan +# application/vnd.xmpie.ppkg +# application/vnd.xmpie.xlim +application/vnd.yamaha.hv-dic hvd +application/vnd.yamaha.hv-script hvs +application/vnd.yamaha.hv-voice hvp +application/vnd.yamaha.openscoreformat osf +application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg +# application/vnd.yamaha.remote-setup +application/vnd.yamaha.smaf-audio saf +application/vnd.yamaha.smaf-phrase spf +# application/vnd.yamaha.tunnel-udpencap +application/vnd.yellowriver-custom-menu cmp +application/vnd.zul zir zirz +application/vnd.zzazz.deck+xml zaz +application/voicexml+xml vxml +# application/vq-rtcpxr +# application/watcherinfo+xml +# application/whoispp-query +# application/whoispp-response +application/widget wgt +application/winhlp hlp +# application/wita +# application/wordperfect5.1 +application/wsdl+xml wsdl +application/wspolicy+xml wspolicy +application/x-7z-compressed 7z +application/x-abiword abw +application/x-ace-compressed ace +application/x-authorware-bin aab x32 u32 vox +application/x-authorware-map aam +application/x-authorware-seg aas +application/x-bcpio bcpio +application/x-bittorrent torrent +application/x-bzip bz +application/x-bzip2 bz2 boz +application/x-cdlink vcd +application/x-chat chat +application/x-chess-pgn pgn +# application/x-compress +application/x-cpio cpio +application/x-csh csh +application/x-debian-package deb udeb +application/x-director dir dcr dxr cst cct cxt w3d fgd swa +application/x-doom wad +application/x-dtbncx+xml ncx +application/x-dtbook+xml dtb +application/x-dtbresource+xml res +application/x-dvi dvi +application/x-font-bdf bdf +# application/x-font-dos +# application/x-font-framemaker +application/x-font-ghostscript gsf +# application/x-font-libgrx +application/x-font-linux-psf psf +application/x-font-otf otf +application/x-font-pcf pcf +application/x-font-snf snf +# application/x-font-speedo +# application/x-font-sunos-news +application/x-font-ttf ttf ttc +application/x-font-type1 pfa pfb pfm afm +application/x-font-woff woff +# application/x-font-vfont +application/x-futuresplash spl +application/x-gnumeric gnumeric +application/x-gtar gtar +# application/x-gzip +application/x-hdf hdf +application/x-java-jnlp-file jnlp +application/x-latex latex +application/x-mobipocket-ebook prc mobi +application/x-ms-application application +application/x-ms-wmd wmd +application/x-ms-wmz wmz +application/x-ms-xbap xbap +application/x-msaccess mdb +application/x-msbinder obd +application/x-mscardfile crd +application/x-msclip clp +application/x-msdownload exe dll com bat msi +application/x-msmediaview mvb m13 m14 +application/x-msmetafile wmf +application/x-msmoney mny +application/x-mspublisher pub +application/x-msschedule scd +application/x-msterminal trm +application/x-mswrite wri +application/x-netcdf nc cdf +application/x-pkcs12 p12 pfx +application/x-pkcs7-certificates p7b spc +application/x-pkcs7-certreqresp p7r +application/x-rar-compressed rar +application/x-sh sh +application/x-shar shar +application/x-shockwave-flash swf +application/x-silverlight-app xap +application/x-stuffit sit +application/x-stuffitx sitx +application/x-sv4cpio sv4cpio +application/x-sv4crc sv4crc +application/x-tar tar +application/x-tcl tcl +application/x-tex tex +application/x-tex-tfm tfm +application/x-texinfo texinfo texi +application/x-ustar ustar +application/x-wais-source src +application/x-x509-ca-cert der crt +application/x-xfig fig +application/x-xpinstall xpi +# application/x400-bp +# application/xcap-att+xml +# application/xcap-caps+xml +application/xcap-diff+xml xdf +# application/xcap-el+xml +# application/xcap-error+xml +# application/xcap-ns+xml +# application/xcon-conference-info-diff+xml +# application/xcon-conference-info+xml +application/xenc+xml xenc +application/xhtml+xml xhtml xht +# application/xhtml-voice+xml +application/xml xml xsl +application/xml-dtd dtd +# application/xml-external-parsed-entity +# application/xmpp+xml +application/xop+xml xop +application/xslt+xml xslt +application/xspf+xml xspf +application/xv+xml mxml xhvml xvml xvm +application/yang yang +application/yin+xml yin +application/zip zip +# audio/1d-interleaved-parityfec +# audio/32kadpcm +# audio/3gpp +# audio/3gpp2 +# audio/ac3 +audio/adpcm adp +# audio/amr +# audio/amr-wb +# audio/amr-wb+ +# audio/asc +# audio/atrac-advanced-lossless +# audio/atrac-x +# audio/atrac3 +audio/basic au snd +# audio/bv16 +# audio/bv32 +# audio/clearmode +# audio/cn +# audio/dat12 +# audio/dls +# audio/dsr-es201108 +# audio/dsr-es202050 +# audio/dsr-es202211 +# audio/dsr-es202212 +# audio/dvi4 +# audio/eac3 +# audio/evrc +# audio/evrc-qcp +# audio/evrc0 +# audio/evrc1 +# audio/evrcb +# audio/evrcb0 +# audio/evrcb1 +# audio/evrcwb +# audio/evrcwb0 +# audio/evrcwb1 +# audio/example +# audio/g719 +# audio/g722 +# audio/g7221 +# audio/g723 +# audio/g726-16 +# audio/g726-24 +# audio/g726-32 +# audio/g726-40 +# audio/g728 +# audio/g729 +# audio/g7291 +# audio/g729d +# audio/g729e +# audio/gsm +# audio/gsm-efr +# audio/gsm-hr-08 +# audio/ilbc +# audio/l16 +# audio/l20 +# audio/l24 +# audio/l8 +# audio/lpc +audio/midi mid midi kar rmi +# audio/mobile-xmf +audio/mp4 mp4a +# audio/mp4a-latm +# audio/mpa +# audio/mpa-robust +audio/mpeg mpga mp2 mp2a mp3 m2a m3a +# audio/mpeg4-generic +audio/ogg oga ogg spx +# audio/parityfec +# audio/pcma +# audio/pcma-wb +# audio/pcmu-wb +# audio/pcmu +# audio/prs.sid +# audio/qcelp +# audio/red +# audio/rtp-enc-aescm128 +# audio/rtp-midi +# audio/rtx +# audio/smv +# audio/smv0 +# audio/smv-qcp +# audio/sp-midi +# audio/speex +# audio/t140c +# audio/t38 +# audio/telephone-event +# audio/tone +# audio/uemclip +# audio/ulpfec +# audio/vdvi +# audio/vmr-wb +# audio/vnd.3gpp.iufp +# audio/vnd.4sb +# audio/vnd.audiokoz +# audio/vnd.celp +# audio/vnd.cisco.nse +# audio/vnd.cmles.radio-events +# audio/vnd.cns.anp1 +# audio/vnd.cns.inf1 +audio/vnd.dece.audio uva uvva +audio/vnd.digital-winds eol +# audio/vnd.dlna.adts +# audio/vnd.dolby.heaac.1 +# audio/vnd.dolby.heaac.2 +# audio/vnd.dolby.mlp +# audio/vnd.dolby.mps +# audio/vnd.dolby.pl2 +# audio/vnd.dolby.pl2x +# audio/vnd.dolby.pl2z +# audio/vnd.dolby.pulse.1 +audio/vnd.dra dra +audio/vnd.dts dts +audio/vnd.dts.hd dtshd +# audio/vnd.everad.plj +# audio/vnd.hns.audio +audio/vnd.lucent.voice lvp +audio/vnd.ms-playready.media.pya pya +# audio/vnd.nokia.mobile-xmf +# audio/vnd.nortel.vbk +audio/vnd.nuera.ecelp4800 ecelp4800 +audio/vnd.nuera.ecelp7470 ecelp7470 +audio/vnd.nuera.ecelp9600 ecelp9600 +# audio/vnd.octel.sbc +# audio/vnd.qcelp +# audio/vnd.rhetorex.32kadpcm +audio/vnd.rip rip +# audio/vnd.sealedmedia.softseal.mpeg +# audio/vnd.vmx.cvsd +# audio/vorbis +# audio/vorbis-config +audio/webm weba +audio/x-aac aac +audio/x-aiff aif aiff aifc +audio/x-mpegurl m3u +audio/x-ms-wax wax +audio/x-ms-wma wma +audio/x-pn-realaudio ram ra +audio/x-pn-realaudio-plugin rmp +audio/x-wav wav +chemical/x-cdx cdx +chemical/x-cif cif +chemical/x-cmdf cmdf +chemical/x-cml cml +chemical/x-csml csml +# chemical/x-pdb +chemical/x-xyz xyz +image/bmp bmp +image/cgm cgm +# image/example +# image/fits +image/g3fax g3 +image/gif gif +image/ief ief +# image/jp2 +image/jpeg jpeg jpg jpe +# image/jpm +# image/jpx +image/ktx ktx +# image/naplps +image/png png +image/prs.btif btif +# image/prs.pti +image/svg+xml svg svgz +# image/t38 +image/tiff tiff tif +# image/tiff-fx +image/vnd.adobe.photoshop psd +# image/vnd.cns.inf2 +image/vnd.dece.graphic uvi uvvi uvg uvvg +image/vnd.dvb.subtitle sub +image/vnd.djvu djvu djv +image/vnd.dwg dwg +image/vnd.dxf dxf +image/vnd.fastbidsheet fbs +image/vnd.fpx fpx +image/vnd.fst fst +image/vnd.fujixerox.edmics-mmr mmr +image/vnd.fujixerox.edmics-rlc rlc +# image/vnd.globalgraphics.pgb +# image/vnd.microsoft.icon +# image/vnd.mix +image/vnd.ms-modi mdi +image/vnd.net-fpx npx +# image/vnd.radiance +# image/vnd.sealed.png +# image/vnd.sealedmedia.softseal.gif +# image/vnd.sealedmedia.softseal.jpg +# image/vnd.svf +image/vnd.wap.wbmp wbmp +image/vnd.xiff xif +image/webp webp +image/x-cmu-raster ras +image/x-cmx cmx +image/x-freehand fh fhc fh4 fh5 fh7 +image/x-icon ico +image/x-pcx pcx +image/x-pict pic pct +image/x-portable-anymap pnm +image/x-portable-bitmap pbm +image/x-portable-graymap pgm +image/x-portable-pixmap ppm +image/x-rgb rgb +image/x-xbitmap xbm +image/x-xpixmap xpm +image/x-xwindowdump xwd +# message/cpim +# message/delivery-status +# message/disposition-notification +# message/example +# message/external-body +# message/feedback-report +# message/global +# message/global-delivery-status +# message/global-disposition-notification +# message/global-headers +# message/http +# message/imdn+xml +# message/news +# message/partial +message/rfc822 eml mime +# message/s-http +# message/sip +# message/sipfrag +# message/tracking-status +# message/vnd.si.simp +# model/example +model/iges igs iges +model/mesh msh mesh silo +model/vnd.collada+xml dae +model/vnd.dwf dwf +# model/vnd.flatland.3dml +model/vnd.gdl gdl +# model/vnd.gs-gdl +# model/vnd.gs.gdl +model/vnd.gtw gtw +# model/vnd.moml+xml +model/vnd.mts mts +# model/vnd.parasolid.transmit.binary +# model/vnd.parasolid.transmit.text +model/vnd.vtu vtu +model/vrml wrl vrml +# multipart/alternative +# multipart/appledouble +# multipart/byteranges +# multipart/digest +# multipart/encrypted +# multipart/example +# multipart/form-data +# multipart/header-set +# multipart/mixed +# multipart/parallel +# multipart/related +# multipart/report +# multipart/signed +# multipart/voice-message +# text/1d-interleaved-parityfec +text/calendar ics ifb +text/css css +text/csv csv +# text/directory +# text/dns +# text/ecmascript +# text/enriched +# text/example +text/html html htm +# text/javascript +text/n3 n3 +# text/parityfec +text/plain txt text conf def list log in +# text/prs.fallenstein.rst +text/prs.lines.tag dsc +# text/vnd.radisys.msml-basic-layout +# text/red +# text/rfc822-headers +text/richtext rtx +# text/rtf +# text/rtp-enc-aescm128 +# text/rtx +text/sgml sgml sgm +# text/t140 +text/tab-separated-values tsv +text/troff t tr roff man me ms +text/turtle ttl +# text/ulpfec +text/uri-list uri uris urls +# text/vnd.abc +text/vnd.curl curl +text/vnd.curl.dcurl dcurl +text/vnd.curl.scurl scurl +text/vnd.curl.mcurl mcurl +# text/vnd.dmclientscript +# text/vnd.esmertec.theme-descriptor +text/vnd.fly fly +text/vnd.fmi.flexstor flx +text/vnd.graphviz gv +text/vnd.in3d.3dml 3dml +text/vnd.in3d.spot spot +# text/vnd.iptc.newsml +# text/vnd.iptc.nitf +# text/vnd.latex-z +# text/vnd.motorola.reflex +# text/vnd.ms-mediapackage +# text/vnd.net2phone.commcenter.command +# text/vnd.si.uricatalogue +text/vnd.sun.j2me.app-descriptor jad +# text/vnd.trolltech.linguist +# text/vnd.wap.si +# text/vnd.wap.sl +text/vnd.wap.wml wml +text/vnd.wap.wmlscript wmls +text/x-asm s asm +text/x-c c cc cxx cpp h hh dic +text/x-fortran f for f77 f90 +text/x-pascal p pas +text/x-java-source java +text/x-setext etx +text/x-uuencode uu +text/x-vcalendar vcs +text/x-vcard vcf +# text/xml +# text/xml-external-parsed-entity +# video/1d-interleaved-parityfec +video/3gpp 3gp +# video/3gpp-tt +video/3gpp2 3g2 +# video/bmpeg +# video/bt656 +# video/celb +# video/dv +# video/example +video/h261 h261 +video/h263 h263 +# video/h263-1998 +# video/h263-2000 +video/h264 h264 +# video/h264-rcdo +# video/h264-svc +video/jpeg jpgv +# video/jpeg2000 +video/jpm jpm jpgm +video/mj2 mj2 mjp2 +# video/mp1s +# video/mp2p +# video/mp2t +video/mp4 mp4 mp4v mpg4 +# video/mp4v-es +video/mpeg mpeg mpg mpe m1v m2v +# video/mpeg4-generic +# video/mpv +# video/nv +video/ogg ogv +# video/parityfec +# video/pointer +video/quicktime qt mov +# video/raw +# video/rtp-enc-aescm128 +# video/rtx +# video/smpte292m +# video/ulpfec +# video/vc1 +# video/vnd.cctv +video/vnd.dece.hd uvh uvvh +video/vnd.dece.mobile uvm uvvm +# video/vnd.dece.mp4 +video/vnd.dece.pd uvp uvvp +video/vnd.dece.sd uvs uvvs +video/vnd.dece.video uvv uvvv +# video/vnd.directv.mpeg +# video/vnd.directv.mpeg-tts +# video/vnd.dlna.mpeg-tts +video/vnd.fvt fvt +# video/vnd.hns.video +# video/vnd.iptvforum.1dparityfec-1010 +# video/vnd.iptvforum.1dparityfec-2005 +# video/vnd.iptvforum.2dparityfec-1010 +# video/vnd.iptvforum.2dparityfec-2005 +# video/vnd.iptvforum.ttsavc +# video/vnd.iptvforum.ttsmpeg2 +# video/vnd.motorola.video +# video/vnd.motorola.videop +video/vnd.mpegurl mxu m4u +video/vnd.ms-playready.media.pyv pyv +# video/vnd.nokia.interleaved-multimedia +# video/vnd.nokia.videovoip +# video/vnd.objectvideo +# video/vnd.sealed.mpeg1 +# video/vnd.sealed.mpeg4 +# video/vnd.sealed.swf +# video/vnd.sealedmedia.softseal.mov +video/vnd.uvvu.mp4 uvu uvvu +video/vnd.vivo viv +video/webm webm +video/x-f4v f4v +video/x-fli fli +video/x-flv flv +video/x-m4v m4v +video/x-ms-asf asf asx +video/x-ms-wm wm +video/x-ms-wmv wmv +video/x-ms-wmx wmx +video/x-ms-wvx wvx +video/x-msvideo avi +video/x-sgi-movie movie +x-conference/x-cooltalk ice diff --git a/node_modules/express/node_modules/mime/types/node.types b/node_modules/express/node_modules/mime/types/node.types new file mode 100644 index 0000000..fdabaa4 --- /dev/null +++ b/node_modules/express/node_modules/mime/types/node.types @@ -0,0 +1,43 @@ +# What: Google Chrome Extension +# Why: To allow apps to (work) be served with the right content type header. +# http://codereview.chromium.org/2830017 +# Added by: niftylettuce +application/x-chrome-extension crx + +# What: OTF Message Silencer +# Why: To silence the "Resource interpreted as font but transferred with MIME +# type font/otf" message that occurs in Google Chrome +# Added by: niftylettuce +font/opentype otf + +# What: HTC support +# Why: To properly render .htc files such as CSS3PIE +# Added by: niftylettuce +text/x-component htc + +# What: HTML5 application cache manifest +# Why: De-facto standard. Required by Mozilla browser when serving HTML5 apps +# per https://developer.mozilla.org/en/offline_resources_in_firefox +# Added by: louisremi +text/cache-manifest appcache manifest + +# What: node binary buffer format +# Why: semi-standard extension w/in the node community +# Added by: tootallnate +application/octet-stream buffer + +# What: The "protected" MP-4 formats used by iTunes. +# Why: Required for streaming music to browsers (?) +# Added by: broofa +application/mp4 m4p +audio/mp4 m4a + +# What: Music playlist format (http://en.wikipedia.org/wiki/M3U) +# Why: See https://github.com/bentomas/node-mime/pull/6 +# Added by: mjrusso +application/x-mpegURL m3u8 + +# What: Video format, Part of RFC1890 +# Why: See https://github.com/bentomas/node-mime/pull/6 +# Added by: mjrusso +video/MP2T ts diff --git a/node_modules/express/node_modules/mkdirp/.gitignore.orig b/node_modules/express/node_modules/mkdirp/.gitignore.orig new file mode 100644 index 0000000..9303c34 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/.gitignore.orig @@ -0,0 +1,2 @@ +node_modules/ +npm-debug.log \ No newline at end of file diff --git a/node_modules/express/node_modules/mkdirp/.gitignore.rej b/node_modules/express/node_modules/mkdirp/.gitignore.rej new file mode 100644 index 0000000..69244ff --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/.gitignore.rej @@ -0,0 +1,5 @@ +--- /dev/null ++++ .gitignore +@@ -0,0 +1,2 @@ ++node_modules/ ++npm-debug.log \ No newline at end of file diff --git a/node_modules/express/node_modules/mkdirp/.npmignore b/node_modules/express/node_modules/mkdirp/.npmignore new file mode 100644 index 0000000..9303c34 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/.npmignore @@ -0,0 +1,2 @@ +node_modules/ +npm-debug.log \ No newline at end of file diff --git a/node_modules/express/node_modules/mkdirp/LICENSE b/node_modules/express/node_modules/mkdirp/LICENSE new file mode 100644 index 0000000..432d1ae --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/LICENSE @@ -0,0 +1,21 @@ +Copyright 2010 James Halliday (mail@substack.net) + +This project is free software released under the MIT/X11 license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/express/node_modules/mkdirp/README.markdown b/node_modules/express/node_modules/mkdirp/README.markdown new file mode 100644 index 0000000..0393c4e --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/README.markdown @@ -0,0 +1,21 @@ +mkdirp +====== + +Like `mkdir -p`, but in node.js! + +Example +======= + +pow.js +------ + var mkdirp = require('mkdirp'); + + mkdirp('/tmp/foo/bar/baz', 0755, function (err) { + if (err) console.error(err) + else console.log('pow!') + }); + +Output + pow! + +And now /tmp/foo/bar/baz exists, huzzah! diff --git a/node_modules/express/node_modules/mkdirp/examples/pow.js b/node_modules/express/node_modules/mkdirp/examples/pow.js new file mode 100644 index 0000000..7741462 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/examples/pow.js @@ -0,0 +1,6 @@ +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', 0755, function (err) { + if (err) console.error(err) + else console.log('pow!') +}); diff --git a/node_modules/express/node_modules/mkdirp/examples/pow.js.orig b/node_modules/express/node_modules/mkdirp/examples/pow.js.orig new file mode 100644 index 0000000..7741462 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/examples/pow.js.orig @@ -0,0 +1,6 @@ +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', 0755, function (err) { + if (err) console.error(err) + else console.log('pow!') +}); diff --git a/node_modules/express/node_modules/mkdirp/examples/pow.js.rej b/node_modules/express/node_modules/mkdirp/examples/pow.js.rej new file mode 100644 index 0000000..81e7f43 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/examples/pow.js.rej @@ -0,0 +1,19 @@ +--- examples/pow.js ++++ examples/pow.js +@@ -1,6 +1,15 @@ +-var mkdirp = require('mkdirp').mkdirp; ++var mkdirp = require('../').mkdirp, ++ mkdirpSync = require('../').mkdirpSync; + + mkdirp('/tmp/foo/bar/baz', 0755, function (err) { + if (err) console.error(err) + else console.log('pow!') + }); ++ ++try { ++ mkdirpSync('/tmp/bar/foo/baz', 0755); ++ console.log('double pow!'); ++} ++catch (ex) { ++ console.log(ex); ++} \ No newline at end of file diff --git a/node_modules/express/node_modules/mkdirp/index.js b/node_modules/express/node_modules/mkdirp/index.js new file mode 100644 index 0000000..30e9600 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/index.js @@ -0,0 +1,20 @@ +var path = require('path'); +var fs = require('fs'); + +var exports = module.exports = function mkdirP (p, mode, f) { + var cb = f || function () {}; + p = path.resolve(p); + + var ps = path.normalize(p).split('/'); + path.exists(p, function (exists) { + if (exists) cb(null); + else mkdirP(ps.slice(0,-1).join('/'), mode, function (err) { + if (err && err.code !== 'EEXIST') cb(err) + else fs.mkdir(p, mode, function (err) { + if (err && err.code !== 'EEXIST') cb(err) + else cb() + }); + }); + }); +}; +exports.mkdirp = exports.mkdirP = module.exports; diff --git a/node_modules/express/node_modules/mkdirp/package.json b/node_modules/express/node_modules/mkdirp/package.json new file mode 100644 index 0000000..f5ceb00 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/package.json @@ -0,0 +1,23 @@ +{ + "name" : "mkdirp", + "description" : "Recursively mkdir, like `mkdir -p`", + "version" : "0.0.7", + "author" : "James Halliday (http://substack.net)", + "main" : "./index", + "keywords" : [ + "mkdir", + "directory" + ], + "repository" : { + "type" : "git", + "url" : "http://github.com/substack/node-mkdirp.git" + }, + "scripts" : { + "test" : "node node_modules/tap/bin/tap.js test/*.js" + }, + "devDependencies" : { + "tap" : "0.0.x" + }, + "license" : "MIT/X11", + "engines": { "node": "*" } +} diff --git a/node_modules/express/node_modules/mkdirp/test/mkdirp.js b/node_modules/express/node_modules/mkdirp/test/mkdirp.js new file mode 100644 index 0000000..b07cd70 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/test/mkdirp.js @@ -0,0 +1,28 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('woo', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); diff --git a/node_modules/express/node_modules/mkdirp/test/race.js b/node_modules/express/node_modules/mkdirp/test/race.js new file mode 100644 index 0000000..96a0447 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/test/race.js @@ -0,0 +1,41 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('race', function (t) { + t.plan(4); + var ps = [ '', 'tmp' ]; + + for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); + } + var file = ps.join('/'); + + var res = 2; + mk(file, function () { + if (--res === 0) t.end(); + }); + + mk(file, function () { + if (--res === 0) t.end(); + }); + + function mk (file, cb) { + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + if (cb) cb(); + } + }) + }) + }); + } +}); diff --git a/node_modules/express/node_modules/mkdirp/test/rel.js b/node_modules/express/node_modules/mkdirp/test/rel.js new file mode 100644 index 0000000..7985824 --- /dev/null +++ b/node_modules/express/node_modules/mkdirp/test/rel.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('rel', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var cwd = process.cwd(); + process.chdir('/tmp'); + + var file = [x,y,z].join('/'); + + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + process.chdir(cwd); + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); diff --git a/node_modules/express/node_modules/qs/.gitignore b/node_modules/express/node_modules/qs/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/node_modules/express/node_modules/qs/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/node_modules/express/node_modules/qs/.gitmodules b/node_modules/express/node_modules/qs/.gitmodules new file mode 100644 index 0000000..49e31da --- /dev/null +++ b/node_modules/express/node_modules/qs/.gitmodules @@ -0,0 +1,6 @@ +[submodule "support/expresso"] + path = support/expresso + url = git://github.com/visionmedia/expresso.git +[submodule "support/should"] + path = support/should + url = git://github.com/visionmedia/should.js.git diff --git a/node_modules/express/node_modules/qs/History.md b/node_modules/express/node_modules/qs/History.md new file mode 100644 index 0000000..09b90be --- /dev/null +++ b/node_modules/express/node_modules/qs/History.md @@ -0,0 +1,63 @@ + +0.4.0 / 2011-11-21 +================== + + * Allow parsing of an existing object (for `bodyParser()`) [jackyz] + * Replaced expresso with mocha + +0.3.2 / 2011-11-08 +================== + + * Fixed global variable leak + +0.3.1 / 2011-08-17 +================== + + * Added `try/catch` around malformed uri components + * Add test coverage for Array native method bleed-though + +0.3.0 / 2011-07-19 +================== + + * Allow `array[index]` and `object[property]` syntaxes [Aria Stewart] + +0.2.0 / 2011-06-29 +================== + + * Added `qs.stringify()` [Cory Forsyth] + +0.1.0 / 2011-04-13 +================== + + * Added jQuery-ish array support + +0.0.7 / 2011-03-13 +================== + + * Fixed; handle empty string and `== null` in `qs.parse()` [dmit] + allows for convenient `qs.parse(url.parse(str).query)` + +0.0.6 / 2011-02-14 +================== + + * Fixed; support for implicit arrays + +0.0.4 / 2011-02-09 +================== + + * Fixed `+` as a space + +0.0.3 / 2011-02-08 +================== + + * Fixed case when right-hand value contains "]" + +0.0.2 / 2011-02-07 +================== + + * Fixed "=" presence in key + +0.0.1 / 2011-02-07 +================== + + * Initial release \ No newline at end of file diff --git a/node_modules/express/node_modules/qs/Makefile b/node_modules/express/node_modules/qs/Makefile new file mode 100644 index 0000000..e4df837 --- /dev/null +++ b/node_modules/express/node_modules/qs/Makefile @@ -0,0 +1,5 @@ + +test: + @./node_modules/.bin/mocha + +.PHONY: test \ No newline at end of file diff --git a/node_modules/express/node_modules/qs/Readme.md b/node_modules/express/node_modules/qs/Readme.md new file mode 100644 index 0000000..a3148ff --- /dev/null +++ b/node_modules/express/node_modules/qs/Readme.md @@ -0,0 +1,47 @@ +# node-querystring + + query string parser for node supporting nesting, as it was removed from `0.3.x`, so this library provides the previous and commonly desired behaviour (and twice as fast). Used by [express](http://expressjs.com), [connect](http://senchalabs.github.com/connect) and others. + +## Installation + + $ npm install qs + +## Examples + + require('qs').parse('user[name][first]=tj&user[email]=tj'); + // => { user: { name: { first: 'tj' }, email: 'tj' } } + +## Testing + +Install dev dependencies: + + $ npm install -d + +and execute: + + $ make test + +## License + +(The MIT License) + +Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/express/node_modules/qs/benchmark.js b/node_modules/express/node_modules/qs/benchmark.js new file mode 100644 index 0000000..97e2c93 --- /dev/null +++ b/node_modules/express/node_modules/qs/benchmark.js @@ -0,0 +1,17 @@ + +var qs = require('./'); + +var times = 100000 + , start = new Date + , n = times; + +console.log('times: %d', times); + +while (n--) qs.parse('foo=bar'); +console.log('simple: %dms', new Date - start); + +var start = new Date + , n = times; + +while (n--) qs.parse('user[name][first]=tj&user[name][last]=holowaychuk'); +console.log('nested: %dms', new Date - start); \ No newline at end of file diff --git a/node_modules/express/node_modules/qs/examples.js b/node_modules/express/node_modules/qs/examples.js new file mode 100644 index 0000000..9b652b0 --- /dev/null +++ b/node_modules/express/node_modules/qs/examples.js @@ -0,0 +1,48 @@ + +/** + * Module dependencies. + */ + +var qs = require('./'); + +var obj = qs.parse('foo'); +console.log(obj) + +var obj = qs.parse('foo=bar=baz'); +console.log(obj) + +var obj = qs.parse('users[]'); +console.log(obj) + +var obj = qs.parse('name=tj&email=tj@vision-media.ca'); +console.log(obj) + +var obj = qs.parse('users[]=tj&users[]=tobi&users[]=jane'); +console.log(obj) + +var obj = qs.parse('user[name][first]=tj&user[name][last]=holowaychuk'); +console.log(obj) + +var obj = qs.parse('users[][name][first]=tj&users[][name][last]=holowaychuk'); +console.log(obj) + +var obj = qs.parse('a=a&a=b&a=c'); +console.log(obj) + +var obj = qs.parse('user[tj]=tj&user[tj]=TJ'); +console.log(obj) + +var obj = qs.parse('user[names]=tj&user[names]=TJ&user[names]=Tyler'); +console.log(obj) + +var obj = qs.parse('user[name][first]=tj&user[name][first]=TJ'); +console.log(obj) + +var obj = qs.parse('user[0]=tj&user[1]=TJ'); +console.log(obj) + +var obj = qs.parse('user[0]=tj&user[]=TJ'); +console.log(obj) + +var obj = qs.parse('user[0]=tj&user[foo]=TJ'); +console.log(obj) diff --git a/node_modules/express/node_modules/qs/index.js b/node_modules/express/node_modules/qs/index.js new file mode 100644 index 0000000..d177d20 --- /dev/null +++ b/node_modules/express/node_modules/qs/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/querystring'); \ No newline at end of file diff --git a/node_modules/express/node_modules/qs/lib/querystring.js b/node_modules/express/node_modules/qs/lib/querystring.js new file mode 100644 index 0000000..36be1b0 --- /dev/null +++ b/node_modules/express/node_modules/qs/lib/querystring.js @@ -0,0 +1,262 @@ + +/*! + * querystring + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Library version. + */ + +exports.version = '0.4.0'; + +/** + * Object#toString() ref for stringify(). + */ + +var toString = Object.prototype.toString; + +/** + * Cache non-integer test regexp. + */ + +var notint = /[^0-9]/; + +function promote(parent, key) { + if (parent[key].length == 0) return parent[key] = {}; + var t = {}; + for (var i in parent[key]) t[i] = parent[key][i]; + parent[key] = t; + return t; +} + +function parse(parts, parent, key, val) { + var part = parts.shift(); + // end + if (!part) { + if (Array.isArray(parent[key])) { + parent[key].push(val); + } else if ('object' == typeof parent[key]) { + parent[key] = val; + } else if ('undefined' == typeof parent[key]) { + parent[key] = val; + } else { + parent[key] = [parent[key], val]; + } + // array + } else { + var obj = parent[key] = parent[key] || []; + if (']' == part) { + if (Array.isArray(obj)) { + if ('' != val) obj.push(val); + } else if ('object' == typeof obj) { + obj[Object.keys(obj).length] = val; + } else { + obj = parent[key] = [parent[key], val]; + } + // prop + } else if (~part.indexOf(']')) { + part = part.substr(0, part.length - 1); + if(notint.test(part) && Array.isArray(obj)) obj = promote(parent, key); + parse(parts, obj, part, val); + // key + } else { + if(notint.test(part) && Array.isArray(obj)) obj = promote(parent, key); + parse(parts, obj, part, val); + } + } +} + +/** + * Merge parent key/val pair. + */ + +function merge(parent, key, val){ + if (~key.indexOf(']')) { + var parts = key.split('[') + , len = parts.length + , last = len - 1; + parse(parts, parent, 'base', val); + // optimize + } else { + if (notint.test(key) && Array.isArray(parent.base)) { + var t = {}; + for (var k in parent.base) t[k] = parent.base[k]; + parent.base = t; + } + set(parent.base, key, val); + } + + return parent; +} + +/** + * Parse the given obj. + */ + +function parseObject(obj){ + var ret = { base: {} }; + Object.keys(obj).forEach(function(name){ + merge(ret, name, obj[name]); + }); + return ret.base; +} + +/** + * Parse the given str. + */ + +function parseString(str){ + return String(str) + .split('&') + .reduce(function(ret, pair){ + try{ + pair = decodeURIComponent(pair.replace(/\+/g, ' ')); + } catch(e) { + // ignore + } + + var eql = pair.indexOf('=') + , brace = lastBraceInKey(pair) + , key = pair.substr(0, brace || eql) + , val = pair.substr(brace || eql, pair.length) + , val = val.substr(val.indexOf('=') + 1, val.length); + + // ?foo + if ('' == key) key = pair, val = ''; + + return merge(ret, key, val); + }, { base: {} }).base; +} + +/** + * Parse the given query `str` or `obj`, returning an object. + * + * @param {String} str | {Object} obj + * @return {Object} + * @api public + */ + +exports.parse = function(str){ + if (null == str || '' == str) return {}; + return 'object' == typeof str + ? parseObject(str) + : parseString(str); +}; + +/** + * Turn the given `obj` into a query string + * + * @param {Object} obj + * @return {String} + * @api public + */ + +var stringify = exports.stringify = function(obj, prefix) { + if (Array.isArray(obj)) { + return stringifyArray(obj, prefix); + } else if ('[object Object]' == toString.call(obj)) { + return stringifyObject(obj, prefix); + } else if ('string' == typeof obj) { + return stringifyString(obj, prefix); + } else { + return prefix; + } +}; + +/** + * Stringify the given `str`. + * + * @param {String} str + * @param {String} prefix + * @return {String} + * @api private + */ + +function stringifyString(str, prefix) { + if (!prefix) throw new TypeError('stringify expects an object'); + return prefix + '=' + encodeURIComponent(str); +} + +/** + * Stringify the given `arr`. + * + * @param {Array} arr + * @param {String} prefix + * @return {String} + * @api private + */ + +function stringifyArray(arr, prefix) { + var ret = []; + if (!prefix) throw new TypeError('stringify expects an object'); + for (var i = 0; i < arr.length; i++) { + ret.push(stringify(arr[i], prefix + '[]')); + } + return ret.join('&'); +} + +/** + * Stringify the given `obj`. + * + * @param {Object} obj + * @param {String} prefix + * @return {String} + * @api private + */ + +function stringifyObject(obj, prefix) { + var ret = [] + , keys = Object.keys(obj) + , key; + for (var i = 0, len = keys.length; i < len; ++i) { + key = keys[i]; + ret.push(stringify(obj[key], prefix + ? prefix + '[' + encodeURIComponent(key) + ']' + : encodeURIComponent(key))); + } + return ret.join('&'); +} + +/** + * Set `obj`'s `key` to `val` respecting + * the weird and wonderful syntax of a qs, + * where "foo=bar&foo=baz" becomes an array. + * + * @param {Object} obj + * @param {String} key + * @param {String} val + * @api private + */ + +function set(obj, key, val) { + var v = obj[key]; + if (undefined === v) { + obj[key] = val; + } else if (Array.isArray(v)) { + v.push(val); + } else { + obj[key] = [v, val]; + } +} + +/** + * Locate last brace in `str` within the key. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function lastBraceInKey(str) { + var len = str.length + , brace + , c; + for (var i = 0; i < len; ++i) { + c = str[i]; + if (']' == c) brace = false; + if ('[' == c) brace = true; + if ('=' == c && !brace) return i; + } +} diff --git a/node_modules/express/node_modules/qs/package.json b/node_modules/express/node_modules/qs/package.json new file mode 100644 index 0000000..e04d72b --- /dev/null +++ b/node_modules/express/node_modules/qs/package.json @@ -0,0 +1,16 @@ +{ + "name": "qs", + "description": "querystring parser", + "version": "0.4.0", + "repository": { + "type" : "git", + "url" : "git://github.com/visionmedia/node-querystring.git" + }, + "devDependencies": { + "mocha": "*" + , "should": "*" + }, + "author": "TJ Holowaychuk (http://tjholowaychuk.com)", + "main": "index", + "engines": { "node": "*" } +} \ No newline at end of file diff --git a/node_modules/express/node_modules/qs/test/mocha.opts b/node_modules/express/node_modules/qs/test/mocha.opts new file mode 100644 index 0000000..521cbb2 --- /dev/null +++ b/node_modules/express/node_modules/qs/test/mocha.opts @@ -0,0 +1,2 @@ +--require should +--ui exports diff --git a/node_modules/express/node_modules/qs/test/parse.js b/node_modules/express/node_modules/qs/test/parse.js new file mode 100644 index 0000000..2a28cf3 --- /dev/null +++ b/node_modules/express/node_modules/qs/test/parse.js @@ -0,0 +1,155 @@ + +/** + * Module dependencies. + */ + +var qs = require('../'); + +module.exports = { + 'test basics': function(){ + qs.parse('0=foo').should.eql({ '0': 'foo' }); + + qs.parse('foo=c++') + .should.eql({ foo: 'c ' }); + + qs.parse('a[>=]=23') + .should.eql({ a: { '>=': '23' }}); + + qs.parse('a[<=>]==23') + .should.eql({ a: { '<=>': '=23' }}); + + qs.parse('a[==]=23') + .should.eql({ a: { '==': '23' }}); + + qs.parse('foo') + .should.eql({ foo: '' }); + + qs.parse('foo=bar') + .should.eql({ foo: 'bar' }); + + qs.parse('foo%3Dbar=baz') + .should.eql({ foo: 'bar=baz' }); + + qs.parse(' foo = bar = baz ') + .should.eql({ ' foo ': ' bar = baz ' }); + + qs.parse('foo=bar=baz') + .should.eql({ foo: 'bar=baz' }); + + qs.parse('foo=bar&bar=baz') + .should.eql({ foo: 'bar', bar: 'baz' }); + + qs.parse('foo=bar&baz') + .should.eql({ foo: 'bar', baz: '' }); + + qs.parse('cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World') + .should.eql({ + cht: 'p3' + , chd: 't:60,40' + , chs: '250x100' + , chl: 'Hello|World' + }); + }, + + 'test nesting': function(){ + qs.parse('ops[>=]=25') + .should.eql({ ops: { '>=': '25' }}); + + qs.parse('user[name]=tj') + .should.eql({ user: { name: 'tj' }}); + + qs.parse('user[name][first]=tj&user[name][last]=holowaychuk') + .should.eql({ user: { name: { first: 'tj', last: 'holowaychuk' }}}); + }, + + 'test escaping': function(){ + qs.parse('foo=foo%20bar') + .should.eql({ foo: 'foo bar' }); + }, + + 'test arrays': function(){ + qs.parse('images[]') + .should.eql({ images: [] }); + + qs.parse('user[]=tj') + .should.eql({ user: ['tj'] }); + + qs.parse('user[]=tj&user[]=tobi&user[]=jane') + .should.eql({ user: ['tj', 'tobi', 'jane'] }); + + qs.parse('user[names][]=tj&user[names][]=tyler') + .should.eql({ user: { names: ['tj', 'tyler'] }}); + + qs.parse('user[names][]=tj&user[names][]=tyler&user[email]=tj@vision-media.ca') + .should.eql({ user: { names: ['tj', 'tyler'], email: 'tj@vision-media.ca' }}); + + qs.parse('items=a&items=b') + .should.eql({ items: ['a', 'b'] }); + + qs.parse('user[names]=tj&user[names]=holowaychuk&user[names]=TJ') + .should.eql({ user: { names: ['tj', 'holowaychuk', 'TJ'] }}); + + qs.parse('user[name][first]=tj&user[name][first]=TJ') + .should.eql({ user: { name: { first: ['tj', 'TJ'] }}}); + }, + + 'test right-hand brackets': function(){ + qs.parse('pets=["tobi"]') + .should.eql({ pets: '["tobi"]' }); + + qs.parse('operators=[">=", "<="]') + .should.eql({ operators: '[">=", "<="]' }); + + qs.parse('op[>=]=[1,2,3]') + .should.eql({ op: { '>=': '[1,2,3]' }}); + + qs.parse('op[>=]=[1,2,3]&op[=]=[[[[1]]]]') + .should.eql({ op: { '>=': '[1,2,3]', '=': '[[[[1]]]]' }}); + }, + + 'test duplicates': function(){ + qs.parse('items=bar&items=baz&items=raz') + .should.eql({ items: ['bar', 'baz', 'raz'] }); + }, + + 'test empty': function(){ + qs.parse('').should.eql({}); + qs.parse(undefined).should.eql({}); + qs.parse(null).should.eql({}); + }, + + 'test arrays with indexes': function(){ + qs.parse('foo[0]=bar&foo[1]=baz').should.eql({ foo: ['bar', 'baz'] }); + qs.parse('foo[1]=bar&foo[0]=baz').should.eql({ foo: ['baz', 'bar'] }); + qs.parse('foo[base64]=RAWR').should.eql({ foo: { base64: 'RAWR' }}); + qs.parse('foo[64base]=RAWR').should.eql({ foo: { '64base': 'RAWR' }}); + }, + + 'test arrays becoming objects': function(){ + qs.parse('foo[0]=bar&foo[bad]=baz').should.eql({ foo: { 0: "bar", bad: "baz" }}); + qs.parse('foo[bad]=baz&foo[0]=bar').should.eql({ foo: { 0: "bar", bad: "baz" }}); + }, + + 'test bleed-through of Array native properties/methods': function(){ + Array.prototype.protoProperty = true; + Array.prototype.protoFunction = function () {}; + qs.parse('foo=bar').should.eql({ foo: 'bar' }); + }, + + 'test malformed uri': function(){ + qs.parse('{%:%}').should.eql({ '{%:%}': '' }); + qs.parse('foo=%:%}').should.eql({ 'foo': '%:%}' }); + } + + // 'test complex': function(){ + // qs.parse('users[][name][first]=tj&users[foo]=bar') + // .should.eql({ + // users: [ { name: 'tj' }, { name: 'tobi' }, { foo: 'bar' }] + // }); + // + // qs.parse('users[][name][first]=tj&users[][name][first]=tobi') + // .should.eql({ + // users: [ { name: 'tj' }, { name: 'tobi' }] + // }); + // } +}; diff --git a/node_modules/express/node_modules/qs/test/stringify.js b/node_modules/express/node_modules/qs/test/stringify.js new file mode 100644 index 0000000..5f688d8 --- /dev/null +++ b/node_modules/express/node_modules/qs/test/stringify.js @@ -0,0 +1,95 @@ + +/** + * Module dependencies. + */ + +var qs = require('../') + , should = require('should') + , query_string_identities = { + 'basics': [ + {query_string: 'foo=bar', parsed: {'foo' : 'bar'}}, + {query_string: 'foo=%22bar%22', parsed: {'foo' : '\"bar\"'}}, + {query_string: 'foo=', parsed: {'foo': ''}}, + {query_string: 'foo=1&bar=2', parsed: {'foo' : '1', 'bar' : '2'}}, + {query_string: 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F', parsed: {'my weird field': "q1!2\"'w$5&7/z8)?"}}, + {query_string: 'foo%3Dbaz=bar', parsed: {'foo=baz': 'bar'}}, + {query_string: 'foo=bar&bar=baz', parsed: {foo: 'bar', bar: 'baz'}} + ], + 'escaping': [ + {query_string: 'foo=foo%20bar', parsed: {foo: 'foo bar'}}, + {query_string: 'cht=p3&chd=t%3A60%2C40&chs=250x100&chl=Hello%7CWorld', parsed: { + cht: 'p3' + , chd: 't:60,40' + , chs: '250x100' + , chl: 'Hello|World' + }} + ], + 'nested': [ + {query_string: 'foo[]=bar&foo[]=quux', parsed: {'foo' : ['bar', 'quux']}}, + {query_string: 'foo[]=bar', parsed: {foo: ['bar']}}, + {query_string: 'foo[]=1&foo[]=2', parsed: {'foo' : ['1', '2']}}, + {query_string: 'foo=bar&baz[]=1&baz[]=2&baz[]=3', parsed: {'foo' : 'bar', 'baz' : ['1', '2', '3']}}, + {query_string: 'foo[]=bar&baz[]=1&baz[]=2&baz[]=3', parsed: {'foo' : ['bar'], 'baz' : ['1', '2', '3']}}, + {query_string: 'x[y][z]=1', parsed: {'x' : {'y' : {'z' : '1'}}}}, + {query_string: 'x[y][z][]=1', parsed: {'x' : {'y' : {'z' : ['1']}}}}, + {query_string: 'x[y][z]=2', parsed: {'x' : {'y' : {'z' : '2'}}}}, + {query_string: 'x[y][z][]=1&x[y][z][]=2', parsed: {'x' : {'y' : {'z' : ['1', '2']}}}}, + {query_string: 'x[y][][z]=1', parsed: {'x' : {'y' : [{'z' : '1'}]}}}, + {query_string: 'x[y][][z][]=1', parsed: {'x' : {'y' : [{'z' : ['1']}]}}}, + {query_string: 'x[y][][z]=1&x[y][][w]=2', parsed: {'x' : {'y' : [{'z' : '1', 'w' : '2'}]}}}, + {query_string: 'x[y][][v][w]=1', parsed: {'x' : {'y' : [{'v' : {'w' : '1'}}]}}}, + {query_string: 'x[y][][z]=1&x[y][][v][w]=2', parsed: {'x' : {'y' : [{'z' : '1', 'v' : {'w' : '2'}}]}}}, + {query_string: 'x[y][][z]=1&x[y][][z]=2', parsed: {'x' : {'y' : [{'z' : '1'}, {'z' : '2'}]}}}, + {query_string: 'x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3', parsed: {'x' : {'y' : [{'z' : '1', 'w' : 'a'}, {'z' : '2', 'w' : '3'}]}}}, + {query_string: 'user[name][first]=tj&user[name][last]=holowaychuk', parsed: { user: { name: { first: 'tj', last: 'holowaychuk' }}}} + ], + 'errors': [ + { parsed: 'foo=bar', message: 'stringify expects an object' }, + { parsed: ['foo', 'bar'], message: 'stringify expects an object' } + ] + }; + + +// Assert error +function err(fn, msg){ + var err; + try { + fn(); + } catch (e) { + should.equal(e.message, msg); + return; + } + throw new Error('no exception thrown, expected "' + msg + '"'); +} + +function test(type) { + var str, obj; + for (var i = 0; i < query_string_identities[type].length; i++) { + str = query_string_identities[type][i].query_string; + obj = query_string_identities[type][i].parsed; + qs.stringify(obj).should.eql(str); + } +} + +module.exports = { + 'test basics': function() { + test('basics'); + }, + + 'test escaping': function() { + test('escaping'); + }, + + 'test nested': function() { + test('nested'); + }, + + 'test errors': function() { + var parsed, message; + for (var i = 0; i < query_string_identities['errors'].length; i++) { + message = query_string_identities['errors'][i].message; + parsed = query_string_identities['errors'][i].parsed; + err(function(){ qs.stringify(parsed) }, message); + } + } +}; \ No newline at end of file diff --git a/node_modules/express/package.json b/node_modules/express/package.json new file mode 100644 index 0000000..7678f11 --- /dev/null +++ b/node_modules/express/package.json @@ -0,0 +1,39 @@ +{ + "name": "express", + "description": "Sinatra inspired web development framework", + "version": "2.5.2", + "author": "TJ Holowaychuk ", + "contributors": [ + { "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" }, + { "name": "Aaron Heckmann", "email": "aaron.heckmann+github@gmail.com" }, + { "name": "Ciaran Jessup", "email": "ciaranj@gmail.com" }, + { "name": "Guillermo Rauch", "email": "rauchg@gmail.com" } + ], + "dependencies": { + "connect": "1.8.x", + "mime": ">= 0.0.1", + "qs": ">= 0.3.1", + "mkdirp": "0.0.7" + }, + "devDependencies": { + "connect-form": "0.2.1", + "ejs": "0.4.2", + "expresso": "0.9.2", + "hamljs": "0.5.1", + "jade": "0.16.2", + "stylus": "0.13.0", + "should": "0.3.2", + "express-messages": "0.0.2", + "node-markdown": ">= 0.0.1", + "connect-redis": ">= 0.0.1" + }, + "keywords": ["framework", "sinatra", "web", "rest", "restful"], + "repository": "git://github.com/visionmedia/express", + "main": "index", + "bin": { "express": "./bin/express" }, + "scripts": { + "test": "make test", + "prepublish" : "npm prune" + }, + "engines": { "node": ">= 0.4.1 < 0.7.0" } +} \ No newline at end of file diff --git a/node_modules/express/testing/foo/app.js b/node_modules/express/testing/foo/app.js new file mode 100644 index 0000000..7574676 --- /dev/null +++ b/node_modules/express/testing/foo/app.js @@ -0,0 +1,35 @@ + +/** + * Module dependencies. + */ + +var express = require('express') + , routes = require('./routes') + +var app = module.exports = express.createServer(); + +// Configuration + +app.configure(function(){ + app.set('views', __dirname + '/views'); + app.set('view engine', 'jade'); + app.use(express.bodyParser()); + app.use(express.methodOverride()); + app.use(app.router); + app.use(express.static(__dirname + '/public')); +}); + +app.configure('development', function(){ + app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); +}); + +app.configure('production', function(){ + app.use(express.errorHandler()); +}); + +// Routes + +app.get('/', routes.index); + +app.listen(3000); +console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env); diff --git a/node_modules/express/testing/foo/package.json b/node_modules/express/testing/foo/package.json new file mode 100644 index 0000000..dd54123 --- /dev/null +++ b/node_modules/express/testing/foo/package.json @@ -0,0 +1,9 @@ +{ + "name": "application-name" + , "version": "0.0.1" + , "private": true + , "dependencies": { + "express": "2.5.0" + , "jade": ">= 0.0.1" + } +} \ No newline at end of file diff --git a/node_modules/express/testing/foo/public/stylesheets/style.css b/node_modules/express/testing/foo/public/stylesheets/style.css new file mode 100644 index 0000000..30e047d --- /dev/null +++ b/node_modules/express/testing/foo/public/stylesheets/style.css @@ -0,0 +1,8 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} \ No newline at end of file diff --git a/node_modules/express/testing/foo/routes/index.js b/node_modules/express/testing/foo/routes/index.js new file mode 100644 index 0000000..0b2205c --- /dev/null +++ b/node_modules/express/testing/foo/routes/index.js @@ -0,0 +1,10 @@ + +/* + * GET home page. + */ + +exports.index = function(req, res){ + res.writeHead(200); + req.doesnotexist(); + // res.render('index', { title: 'Express' }) +}; \ No newline at end of file diff --git a/node_modules/express/testing/foo/views/index.jade b/node_modules/express/testing/foo/views/index.jade new file mode 100644 index 0000000..c9c35fa --- /dev/null +++ b/node_modules/express/testing/foo/views/index.jade @@ -0,0 +1,2 @@ +h1= title +p Welcome to #{title} \ No newline at end of file diff --git a/node_modules/express/testing/foo/views/layout.jade b/node_modules/express/testing/foo/views/layout.jade new file mode 100644 index 0000000..1a36941 --- /dev/null +++ b/node_modules/express/testing/foo/views/layout.jade @@ -0,0 +1,6 @@ +!!! +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body!= body \ No newline at end of file diff --git a/node_modules/express/testing/index.js b/node_modules/express/testing/index.js new file mode 100644 index 0000000..3c5185d --- /dev/null +++ b/node_modules/express/testing/index.js @@ -0,0 +1,43 @@ + +/** + * Module dependencies. + */ + +var express = require('../') + , http = require('http') + , connect = require('connect'); + +var app = express.createServer(); + +app.get('/', function(req, res){ + req.foo(); + res.send('test'); +}); + +// app.set('views', __dirname + '/views'); +// app.set('view engine', 'jade'); +// +// app.configure(function(){ +// app.use(function(req, res, next){ +// debugger +// res.write('first'); +// console.error('first'); +// next(); +// }); +// +// app.use(app.router); +// +// app.use(function(req, res, next){ +// console.error('last'); +// res.end('last'); +// }); +// }); +// +// app.get('/', function(req, res, next){ +// console.error('middle'); +// res.write(' route '); +// next(); +// }); + +app.listen(3000); +console.log('listening on port 3000'); \ No newline at end of file diff --git a/node_modules/express/testing/public/test.txt b/node_modules/express/testing/public/test.txt new file mode 100644 index 0000000..cb9a165 --- /dev/null +++ b/node_modules/express/testing/public/test.txt @@ -0,0 +1,2971 @@ +foo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +bazfoo +bar +baz \ No newline at end of file diff --git a/node_modules/express/testing/views/page.html b/node_modules/express/testing/views/page.html new file mode 100644 index 0000000..4ff9827 --- /dev/null +++ b/node_modules/express/testing/views/page.html @@ -0,0 +1 @@ +p register test \ No newline at end of file diff --git a/node_modules/express/testing/views/page.jade b/node_modules/express/testing/views/page.jade new file mode 100644 index 0000000..9c3f888 --- /dev/null +++ b/node_modules/express/testing/views/page.jade @@ -0,0 +1,3 @@ +html + body + h1 test \ No newline at end of file diff --git a/node_modules/express/testing/views/test.md b/node_modules/express/testing/views/test.md new file mode 100644 index 0000000..9139ff4 --- /dev/null +++ b/node_modules/express/testing/views/test.md @@ -0,0 +1 @@ +testing _some_ markdown \ No newline at end of file diff --git a/node_modules/express/testing/views/user/index.jade b/node_modules/express/testing/views/user/index.jade new file mode 100644 index 0000000..1b66a4f --- /dev/null +++ b/node_modules/express/testing/views/user/index.jade @@ -0,0 +1 @@ +p user page \ No newline at end of file diff --git a/node_modules/express/testing/views/user/list.jade b/node_modules/express/testing/views/user/list.jade new file mode 100644 index 0000000..ed2b471 --- /dev/null +++ b/node_modules/express/testing/views/user/list.jade @@ -0,0 +1 @@ +p user list page \ No newline at end of file diff --git a/node_modules/jade/.gitmodules b/node_modules/jade/.gitmodules new file mode 100644 index 0000000..b5b4321 --- /dev/null +++ b/node_modules/jade/.gitmodules @@ -0,0 +1,21 @@ +[submodule "support/expresso"] + path = support/expresso + url = git://github.com/visionmedia/expresso.git +[submodule "support/sass"] + path = support/sass + url = git://github.com/visionmedia/sass.js.git +[submodule "benchmarks/haml-js"] + path = benchmarks/haml-js + url = git://github.com/creationix/haml-js.git +[submodule "benchmarks/ejs"] + path = benchmarks/ejs + url = git://github.com/visionmedia/ejs.git +[submodule "benchmarks/haml"] + path = benchmarks/haml + url = git://github.com/visionmedia/haml.js.git +[submodule "support/coffee-script"] + path = support/coffee-script + url = http://github.com/jashkenas/coffee-script.git +[submodule "support/stylus"] + path = support/stylus + url = git://github.com/LearnBoost/stylus.git diff --git a/node_modules/jade/.npmignore b/node_modules/jade/.npmignore new file mode 100644 index 0000000..10fd0d4 --- /dev/null +++ b/node_modules/jade/.npmignore @@ -0,0 +1,4 @@ +test +support +benchmarks +examples diff --git a/node_modules/jade/.travis.yml b/node_modules/jade/.travis.yml new file mode 100644 index 0000000..381c985 --- /dev/null +++ b/node_modules/jade/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.4 + - 0.6 \ No newline at end of file diff --git a/node_modules/jade/History.md b/node_modules/jade/History.md new file mode 100644 index 0000000..34976e6 --- /dev/null +++ b/node_modules/jade/History.md @@ -0,0 +1,506 @@ + +0.20.0 / 2011-12-28 +================== + + * Added a browser example + * Added `yield` for block `include`s + * Changed: replaced internal `__` var with `__jade` [chrisleishman] + * Fixed two globals. Closes #433 + +0.19.0 / 2011-12-02 +================== + + * Added block `append` / `prepend` support. Closes #355 + * Added link in readme to jade-mode for Emacs + * Added link to python implementation + +0.18.0 / 2011-11-21 +================== + + * Changed: only ['script', 'style'] are text-only. Closes #398' + +0.17.0 / 2011-11-10 +================== + + * jade.renderFile() is back! (for express 3.x) + * Fixed `Object.keys()` failover bug + +0.16.4 / 2011-10-24 +================== + + * Fixed a test due to reserved keyword + * Fixed: commander 0.1.x dep for 0.5.x + +0.16.3 / 2011-10-24 +================== + + * Added: allow leading space for conditional comments + * Added quick implementation of a switch statement + * Fixed parens in mixin args. Closes #380 + * Fixed: include files with a .jade extension as jade files + +0.16.2 / 2011-09-30 +================== + + * Fixed include regression. Closes #354 + +0.16.1 / 2011-09-29 +================== + + * Fixed unexpected `else` bug when compileDebug: false + * Fixed attr state issue for balancing pairs. Closes #353 + +0.16.0 / 2011-09-26 +================== + + * Added `include` block support. Closes #303 + * Added template inheritance via `block` and `extends`. Closes #242 + * Added 'type="text/css"' to the style tags generated by filters. + * Added 'uglifyjs' as an explicit devDependency. + * Added -p, --path flag to jade(1) + * Added support for any arbitrary doctype + * Added `jade.render(str[,options], fn)` back + * Added first-class `while` support + * Added first-class assignment support + * Fixed runtime.js `Array.isArray()` polyfill. Closes #345 + * Fixed: set .filename option in jade(1) when passing filenames + * Fixed `Object.keys()` polyfill typo. Closes #331 + * Fixed `include` error context + * Renamed magic "index" to "$index". Closes #350 + +0.15.4 / 2011-09-05 +================== + + * Fixed script template html. Closes #316 + * Revert "Fixed script() tag with trailing ".". Closes #314" + +0.15.3 / 2011-08-30 +================== + + * Added Makefile example. Closes #312 + * Fixed script() tag with trailing ".". Closes #314 + +0.15.2 / 2011-08-26 +================== + + * Fixed new conditional boundaries. Closes #307 + +0.15.1 / 2011-08-26 +================== + + * Fixed jade(1) support due to `res.render()` removal + * Removed --watch support (use a makefile + watch...) + +0.15.0 / 2011-08-26 +================== + + * Added `client` option to reference runtime helpers + * Added `Array.isArray()` for runtime.js as well + * Added `Object.keys()` for the client-side runtime + * Added first-class `if`, `unless`, `else` and `else if` support + * Added first-class `each` / `for` support + * Added `make benchmark` for continuous-bench + * Removed `inline` option, SS helpers are no longer inlined either + * Removed `Parser#debug()` + * Removed `jade.render()` and `jade.renderFile()` + * Fixed runtime.js `escape()` bug causing window.escape to be used + * Fixed a bunch of tests + +0.14.2 / 2011-08-16 +================== + + * Added `include` support for non-jade files + * Fixed code indentation when followed by newline(s). Closes #295 [reported by masylum] + +0.14.1 / 2011-08-14 +================== + + * Added `colons` option for everyone stuck with ":". Closes #231 + * Optimization: consecutive lines are merged in compiled js + +0.14.0 / 2011-08-08 +================== + + * Added array iteration with index example. Closes #276 + * Added _runtime.js_ + * Added `compileDebug` option to enable lineno instrumentation + * Added `inline` option to disable inlining of helpers (for client-side) + +0.13.0 / 2011-07-13 +================== + + * Added `mixin` support + * Added `include` support + * Added array support for the class attribute + +0.12.4 / 2011-06-23 +================== + + * Fixed filter indentation bug. Closes #243 + +0.12.3 / 2011-06-21 +================== + + * Fixed empty strings support. Closes #223 + * Fixed conditional comments documentation. Closes #245 + +0.12.2 / 2011-06-16 +================== + + * Fixed `make test` + * Fixed block comments + +0.12.1 / 2011-06-04 +================== + + * Fixed attribute interpolation with double quotes. Fixes #232 [topaxi] + +0.12.0 / 2011-06-03 +================== + + * Added `doctype` as alias of `!!!` + * Added; doctype value is now case-insensitive + * Added attribute interpolation support + * Fixed; retain original indentation spaces in text blocks + +0.11.1 / 2011-06-01 +================== + + * Fixed text block indentation [Laszlo Bacsi] + * Changed; utilizing devDependencies + * Fixed try/catch issue with renderFile(). Closes #227 + * Removed attribute ":" support, use "=" (option for ':' coming soon) + +0.11.0 / 2011-05-14 +================== + + * Added `self` object to avoid poor `with(){}` performance [masylum] + * Added `doctype` option [Jeremy Larkin] + +0.10.7 / 2011-05-04 +================== + + * expose Parser + +0.10.6 / 2011-04-29 +================== + + * Fixed CS `Object.keys()` [reported by robholland] + +0.10.5 / 2011-04-26 +================== + + * Added error context after the lineno + * Added; indicate failing lineno with ">" + * Added `Object.keys()` for the client-side + * Fixed attr strings when containing the opposite quote. Closes 207 + * Fixed attr issue with js expressions within strings + * Fixed single-quote filter escape bug. Closes #196 + + +0.10.4 / 2011-04-05 +================== + + * Added `html` doctype, same as "5" + * Fixed `pre`, no longer text-only + +0.10.3 / 2011-03-30 +================== + + * Fixed support for quoted attribute keys ex `rss("xmlns:atom"="atom")` + +0.10.2 / 2011-03-30 +================== + + * Fixed pipeless text bug with missing outdent + +0.10.1 / 2011-03-28 +================== + + * Fixed `support/compile.js` to exclude browser js in node + * Fixes for IE [Patrick Pfeiffer] + +0.10.0 / 2011-03-25 +================== + + * Added AST-filter support back in the form of `[attrs]<:>` + +0.9.3 / 2011-03-24 +================== + + * Added `Block#unshift(node)` + * Added `jade.js` for the client-side to the repo + * Added `jade.min.js` for the client-side to the repo + * Removed need for pipes in filters. Closes #185 + Note that this _will_ break filters used to + manipulate the AST, until we have a different + syntax for doing so. + +0.9.2 / 2011-03-23 +================== + + * Added jade `--version` + * Removed `${}` interpolation support, use `#{}` + +0.9.1 / 2011-03-16 +================== + + * Fixed invalid `.map()` call due to recent changes + +0.9.0 / 2011-03-16 +================== + + * Added client-side browser support via `make jade.js` and `make jade.min.js`. + +0.8.9 / 2011-03-15 +================== + + * Fixed preservation of newlines in text blocks + +0.8.8 / 2011-03-14 +================== + + * Fixed jade(1) stdio + +0.8.7 / 2011-03-14 +================== + + * Added `mkdirs()` to jade(1) + * Added jade(1) stdio support + * Added new features to jade(1), `--watch`, recursive compilation etc [khingebjerg] + * Fixed pipe-less text newlines + * Removed jade(1) `--pipe` flag + +0.8.6 / 2011-03-11 +================== + + * Fixed parenthesized expressions in attrs. Closes #170 + * Changed; default interpolation values `== null` to ''. Closes #167 + +0.8.5 / 2011-03-09 +================== + + * Added pipe-less text support with immediate ".". Closes #157 + * Fixed object support in attrs + * Fixed array support for attrs + +0.8.4 / 2011-03-08 +================== + + * Fixed issue with expressions being evaluated several times. closes #162 + +0.8.2 / 2011-03-07 +================== + + * Added markdown, discount, and markdown-js support to `:markdown`. Closes #160 + * Removed `:discount` + +0.8.1 / 2011-03-04 +================== + + * Added `pre` pipe-less text support (and auto-escaping) + +0.8.0 / 2011-03-04 +================== + + * Added block-expansion support. Closes #74 + * Added support for multi-line attrs without commas. Closes #65 + +0.7.1 / 2011-03-04 +================== + + * Fixed `script()` etc pipe-less text with attrs + +0.7.0 / 2011-03-04 +================== + + * Removed `:javascript` filter (it doesn't really do anything special, use `script` tags) + * Added pipe-less text support. Tags that only accept text nodes (`script`, `textarea`, etc) do not require `|`. + * Added `:text` filter for ad-hoc pipe-less + * Added flexible indentation. Tabs, arbitrary number of spaces etc + * Added conditional-comment support. Closes #146 + * Added block comment support + * Added rss example + * Added `:stylus` filter + * Added `:discount` filter + * Fixed; auto-detect xml and do not self-close tags. Closes #147 + * Fixed whitespace issue. Closes #118 + * Fixed attrs. `,`, `=`, and `:` within attr value strings are valid Closes #133 + * Fixed; only output "" when code == null. Ex: `span.name= user.name` when undefined or null will not output "undefined". Closes #130 + * Fixed; throw on unexpected token instead of hanging + +0.6.3 / 2011-02-02 +================== + + * Added `each` support for Array-like objects [guillermo] + +0.6.2 / 2011-02-02 +================== + + * Added CSRF example, showing how you can transparently add inputs to a form + * Added link to vim-jade + * Fixed self-closing col support [guillermo] + * Fixed exception when getAttribute or removeAttribute run into removed attributes [Naitik Shah] + +0.6.0 / 2010-12-19 +================== + + * Added unescaped interpolation variant `!{code}`. Closes #124 + * Changed; escape interpolated code by default `#{code}` + +0.5.7 / 2010-12-08 +================== + + * Fixed; hyphen in get `tag()` + +0.5.6 / 2010-11-24 +================== + + * Added `exports.compile(str, options)` + * Renamed internal `_` to `__`, since `_()` is commonly used for translation + +0.5.5 / 2010-10-30 +================== + + * Add _coffeescript_ filter [Michael Hampton] + * Added link to _slim_; a ruby implementation + * Fixed quoted attributes issue. + + * Fixed attribute issue with over greedy regexp. + Previously "p(foo=(((('bar')))))= ((('baz')))" + would __fail__ for example since the regexp + would lookahead to far. Now we simply pair + the delimiters. + +0.5.4 / 2010-10-18 +================== + + * Adding newline when using tag code when preceding text + * Assume newline in tag text when preceding text + * Changed; retain leading text whitespace + * Fixed code block support to prevent multiple buffer openings [Jake Luer] + * Fixed nested filter support + +0.5.3 / 2010-10-06 +================== + + * Fixed bug when tags with code also have a block [reported by chrisirhc] + +0.5.2 / 2010-10-05 +================== + + * Added; Text introduces newlines to mimic the grammar. + Whitespace handling is a little tricky with this sort of grammar. + Jade will now mimic the written grammar, meaning that text blocks + using the "|" margin character will introduce a literal newline, + where as immediate tag text (ex "a(href='#') Link") will not. + + This may not be ideal, but it makes more sense than what Jade was + previously doing. + + * Added `Tag#text` to disambiguate between immediate / block text + * Removed _pretty_ option (was kinda useless in the state it was in) + * Reverted ignoring of newlines. Closes #92. + * Fixed; `Parser#parse()` ignoring newlines + +0.5.1 / 2010-10-04 +================== + + * Added many examples + * Added; compiler api is now public + * Added; filters can accept / manipulate the parse tree + * Added filter attribute support. Closes #79 + * Added LL(*) capabilities + * Performance; wrapping code blocks in {} instead of `(function(){}).call(this)` + * Performance; Optimized attribute buffering + * Fixed trailing newlines in blocks + +0.5.0 / 2010-09-11 +================== + + * __Major__ refactor. Logic now separated into lexer/parser/compiler for future extensibility. + * Added _pretty_ option + * Added parse tree output for _debug_ option + * Added new examples + * Removed _context_ option, use _scope_ + +0.4.1 / 2010-09-09 +================== + + * Added support for arbitrary indentation for single-line comments. Closes #71 + * Only strip first space in text (ex '| foo' will buffer ' foo') + +0.4.0 / 2010-08-30 +================== + + * Added tab naive support (tabs are converted to a single indent, aka two spaces). Closes #24 + * Added unbuffered comment support. Closes #62 + * Added hyphen support for tag names, ex: "fb:foo-bar" + * Fixed bug with single quotes in comments. Closes #61 + * Fixed comment whitespace issue, previously padding. Closes #55 + +0.3.0 / 2010-08-04 +================== + + * Added single line comment support. Closes #25 + * Removed CDATA from _:javascript_ filter. Closes #47 + * Removed _sys_ local + * Fixed code following tag + +0.2.4 / 2010-08-02 +================== + + * Added Buffer support to `render()` + * Fixed filter text block exception reporting + * Fixed tag exception reporting + +0.2.3 / 2010-07-27 +================== + + * Fixed newlines before block + * Fixed; tag text allowing arbitrary trailing whitespace + +0.2.2 / 2010-07-16 +================== + + * Added support for `jade.renderFile()` to utilize primed cache + * Added link to [textmate bundle](http://github.com/miksago/jade-tmbundle) + * Fixed filter issue with single quotes + * Fixed hyphenated attr bug + * Fixed interpolation single quotes. Closes #28 + * Fixed issue with comma in attrs + +0.2.1 / 2010-07-09 +================== + + * Added support for node-discount and markdown-js + depending on which is available. + + * Added support for tags to have blocks _and_ text. + this kinda fucks with arbitrary whitespace unfortunately, + but also fixes trailing spaces after tags _with_ blocks. + + * Caching generated functions. Closes #46 + +0.2.0 / 2010-07-08 +================== + + * Added `- each` support for readable iteration + * Added [markdown-js](http://github.com/evilstreak/markdown-js) support (no compilation required) + * Removed node-discount support + +0.1.0 / 2010-07-05 +================== + + * Added `${}` support for interpolation. Closes #45 + * Added support for quoted attr keys: `label("for": 'something')` is allowed (_although not required_) [Guillermo] + * Added `:less` filter [jakeluer] + +0.0.2 / 2010-07-03 +================== + + * Added `context` as synonym for `scope` option [Guillermo] + * Fixed attr splitting: `div(style:"color: red")` is now allowed + * Fixed issue with `(` and `)` within attrs: `a(class: (a ? 'a' : 'b'))` is now allowed + * Fixed issue with leading / trailing spaces in attrs: `a( href="#" )` is now allowed [Guillermo] + diff --git a/node_modules/jade/LICENSE b/node_modules/jade/LICENSE new file mode 100644 index 0000000..8ad0e0d --- /dev/null +++ b/node_modules/jade/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2009-2010 TJ Holowaychuk + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/jade/Makefile b/node_modules/jade/Makefile new file mode 100644 index 0000000..6ef5edb --- /dev/null +++ b/node_modules/jade/Makefile @@ -0,0 +1,40 @@ + +TESTS = test/*.js +SRC = $(shell find lib -name "*.js" -type f) +UGLIFY = $(shell find node_modules -name "uglifyjs" -type f) +UGLIFY_FLAGS = --no-mangle + +all: jade.min.js runtime.min.js + +test: + @./node_modules/.bin/mocha \ + --ui exports \ + --globals name \ + $(TESTS) + +benchmark: + @node support/benchmark + +jade.js: $(SRC) + @node support/compile.js $^ + +jade.min.js: jade.js + @$(UGLIFY) $(UGLIFY_FLAGS) $< > $@ \ + && du jade.min.js \ + && du jade.js + +runtime.js: lib/runtime.js + @cat support/head.js $< support/foot.js > $@ + +runtime.min.js: runtime.js + @$(UGLIFY) $(UGLIFY_FLAGS) $< > $@ \ + && du runtime.min.js \ + && du runtime.js + +clean: + rm -f jade.js + rm -f jade.min.js + rm -f runtime.js + rm -f runtime.min.js + +.PHONY: test benchmark clean diff --git a/node_modules/jade/Readme.md b/node_modules/jade/Readme.md new file mode 100644 index 0000000..c9dc782 --- /dev/null +++ b/node_modules/jade/Readme.md @@ -0,0 +1,1072 @@ + [![Build Status](https://secure.travis-ci.org/visionmedia/jade.png)](http://travis-ci.org/visionmedia/jade) + +# Jade - template engine + + Jade is a high performance template engine heavily influenced by [Haml](http://haml-lang.com) + and implemented with JavaScript for [node](http://nodejs.org). + +## Features + + - client-side support + - great readability + - flexible indentation + - block-expansion + - mixins + - static includes + - attribute interpolation + - code is escaped by default for security + - contextual error reporting at compile & run time + - executable for compiling jade templates via the command line + - html 5 mode (using the _!!! 5_ doctype) + - optional memory caching + - combine dynamic and static tag classes + - parse tree manipulation via _filters_ + - template inheritance + - block append / prepend + - supports [Express JS](http://expressjs.com) out of the box + - transparent iteration over objects, arrays, and even non-enumerables via `each` + - block comments + - no tag prefix + - AST filters + - filters + - :stylus must have [stylus](http://github.com/LearnBoost/stylus) installed + - :sass must have [sass.js](http://github.com/visionmedia/sass.js) installed + - :less must have [less.js](http://github.com/cloudhead/less.js) installed + - :markdown must have [markdown-js](http://github.com/evilstreak/markdown-js) installed or [node-discount](http://github.com/visionmedia/node-discount) + - :cdata + - :coffeescript must have [coffee-script](http://jashkenas.github.com/coffee-script/) installed + - [Emacs Mode](https://github.com/brianc/jade-mode) + - [Vim Syntax](https://github.com/digitaltoad/vim-jade) + - [TextMate Bundle](http://github.com/miksago/jade-tmbundle) + - [Screencasts](http://tjholowaychuk.com/post/1004255394/jade-screencast-template-engine-for-nodejs) + - [html2jade](https://github.com/donpark/html2jade) converter + +## Implementations + + - [php](http://github.com/everzet/jade.php) + - [scala](http://scalate.fusesource.org/versions/snapshot/documentation/scaml-reference.html) + - [ruby](http://github.com/stonean/slim) + - [python](https://github.com/SyrusAkbary/pyjade) + +## Installation + +via npm: + + npm install jade + +## Browser Support + + To compile jade to a single file compatible for client-side use simply execute: + + $ make jade.js + + Alternatively, if uglifyjs is installed via npm (`npm install uglify-js`) you may execute the following which will create both files. However each release builds these for you. + + $ make jade.min.js + + By default Jade instruments templates with line number statements such as `__.lineno = 3` for debugging purposes. When used in a browser it's useful to minimize this boiler plate, you can do so by passing the option `{ compileDebug: false }`. The following template + + p Hello #{name} + + Can then be as small as the following generated function: + +```js +function anonymous(locals, attrs, escape, rethrow) { + var buf = []; + with (locals || {}) { + var interp; + buf.push('\n

    Hello ' + escape((interp = name) == null ? '' : interp) + '\n

    '); + } + return buf.join(""); +} +``` + + Through the use of Jade's `./runtime.js` you may utilize these pre-compiled templates on the client-side _without_ Jade itself, all you need is the associated utility functions (in runtime.js), which are then available as `jade.attrs`, `jade.escape` etc. To enable this you should pass `{ client: true }` to `jade.compile()` to tell Jade to reference the helper functions + via `jade.attrs`, `jade.escape` etc. + +```js +function anonymous(locals, attrs, escape, rethrow) { + var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow; + var buf = []; + with (locals || {}) { + var interp; + buf.push('\n

    Hello ' + escape((interp = name) == null ? '' : interp) + '\n

    '); + } + return buf.join(""); +} +``` + +## Public API + +```javascript + var jade = require('jade'); + + // Compile a function + var fn = jade.compile('string of jade', options); + fn(locals); +``` + +### Options + + - `self` Use a `self` namespace to hold the locals. _false by default_ + - `locals` Local variable object + - `filename` Used in exceptions, and required when using includes + - `debug` Outputs tokens and function body generated + - `compiler` Compiler to replace jade's default + - `compileDebug` When `false` no debug instrumentation is compiled + +## Syntax + +### Line Endings + +**CRLF** and **CR** are converted to **LF** before parsing. + +### Tags + +A tag is simply a leading word: + + html + +for example is converted to `` + +tags can also have ids: + + div#container + +which would render `
    ` + +how about some classes? + + div.user-details + +renders `
    ` + +multiple classes? _and_ an id? sure: + + div#foo.bar.baz + +renders `
    ` + +div div div sure is annoying, how about: + + #foo + .bar + +which is syntactic sugar for what we have already been doing, and outputs: + + `
    ` + +### Tag Text + +Simply place some content after the tag: + + p wahoo! + +renders `

    wahoo!

    `. + +well cool, but how about large bodies of text: + + p + | foo bar baz + | rawr rawr + | super cool + | go jade go + +renders `

    foo bar baz rawr.....

    ` + +interpolation? yup! both types of text can utilize interpolation, +if we passed `{ name: 'tj', email: 'tj@vision-media.ca' }` to the compiled function we can do the following: + + #user #{name} <#{email}> + +outputs `
    tj <tj@vision-media.ca>
    ` + +Actually want `#{}` for some reason? escape it! + + p \#{something} + +now we have `

    #{something}

    ` + +We can also utilize the unescaped variant `!{html}`, so the following +will result in a literal script tag: + + - var html = "" + | !{html} + +Nested tags that also contain text can optionally use a text block: + + label + | Username: + input(name='user[name]') + +or immediate tag text: + + label Username: + input(name='user[name]') + +Tags that accept _only_ text such as `script` and `style` do not +need the leading `|` character, for example: + + html + head + title Example + script + if (foo) { + bar(); + } else { + baz(); + } + +Once again as an alternative, we may use a trailing '.' to indicate a text block, for example: + + p. + foo asdf + asdf + asdfasdfaf + asdf + asd. + +outputs: + +

    foo asdf + asdf + asdfasdfaf + asdf + asd. +

    + +This however differs from a trailing '.' followed by a space, which although is ignored by the Jade parser, tells Jade that this period is a literal: + + p . + +outputs: + +

    .

    + + +It should be noted that text blocks should be doubled escaped. For example if you desire the following output. + +

    foo\bar

    + +use: + + p. + foo\\bar + +### Comments + +Single line comments currently look the same as JavaScript comments, +aka "//" and must be placed on their own line: + + // just some paragraphs + p foo + p bar + +would output + + +

    foo

    +

    bar

    + +Jade also supports unbuffered comments, by simply adding a hyphen: + + //- will not output within markup + p foo + p bar + +outputting + +

    foo

    +

    bar

    + +### Block Comments + + A block comment is legal as well: + + body + // + #content + h1 Example + +outputting + + + + + +Jade supports conditional-comments as well, for example: + + head + //if lt IE 8 + script(src='/ie-sucks.js') + +outputs: + + + + + + +### Nesting + + Jade supports nesting to define the tags in a natural way: + + ul + li.first + a(href='#') foo + li + a(href='#') bar + li.last + a(href='#') baz + +### Block Expansion + + Block expansion allows you to create terse single-line nested tags, + the following example is equivalent to the nesting example above. + + ul + li.first: a(href='#') foo + li: a(href='#') bar + li.last: a(href='#') baz + +### Case + + The case statement takes the following form: + + html + body + friends = 10 + case friends + when 0 + p you have no friends + when 1 + p you have a friend + default + p you have #{friends} friends + + Block expansion may also be used: + + friends = 5 + + html + body + case friends + when 0: p you have no friends + when 1: p you have a friend + default: p you have #{friends} friends + +### Attributes + +Jade currently supports '(' and ')' as attribute delimiters. + + a(href='/login', title='View login page') Login + +When a value is `undefined` or `null` the attribute is _not_ added, +so this is fine, it will not compile 'something="null"'. + + div(something=null) + +Boolean attributes are also supported: + + input(type="checkbox", checked) + +Boolean attributes with code will only output the attribute when `true`: + + input(type="checkbox", checked=someValue) + +Multiple lines work too: + + input(type='checkbox', + name='agreement', + checked) + +Multiple lines without the comma work fine: + + input(type='checkbox' + name='agreement' + checked) + +Funky whitespace? fine: + + + input( + type='checkbox' + name='agreement' + checked) + +Colons work: + + rss(xmlns:atom="atom") + +Suppose we have the `user` local `{ id: 12, name: 'tobi' }` +and we wish to create an anchor tag with `href` pointing to "/user/12" +we could use regular javascript concatenation: + + a(href='/user/' + user.id)= user.name + +or we could use jade's interpolation, which I added because everyone +using Ruby or CoffeeScript seems to think this is legal js..: + + a(href='/user/#{user.id}')= user.name + +The `class` attribute is special-cased when an array is given, +allowing you to pass an array such as `bodyClasses = ['user', 'authenticated']` directly: + + body(class=bodyClasses) + +### HTML + + Inline html is fine, we can use the pipe syntax to + write arbitrary text, in this case some html: + +``` +html + body + |

    Title

    + |

    foo bar baz

    +``` + + Or we can use the trailing `.` to indicate to Jade that we + only want text in this block, allowing us to omit the pipes: + +``` +html + body. +

    Title

    +

    foo bar baz

    +``` + + Both of these examples yield the same result: + +``` +

    Title

    +

    foo bar baz

    + +``` + + The same rule applies for anywhere you can have text + in jade, raw html is fine: + +``` +html + body + h1 User #{name} +``` + +### Doctypes + +To add a doctype simply use `!!!`, or `doctype` followed by an optional value: + + !!! + +Will output the _transitional_ doctype, however: + + !!! 5 + +or + + !!! html + +or + + doctype html + +doctypes are case-insensitive, so the following are equivalent: + + doctype Basic + doctype basic + +it's also possible to simply pass a doctype literal: + + doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN + +yielding: + + + +Will output the _html 5_ doctype. Below are the doctypes +defined by default, which can easily be extended: + +```javascript + var doctypes = exports.doctypes = { + '5': '', + 'xml': '', + 'default': '', + 'transitional': '', + 'strict': '', + 'frameset': '', + '1.1': '', + 'basic': '', + 'mobile': '' + }; +``` + +To alter the default simply change: + +```javascript + jade.doctypes.default = 'whatever you want'; +``` + +## Filters + +Filters are prefixed with `:`, for example `:markdown` and +pass the following block of text to an arbitrary function for processing. View the _features_ +at the top of this document for available filters. + + body + :markdown + Woah! jade _and_ markdown, very **cool** + we can even link to [stuff](http://google.com) + +Renders: + +

    Woah! jade and markdown, very cool we can even link to stuff

    + +## Code + +Jade currently supports three classifications of executable code. The first +is prefixed by `-`, and is not buffered: + + - var foo = 'bar'; + +This can be used for conditionals, or iteration: + + - for (var key in obj) + p= obj[key] + +Due to Jade's buffering techniques the following is valid as well: + + - if (foo) + ul + li yay + li foo + li worked + - else + p oh no! didnt work + +Hell, even verbose iteration: + + - if (items.length) + ul + - items.forEach(function(item){ + li= item + - }) + +Anything you want! + +Next up we have _escaped_ buffered code, which is used to +buffer a return value, which is prefixed by `=`: + + - var foo = 'bar' + = foo + h1= foo + +Which outputs `bar

    bar

    `. Code buffered by `=` is escaped +by default for security, however to output unescaped return values +you may use `!=`: + + p!= aVarContainingMoreHTML + + Jade also has designer-friendly variants, making the literal JavaScript + more expressive and declarative. For example the following assignments + are equivalent, and the expression is still regular javascript: + + - var foo = 'foo ' + 'bar' + foo = 'foo ' + 'bar' + + Likewise Jade has first-class `if`, `else if`, `else`, `until`, `while`, `unless` among others, however you must remember that the expressions are still regular javascript: + + if foo == 'bar' + ul + li yay + li foo + li worked + else + p oh no! didnt work + +## Iteration + + Along with vanilla JavaScript Jade also supports a subset of + constructs that allow you to create more designer-friendly templates, + one of these constructs is `each`, taking the form: + + each VAL[, KEY] in OBJ + +An example iterating over an array: + + - var items = ["one", "two", "three"] + each item in items + li= item + +outputs: + +
  • one
  • +
  • two
  • +
  • three
  • + +iterating an array with index: + + items = ["one", "two", "three"] + each item, i in items + li #{item}: #{i} + +outputs: + +
  • one: 0
  • +
  • two: 1
  • +
  • three: 2
  • + +iterating an object's keys and values: + + obj = { foo: 'bar' } + each val, key in obj + li #{key}: #{val} + +would output `
  • foo: bar
  • ` + +Internally Jade converts these statements to regular +JavaScript loops such as `users.forEach(function(user){`, +so lexical scope and nesting applies as it would with regular +JavaScript: + + each user in users + each role in user.roles + li= role + + You may also use `for` if you prefer: + + for user in users + for role in user.roles + li= role + +## Conditionals + + Jade conditionals are equivalent to those using the code (`-`) prefix, + however allow you to ditch parenthesis to become more designer friendly, + however keep in mind the expression given is _regular_ JavaScript: + + for user in users + if user.role == 'admin' + p #{user.name} is an admin + else + p= user.name + + is equivalent to the following using vanilla JavaScript literals: + + for user in users + - if (user.role == 'admin') + p #{user.name} is an admin + - else + p= user.name + + Jade also provides have `unless` which is equivalent to `if (!(expr))`: + + for user in users + unless user.isAnonymous + p + | Click to view + a(href='/users/' + user.id)= user.name + +## Template inheritance + + Jade supports template inheritance via the `block` and `extends` keywords. A block is simply a "block" of Jade that may be replaced within a child template, this process is recursive. To activate template inheritance in Express 2.x you must add: `app.set('view options', { layout: false });`. + + Jade blocks can provide default content if desired, however optional as shown below by `block scripts`, `block content`, and `block foot`. + +``` +html + head + h1 My Site - #{title} + block scripts + script(src='/jquery.js') + body + block content + block foot + #footer + p some footer content +``` + + Now to extend the layout, simply create a new file and use the `extends` directive as shown below, giving the path (with or without the .jade extension). You may now define one or more blocks that will override the parent block content, note that here the `foot` block is _not_ redefined and will output "some footer content". + +``` +extends layout + +block scripts + script(src='/jquery.js') + script(src='/pets.js') + +block content + h1= title + each pet in pets + include pet +``` + + It's also possible to override a block to provide additional blocks, as shown in the following example where `content` now exposes a `sidebar` and `primary` block for overriding, or the child template could override `content` all together. + +``` +extends regular-layout + +block content + .sidebar + block sidebar + p nothing + .primary + block primary + p nothing +``` + +## Block append / prepend + + Jade allows you to _replace_ (default), _prepend_, or _append_ blocks. Suppose for example you have default scripts in a "head" block that you wish to utilize on _every_ page, you might do this: + +``` +html + head + block head + script(src='/vendor/jquery.js') + script(src='/vendor/caustic.js') + body + block content +``` + + Now suppose you have a page of your application for a JavaScript game, you want some game related scripts as well as these defaults, you can simply `append` the block: + +``` +include layout + +block append head + script(src='/vendor/three.js') + script(src='/game.js') +``` + + When using `block append` or `block prepend` the `block` is optional: + +``` +include layout + +append head + script(src='/vendor/three.js') + script(src='/game.js') +``` + +## Includes + + Includes allow you to statically include chunks of Jade, + or other content like css, or html which lives in separate files. The classical example is including a header and footer. Suppose we have the following directory structure: + + ./layout.jade + ./includes/ + ./head.jade + ./tail.jade + +and the following _layout.jade_: + + html + include includes/head + body + h1 My Site + p Welcome to my super amazing site. + include includes/foot + +both includes _includes/head_ and _includes/foot_ are +read relative to the `filename` option given to _layout.jade_, +which should be an absolute path to this file, however Express does this for you. Include then parses these files, and injects the AST produced to render what you would expect: + +```html + + + My Site + + + +

    My Site

    +

    Welcome to my super lame site.

    + + + +``` + + As mentioned `include` can be used to include other content + such as html or css. By providing an extension Jade will not + assume that the file is Jade source and will include it as + a literal: + +``` +html + body + include content.html +``` + + Include directives may also accept a block, in which case the + the given block will be appended to the _last_ block defined + in the file. For example if `head.jade` contains: + +``` +head + script(src='/jquery.js') +``` + + We may append values by providing a block to `include head` + as shown below, adding the two scripts. + +``` +html + include head + script(src='/foo.js') + script(src='/bar.js') + body + h1 test +``` + + You may also `yield` within an included template, allowing you to explicitly mark where the block given to `include` will be placed. Suppose for example you wish to prepend scripts rather than append, you might do the following: + +``` +head + yield + script(src='/jquery.js') + script(src='/jquery.ui.js') +``` + + Since included Jade is parsed and literally merges the AST, lexically scoped variables function as if the included Jade was written right in the same file. This means `include` may be used as sort of partial, for example support we have `user.jade` which utilizes a `user` variable. + +``` +h1= user.name +p= user.occupation +``` + +We could then simply `include user` while iterating users, and since the `user` variable is already defined within the loop the included template will have access to it. + +``` +users = [{ name: 'Tobi', occupation: 'Ferret' }] + +each user in users + .user + include user +``` + +yielding: + +```html +
    +

    Tobi

    +

    Ferret

    +
    +``` + +If we wanted to expose a different variable name as `user` since `user.jade` references that name, we could simply define a new variable as shown here with `user = person`: + +``` +each person in users + .user + user = person + include user +``` + +## Mixins + + Mixins are converted to regular JavaScript functions in + the compiled template that Jade constructs. Mixins may + take arguments, though not required: + + mixin list + ul + li foo + li bar + li baz + + Utilizing a mixin without args looks similar, just without a block: + + h2 Groceries + mixin list + + Mixins may take one or more arguments as well, the arguments + are regular javascripts expressions, so for example the following: + + mixin pets(pets) + ul.pets + - each pet in pets + li= pet + + mixin profile(user) + .user + h2= user.name + mixin pets(user.pets) + + Would yield something similar to the following html: + +```html +
    +

    tj

    +
      +
    • tobi
    • +
    • loki
    • +
    • jane
    • +
    • manny
    • +
    +
    +``` + +## Generated Output + + Suppose we have the following Jade: + +``` +- var title = 'yay' +h1.title #{title} +p Just an example +``` + + When the `compileDebug` option is not explicitly `false`, Jade + will compile the function instrumented with `__.lineno = n;`, which + in the event of an exception is passed to `rethrow()` which constructs + a useful message relative to the initial Jade input. + +```js +function anonymous(locals) { + var __ = { lineno: 1, input: "- var title = 'yay'\nh1.title #{title}\np Just an example", filename: "testing/test.js" }; + var rethrow = jade.rethrow; + try { + var attrs = jade.attrs, escape = jade.escape; + var buf = []; + with (locals || {}) { + var interp; + __.lineno = 1; + var title = 'yay' + __.lineno = 2; + buf.push(''); + buf.push('' + escape((interp = title) == null ? '' : interp) + ''); + buf.push(''); + __.lineno = 3; + buf.push('

    '); + buf.push('Just an example'); + buf.push('

    '); + } + return buf.join(""); + } catch (err) { + rethrow(err, __.input, __.filename, __.lineno); + } +} +``` + +When the `compileDebug` option _is_ explicitly `false`, this instrumentation +is stripped, which is very helpful for light-weight client-side templates. Combining Jade's options with the `./runtime.js` file in this repo allows you +to toString() compiled templates and avoid running the entire Jade library on +the client, increasing performance, and decreasing the amount of JavaScript +required. + +```js +function anonymous(locals) { + var attrs = jade.attrs, escape = jade.escape; + var buf = []; + with (locals || {}) { + var interp; + var title = 'yay' + buf.push(''); + buf.push('' + escape((interp = title) == null ? '' : interp) + ''); + buf.push(''); + buf.push('

    '); + buf.push('Just an example'); + buf.push('

    '); + } + return buf.join(""); +} +``` + +## Example Makefile + + Below is an example Makefile used to compile _pages/*.jade_ + into _pages/*.html_ files by simply executing `make`. + +```make +JADE = $(shell find pages/*.jade) +HTML = $(JADE:.jade=.html) + +all: $(HTML) + +%.html: %.jade + jade < $< --path $< > $@ + +clean: + rm -f $(HTML) + +.PHONY: clean +``` + +this can be combined with the `watch(1)` command to produce +a watcher-like behaviour: + + $ watch make + +## jade(1) + +``` + +Usage: jade [options] [dir|file ...] + +Options: + + -h, --help output usage information + -v, --version output the version number + -o, --obj javascript options object + -O, --out output the compiled html to + -p, --path filename used to resolve includes over stdio + +Examples: + + # translate jade the templates dir + $ jade templates + + # create {foo,bar}.html + $ jade {foo,bar}.jade + + # jade over stdio + $ jade < my.jade > my.html + + # jade over stdio specifying filename to resolve include directives + $ jade < my.jade -p my.jade > my.html + + # jade over stdio + $ echo "h1 Jade!" | jade + + # foo, bar dirs rendering to /tmp + $ jade foo bar --out /tmp + +``` + +## License + +(The MIT License) + +Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/jade/bin/jade b/node_modules/jade/bin/jade new file mode 100755 index 0000000..fd4122c --- /dev/null +++ b/node_modules/jade/bin/jade @@ -0,0 +1,125 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var fs = require('fs') + , program = require('commander') + , path = require('path') + , basename = path.basename + , dirname = path.dirname + , resolve = path.resolve + , join = path.join + , mkdirp = require('mkdirp') + , jade = require('../'); + +// jade options + +var options = {}; + +// options + +program + .version(jade.version) + .usage('[options] [dir|file ...]') + .option('-o, --obj ', 'javascript options object') + .option('-O, --out ', 'output the compiled html to ') + .option('-p, --path ', 'filename used to resolve includes') + +program.on('--help', function(){ + console.log(' Examples:'); + console.log(''); + console.log(' # translate jade the templates dir'); + console.log(' $ jade templates'); + console.log(''); + console.log(' # create {foo,bar}.html'); + console.log(' $ jade {foo,bar}.jade'); + console.log(''); + console.log(' # jade over stdio'); + console.log(' $ jade < my.jade > my.html'); + console.log(''); + console.log(' # jade over stdio'); + console.log(' $ echo "h1 Jade!" | jade'); + console.log(''); + console.log(' # foo, bar dirs rendering to /tmp'); + console.log(' $ jade foo bar --out /tmp '); + console.log(''); +}); + +program.parse(process.argv); + +// options given, parse them + +if (program.obj) options = eval('(' + program.obj + ')'); + +// filename + +if (program.path) options.filename = program.path; + +// left-over args are file paths + +var files = program.args; + +// compile files + +if (files.length) { + console.log(); + files.forEach(renderFile); + process.on('exit', console.log); +// stdio +} else { + stdin(); +} + +/** + * Compile from stdin. + */ + +function stdin() { + var buf = ''; + process.stdin.setEncoding('utf8'); + process.stdin.on('data', function(chunk){ buf += chunk; }); + process.stdin.on('end', function(){ + var fn = jade.compile(buf, options); + process.stdout.write(fn(options)); + }).resume(); +} + +/** + * Process the given path, compiling the jade files found. + * Always walk the subdirectories. + */ + +function renderFile(path) { + var re = /\.jade$/; + fs.lstat(path, function(err, stat) { + if (err) throw err; + // Found jade file + if (stat.isFile() && re.test(path)) { + fs.readFile(path, 'utf8', function(err, str){ + if (err) throw err; + options.filename = path; + var fn = jade.compile(str, options); + path = path.replace(re, '.html'); + if (program.out) path = join(program.out, basename(path)); + var dir = resolve(dirname(path)); + mkdirp(dir, 0755, function(err){ + if (err) throw err; + fs.writeFile(path, fn(options), function(err){ + if (err) throw err; + console.log(' \033[90mrendered \033[36m%s\033[0m', path); + }); + }); + }); + // Found directory + } else if (stat.isDirectory()) { + fs.readdir(path, function(err, files) { + if (err) throw err; + files.map(function(filename) { + return path + '/' + filename; + }).forEach(renderFile); + }); + } + }); +} diff --git a/node_modules/jade/index.js b/node_modules/jade/index.js new file mode 100644 index 0000000..857e431 --- /dev/null +++ b/node_modules/jade/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/jade'); \ No newline at end of file diff --git a/node_modules/jade/jade.js b/node_modules/jade/jade.js new file mode 100644 index 0000000..a8bf6cb --- /dev/null +++ b/node_modules/jade/jade.js @@ -0,0 +1,3135 @@ +(function() { + +// CommonJS require() + +function require(p){ + var path = require.resolve(p) + , mod = require.modules[path]; + if (!mod) throw new Error('failed to require "' + p + '"'); + if (!mod.exports) { + mod.exports = {}; + mod.call(mod.exports, mod, mod.exports, require.relative(path)); + } + return mod.exports; + } + +require.modules = {}; + +require.resolve = function (path){ + var orig = path + , reg = path + '.js' + , index = path + '/index.js'; + return require.modules[reg] && reg + || require.modules[index] && index + || orig; + }; + +require.register = function (path, fn){ + require.modules[path] = fn; + }; + +require.relative = function (parent) { + return function(p){ + if ('.' != p[0]) return require(p); + + var path = parent.split('/') + , segs = p.split('/'); + path.pop(); + + for (var i = 0; i < segs.length; i++) { + var seg = segs[i]; + if ('..' == seg) path.pop(); + else if ('.' != seg) path.push(seg); + } + + return require(path.join('/')); + }; + }; + + +require.register("compiler.js", function(module, exports, require){ + +/*! + * Jade - Compiler + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var nodes = require('./nodes') + , filters = require('./filters') + , doctypes = require('./doctypes') + , selfClosing = require('./self-closing') + , inlineTags = require('./inline-tags') + , utils = require('./utils'); + + + if (!Object.keys) { + Object.keys = function(obj){ + var arr = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + arr.push(key); + } + } + return arr; + } + } + + if (!String.prototype.trimLeft) { + String.prototype.trimLeft = function(){ + return this.replace(/^\s+/, ''); + } + } + + + +/** + * Initialize `Compiler` with the given `node`. + * + * @param {Node} node + * @param {Object} options + * @api public + */ + +var Compiler = module.exports = function Compiler(node, options) { + this.options = options = options || {}; + this.node = node; + this.hasCompiledDoctype = false; + this.hasCompiledTag = false; + this.pp = options.pretty || false; + this.debug = false !== options.compileDebug; + this.indents = 0; + if (options.doctype) this.setDoctype(options.doctype); +}; + +/** + * Compiler prototype. + */ + +Compiler.prototype = { + + /** + * Compile parse tree to JavaScript. + * + * @api public + */ + + compile: function(){ + this.buf = ['var interp;']; + this.lastBufferedIdx = -1 + this.visit(this.node); + return this.buf.join('\n'); + }, + + /** + * Sets the default doctype `name`. Sets terse mode to `true` when + * html 5 is used, causing self-closing tags to end with ">" vs "/>", + * and boolean attributes are not mirrored. + * + * @param {string} name + * @api public + */ + + setDoctype: function(name){ + var doctype = doctypes[(name || 'default').toLowerCase()]; + doctype = doctype || ''; + this.doctype = doctype; + this.terse = '5' == name || 'html' == name; + this.xml = 0 == this.doctype.indexOf('" vs "/>", + * and boolean attributes are not mirrored. + * + * @param {Doctype} doctype + * @api public + */ + + visitDoctype: function(doctype){ + if (doctype && (doctype.val || !this.doctype)) { + this.setDoctype(doctype.val || 'default'); + } + + if (this.doctype) this.buffer(this.doctype); + this.hasCompiledDoctype = true; + }, + + /** + * Visit `mixin`, generating a function that + * may be called within the template. + * + * @param {Mixin} mixin + * @api public + */ + + visitMixin: function(mixin){ + var name = mixin.name.replace(/-/g, '_') + '_mixin' + , args = mixin.args || ''; + + if (mixin.block) { + this.buf.push('var ' + name + ' = function(' + args + '){'); + this.visit(mixin.block); + this.buf.push('}'); + } else { + this.buf.push(name + '(' + args + ');'); + } + }, + + /** + * Visit `tag` buffering tag markup, generating + * attributes, visiting the `tag`'s code and block. + * + * @param {Tag} tag + * @api public + */ + + visitTag: function(tag){ + this.indents++; + var name = tag.name; + + if (!this.hasCompiledTag) { + if (!this.hasCompiledDoctype && 'html' == name) { + this.visitDoctype(); + } + this.hasCompiledTag = true; + } + + // pretty print + if (this.pp && inlineTags.indexOf(name) == -1) { + this.buffer('\\n' + Array(this.indents).join(' ')); + } + + if (~selfClosing.indexOf(name) && !this.xml) { + this.buffer('<' + name); + this.visitAttributes(tag.attrs); + this.terse + ? this.buffer('>') + : this.buffer('/>'); + } else { + // Optimize attributes buffering + if (tag.attrs.length) { + this.buffer('<' + name); + if (tag.attrs.length) this.visitAttributes(tag.attrs); + this.buffer('>'); + } else { + this.buffer('<' + name + '>'); + } + if (tag.code) this.visitCode(tag.code); + if (tag.text) this.buffer(utils.text(tag.text.nodes[0].trimLeft())); + this.escape = 'pre' == tag.name; + this.visit(tag.block); + + // pretty print + if (this.pp && !~inlineTags.indexOf(name) && !tag.textOnly) { + this.buffer('\\n' + Array(this.indents).join(' ')); + } + + this.buffer(''); + } + this.indents--; + }, + + /** + * Visit `filter`, throwing when the filter does not exist. + * + * @param {Filter} filter + * @api public + */ + + visitFilter: function(filter){ + var fn = filters[filter.name]; + + // unknown filter + if (!fn) { + if (filter.isASTFilter) { + throw new Error('unknown ast filter "' + filter.name + ':"'); + } else { + throw new Error('unknown filter ":' + filter.name + '"'); + } + } + if (filter.isASTFilter) { + this.buf.push(fn(filter.block, this, filter.attrs)); + } else { + var text = filter.block.nodes.join(''); + this.buffer(utils.text(fn(text, filter.attrs))); + } + }, + + /** + * Visit `text` node. + * + * @param {Text} text + * @api public + */ + + visitText: function(text){ + text = utils.text(text.nodes.join('')); + if (this.escape) text = escape(text); + this.buffer(text); + this.buffer('\\n'); + }, + + /** + * Visit a `comment`, only buffering when the buffer flag is set. + * + * @param {Comment} comment + * @api public + */ + + visitComment: function(comment){ + if (!comment.buffer) return; + if (this.pp) this.buffer('\\n' + Array(this.indents + 1).join(' ')); + this.buffer(''); + }, + + /** + * Visit a `BlockComment`. + * + * @param {Comment} comment + * @api public + */ + + visitBlockComment: function(comment){ + if (!comment.buffer) return; + if (0 == comment.val.trim().indexOf('if')) { + this.buffer(''); + } else { + this.buffer(''); + } + }, + + /** + * Visit `code`, respecting buffer / escape flags. + * If the code is followed by a block, wrap it in + * a self-calling function. + * + * @param {Code} code + * @api public + */ + + visitCode: function(code){ + // Wrap code blocks with {}. + // we only wrap unbuffered code blocks ATM + // since they are usually flow control + + // Buffer code + if (code.buffer) { + var val = code.val.trimLeft(); + this.buf.push('var __val__ = ' + val); + val = 'null == __val__ ? "" : __val__'; + if (code.escape) val = 'escape(' + val + ')'; + this.buf.push("buf.push(" + val + ");"); + } else { + this.buf.push(code.val); + } + + // Block support + if (code.block) { + if (!code.buffer) this.buf.push('{'); + this.visit(code.block); + if (!code.buffer) this.buf.push('}'); + } + }, + + /** + * Visit `each` block. + * + * @param {Each} each + * @api public + */ + + visitEach: function(each){ + this.buf.push('' + + '// iterate ' + each.obj + '\n' + + '(function(){\n' + + ' if (\'number\' == typeof ' + each.obj + '.length) {\n' + + ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n' + + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n'); + + this.visit(each.block); + + this.buf.push('' + + ' }\n' + + ' } else {\n' + + ' for (var ' + each.key + ' in ' + each.obj + ') {\n' + + ' if (' + each.obj + '.hasOwnProperty(' + each.key + ')){' + + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n'); + + this.visit(each.block); + + this.buf.push(' }\n'); + + this.buf.push(' }\n }\n}).call(this);\n'); + }, + + /** + * Visit `attrs`. + * + * @param {Array} attrs + * @api public + */ + + visitAttributes: function(attrs){ + var buf = [] + , classes = []; + + if (this.terse) buf.push('terse: true'); + + attrs.forEach(function(attr){ + if (attr.name == 'class') { + classes.push('(' + attr.val + ')'); + } else { + var pair = "'" + attr.name + "':(" + attr.val + ')'; + buf.push(pair); + } + }); + + if (classes.length) { + classes = classes.join(" + ' ' + "); + buf.push("class: " + classes); + } + + buf = buf.join(', ').replace('class:', '"class":'); + + this.buf.push("buf.push(attrs({ " + buf + " }));"); + } +}; + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api private + */ + +function escape(html){ + return String(html) + .replace(/&(?!\w+;)/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +}); // module: compiler.js + +require.register("doctypes.js", function(module, exports, require){ + +/*! + * Jade - doctypes + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +module.exports = { + '5': '' + , 'xml': '' + , 'default': '' + , 'transitional': '' + , 'strict': '' + , 'frameset': '' + , '1.1': '' + , 'basic': '' + , 'mobile': '' +}; +}); // module: doctypes.js + +require.register("filters.js", function(module, exports, require){ + +/*! + * Jade - filters + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +module.exports = { + + /** + * Wrap text with CDATA block. + */ + + cdata: function(str){ + return ''; + }, + + /** + * Transform sass to css, wrapped in style tags. + */ + + sass: function(str){ + str = str.replace(/\\n/g, '\n'); + var sass = require('sass').render(str).replace(/\n/g, '\\n'); + return ''; + }, + + /** + * Transform stylus to css, wrapped in style tags. + */ + + stylus: function(str, options){ + var ret; + str = str.replace(/\\n/g, '\n'); + var stylus = require('stylus'); + stylus(str, options).render(function(err, css){ + if (err) throw err; + ret = css.replace(/\n/g, '\\n'); + }); + return ''; + }, + + /** + * Transform less to css, wrapped in style tags. + */ + + less: function(str){ + var ret; + str = str.replace(/\\n/g, '\n'); + require('less').render(str, function(err, css){ + if (err) throw err; + ret = ''; + }); + return ret; + }, + + /** + * Transform markdown to html. + */ + + markdown: function(str){ + var md; + + // support markdown / discount + try { + md = require('markdown'); + } catch (err){ + try { + md = require('discount'); + } catch (err) { + try { + md = require('markdown-js'); + } catch (err) { + throw new Error('Cannot find markdown library, install markdown or discount'); + } + } + } + + str = str.replace(/\\n/g, '\n'); + return md.parse(str).replace(/\n/g, '\\n').replace(/'/g,'''); + }, + + /** + * Transform coffeescript to javascript. + */ + + coffeescript: function(str){ + str = str.replace(/\\n/g, '\n'); + var js = require('coffee-script').compile(str).replace(/\n/g, '\\n'); + return ''; + } +}; + +}); // module: filters.js + +require.register("inline-tags.js", function(module, exports, require){ + +/*! + * Jade - inline tags + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +module.exports = [ + 'a' + , 'abbr' + , 'acronym' + , 'b' + , 'br' + , 'code' + , 'em' + , 'font' + , 'i' + , 'img' + , 'ins' + , 'kbd' + , 'map' + , 'samp' + , 'small' + , 'span' + , 'strong' + , 'sub' + , 'sup' +]; +}); // module: inline-tags.js + +require.register("jade.js", function(module, exports, require){ + +/*! + * Jade + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Parser = require('./parser') + , Lexer = require('./lexer') + , Compiler = require('./compiler') + , runtime = require('./runtime') + +/** + * Library version. + */ + +exports.version = '0.19.0'; + +/** + * Expose self closing tags. + */ + +exports.selfClosing = require('./self-closing'); + +/** + * Default supported doctypes. + */ + +exports.doctypes = require('./doctypes'); + +/** + * Text filters. + */ + +exports.filters = require('./filters'); + +/** + * Utilities. + */ + +exports.utils = require('./utils'); + +/** + * Expose `Compiler`. + */ + +exports.Compiler = Compiler; + +/** + * Expose `Parser`. + */ + +exports.Parser = Parser; + +/** + * Expose `Lexer`. + */ + +exports.Lexer = Lexer; + +/** + * Nodes. + */ + +exports.nodes = require('./nodes'); + +/** + * Jade runtime helpers. + */ + +exports.runtime = runtime; + +/** + * Template function cache. + */ + +exports.cache = {}; + +/** + * Parse the given `str` of jade and return a function body. + * + * @param {String} str + * @param {Object} options + * @return {String} + * @api private + */ + +function parse(str, options){ + try { + // Parse + var parser = new Parser(str, options.filename, options); + + // Compile + var compiler = new (options.compiler || Compiler)(parser.parse(), options) + , js = compiler.compile(); + + // Debug compiler + if (options.debug) { + console.error('\nCompiled Function:\n\n\033[90m%s\033[0m', js.replace(/^/gm, ' ')); + } + + return '' + + 'var buf = [];\n' + + (options.self + ? 'var self = locals || {};\n' + js + : 'with (locals || {}) {\n' + js + '\n}\n') + + 'return buf.join("");'; + } catch (err) { + parser = parser.context(); + runtime.rethrow(err, parser.filename, parser.lexer.lineno); + } +} + +/** + * Compile a `Function` representation of the given jade `str`. + * + * Options: + * + * - `compileDebug` when `false` debugging code is stripped from the compiled template + * - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()` + * for use with the Jade client-side runtime.js + * + * @param {String} str + * @param {Options} options + * @return {Function} + * @api public + */ + +exports.compile = function(str, options){ + var options = options || {} + , client = options.client + , filename = options.filename + ? JSON.stringify(options.filename) + : 'undefined' + , fn; + + if (options.compileDebug !== false) { + fn = [ + 'var __jade = [{ lineno: 1, filename: ' + filename + ' }];' + , 'try {' + , parse(String(str), options || {}) + , '} catch (err) {' + , ' rethrow(err, __jade[0].filename, __jade[0].lineno);' + , '}' + ].join('\n'); + } else { + fn = parse(String(str), options || {}); + } + + if (client) { + fn = 'var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;\n' + fn; + } + + fn = new Function('locals, attrs, escape, rethrow', fn); + + if (client) return fn; + + return function(locals){ + return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow); + }; +}; + +/** + * Render the given `str` of jade and invoke + * the callback `fn(err, str)`. + * + * Options: + * + * - `cache` enable template caching + * - `filename` filename required for `include` / `extends` and caching + * + * @param {String} str + * @param {Object|Function} options or fn + * @param {Function} fn + * @api public + */ + +exports.render = function(str, options, fn){ + // swap args + if ('function' == typeof options) { + fn = options, options = {}; + } + + // cache requires .filename + if (options.cache && !options.filename) { + return fn(new Error('the "filename" option is required for caching')); + } + + try { + var path = options.filename; + var tmpl = options.cache + ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options)) + : exports.compile(str, options); + fn(null, tmpl(options)); + } catch (err) { + fn(err); + } +}; + +/** + * Render a Jade file at the given `path` and callback `fn(err, str)`. + * + * @param {String} path + * @param {Object|Function} options or callback + * @param {Function} fn + * @api public + */ + +exports.renderFile = function(path, options, fn){ + var key = path + ':string'; + + if ('function' == typeof options) { + fn = options, options = {}; + } + + try { + options.filename = path; + var str = options.cache + ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8')) + : fs.readFileSync(path, 'utf8'); + exports.render(str, options, fn); + } catch (err) { + fn(err); + } +}; + +/** + * Express support. + */ + +exports.__express = exports.renderFile; + +}); // module: jade.js + +require.register("lexer.js", function(module, exports, require){ + +/*! + * Jade - Lexer + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Initialize `Lexer` with the given `str`. + * + * Options: + * + * - `colons` allow colons for attr delimiters + * + * @param {String} str + * @param {Object} options + * @api private + */ + +var Lexer = module.exports = function Lexer(str, options) { + options = options || {}; + this.input = str.replace(/\r\n|\r/g, '\n'); + this.colons = options.colons; + this.deferredTokens = []; + this.lastIndents = 0; + this.lineno = 1; + this.stash = []; + this.indentStack = []; + this.indentRe = null; + this.pipeless = false; +}; + +/** + * Lexer prototype. + */ + +Lexer.prototype = { + + /** + * Construct a token with the given `type` and `val`. + * + * @param {String} type + * @param {String} val + * @return {Object} + * @api private + */ + + tok: function(type, val){ + return { + type: type + , line: this.lineno + , val: val + } + }, + + /** + * Consume the given `len` of input. + * + * @param {Number} len + * @api private + */ + + consume: function(len){ + this.input = this.input.substr(len); + }, + + /** + * Scan for `type` with the given `regexp`. + * + * @param {String} type + * @param {RegExp} regexp + * @return {Object} + * @api private + */ + + scan: function(regexp, type){ + var captures; + if (captures = regexp.exec(this.input)) { + this.consume(captures[0].length); + return this.tok(type, captures[1]); + } + }, + + /** + * Defer the given `tok`. + * + * @param {Object} tok + * @api private + */ + + defer: function(tok){ + this.deferredTokens.push(tok); + }, + + /** + * Lookahead `n` tokens. + * + * @param {Number} n + * @return {Object} + * @api private + */ + + lookahead: function(n){ + var fetch = n - this.stash.length; + while (fetch-- > 0) this.stash.push(this.next()); + return this.stash[--n]; + }, + + /** + * Return the indexOf `start` / `end` delimiters. + * + * @param {String} start + * @param {String} end + * @return {Number} + * @api private + */ + + indexOfDelimiters: function(start, end){ + var str = this.input + , nstart = 0 + , nend = 0 + , pos = 0; + for (var i = 0, len = str.length; i < len; ++i) { + if (start == str[i]) { + ++nstart; + } else if (end == str[i]) { + if (++nend == nstart) { + pos = i; + break; + } + } + } + return pos; + }, + + /** + * Stashed token. + */ + + stashed: function() { + return this.stash.length + && this.stash.shift(); + }, + + /** + * Deferred token. + */ + + deferred: function() { + return this.deferredTokens.length + && this.deferredTokens.shift(); + }, + + /** + * end-of-source. + */ + + eos: function() { + if (this.input.length) return; + if (this.indentStack.length) { + this.indentStack.shift(); + return this.tok('outdent'); + } else { + return this.tok('eos'); + } + }, + + /** + * Comment. + */ + + comment: function() { + var captures; + if (captures = /^ *\/\/(-)?([^\n]*)/.exec(this.input)) { + this.consume(captures[0].length); + var tok = this.tok('comment', captures[2]); + tok.buffer = '-' != captures[1]; + return tok; + } + }, + + /** + * Tag. + */ + + tag: function() { + var captures; + if (captures = /^(\w[-:\w]*)/.exec(this.input)) { + this.consume(captures[0].length); + var tok, name = captures[1]; + if (':' == name[name.length - 1]) { + name = name.slice(0, -1); + tok = this.tok('tag', name); + this.defer(this.tok(':')); + while (' ' == this.input[0]) this.input = this.input.substr(1); + } else { + tok = this.tok('tag', name); + } + return tok; + } + }, + + /** + * Filter. + */ + + filter: function() { + return this.scan(/^:(\w+)/, 'filter'); + }, + + /** + * Doctype. + */ + + doctype: function() { + return this.scan(/^(?:!!!|doctype) *([^\n]+)?/, 'doctype'); + }, + + /** + * Id. + */ + + id: function() { + return this.scan(/^#([\w-]+)/, 'id'); + }, + + /** + * Class. + */ + + className: function() { + return this.scan(/^\.([\w-]+)/, 'class'); + }, + + /** + * Text. + */ + + text: function() { + return this.scan(/^(?:\| ?)?([^\n]+)/, 'text'); + }, + + /** + * Extends. + */ + + extends: function() { + return this.scan(/^extends +([^\n]+)/, 'extends'); + }, + + /** + * Block prepend. + */ + + prepend: function() { + var captures; + if (captures = /^prepend +([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var mode = 'prepend' + , name = captures[1] + , tok = this.tok('block', name); + tok.mode = mode; + return tok; + } + }, + + /** + * Block append. + */ + + append: function() { + var captures; + if (captures = /^append +([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var mode = 'append' + , name = captures[1] + , tok = this.tok('block', name); + tok.mode = mode; + return tok; + } + }, + + /** + * Block. + */ + + block: function() { + var captures; + if (captures = /^block +(?:(prepend|append) +)?([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var mode = captures[1] || 'replace' + , name = captures[2] + , tok = this.tok('block', name); + tok.mode = mode; + return tok; + } + }, + + /** + * Yield. + */ + + yield: function() { + return this.scan(/^yield */, 'yield'); + }, + + /** + * Include. + */ + + include: function() { + return this.scan(/^include +([^\n]+)/, 'include'); + }, + + /** + * Case. + */ + + case: function() { + return this.scan(/^case +([^\n]+)/, 'case'); + }, + + /** + * When. + */ + + when: function() { + return this.scan(/^when +([^:\n]+)/, 'when'); + }, + + /** + * Default. + */ + + default: function() { + return this.scan(/^default */, 'default'); + }, + + /** + * Assignment. + */ + + assignment: function() { + var captures; + if (captures = /^(\w+) += *([^;\n]+)( *;? *)/.exec(this.input)) { + this.consume(captures[0].length); + var name = captures[1] + , val = captures[2]; + return this.tok('code', 'var ' + name + ' = (' + val + ');'); + } + }, + + /** + * Mixin. + */ + + mixin: function(){ + var captures; + if (captures = /^mixin +([-\w]+)(?: *\((.*)\))?/.exec(this.input)) { + this.consume(captures[0].length); + var tok = this.tok('mixin', captures[1]); + tok.args = captures[2]; + return tok; + } + }, + + /** + * Conditional. + */ + + conditional: function() { + var captures; + if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) { + this.consume(captures[0].length); + var type = captures[1] + , js = captures[2]; + + switch (type) { + case 'if': js = 'if (' + js + ')'; break; + case 'unless': js = 'if (!(' + js + '))'; break; + case 'else if': js = 'else if (' + js + ')'; break; + case 'else': js = 'else'; break; + } + + return this.tok('code', js); + } + }, + + /** + * While. + */ + + while: function() { + var captures; + if (captures = /^while +([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + return this.tok('code', 'while (' + captures[1] + ')'); + } + }, + + /** + * Each. + */ + + each: function() { + var captures; + if (captures = /^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var tok = this.tok('each', captures[1]); + tok.key = captures[2] || '$index'; + tok.code = captures[3]; + return tok; + } + }, + + /** + * Code. + */ + + code: function() { + var captures; + if (captures = /^(!?=|-)([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var flags = captures[1]; + captures[1] = captures[2]; + var tok = this.tok('code', captures[1]); + tok.escape = flags[0] === '='; + tok.buffer = flags[0] === '=' || flags[1] === '='; + return tok; + } + }, + + /** + * Attributes. + */ + + attrs: function() { + if ('(' == this.input[0]) { + var index = this.indexOfDelimiters('(', ')') + , str = this.input.substr(1, index-1) + , tok = this.tok('attrs') + , len = str.length + , colons = this.colons + , states = ['key'] + , key = '' + , val = '' + , quote + , c; + + function state(){ + return states[states.length - 1]; + } + + function interpolate(attr) { + return attr.replace(/#\{([^}]+)\}/g, function(_, expr){ + return quote + " + (" + expr + ") + " + quote; + }); + } + + this.consume(index + 1); + tok.attrs = {}; + + function parse(c) { + var real = c; + // TODO: remove when people fix ":" + if (colons && ':' == c) c = '='; + switch (c) { + case ',': + case '\n': + switch (state()) { + case 'expr': + case 'array': + case 'string': + case 'object': + val += c; + break; + default: + states.push('key'); + val = val.trim(); + key = key.trim(); + if ('' == key) return; + tok.attrs[key.replace(/^['"]|['"]$/g, '')] = '' == val + ? true + : interpolate(val); + key = val = ''; + } + break; + case '=': + switch (state()) { + case 'key char': + key += real; + break; + case 'val': + case 'expr': + case 'array': + case 'string': + case 'object': + val += real; + break; + default: + states.push('val'); + } + break; + case '(': + if ('val' == state() + || 'expr' == state()) states.push('expr'); + val += c; + break; + case ')': + if ('expr' == state() + || 'val' == state()) states.pop(); + val += c; + break; + case '{': + if ('val' == state()) states.push('object'); + val += c; + break; + case '}': + if ('object' == state()) states.pop(); + val += c; + break; + case '[': + if ('val' == state()) states.push('array'); + val += c; + break; + case ']': + if ('array' == state()) states.pop(); + val += c; + break; + case '"': + case "'": + switch (state()) { + case 'key': + states.push('key char'); + break; + case 'key char': + states.pop(); + break; + case 'string': + if (c == quote) states.pop(); + val += c; + break; + default: + states.push('string'); + val += c; + quote = c; + } + break; + case '': + break; + default: + switch (state()) { + case 'key': + case 'key char': + key += c; + break; + default: + val += c; + } + } + } + + for (var i = 0; i < len; ++i) { + parse(str[i]); + } + + parse(','); + + return tok; + } + }, + + /** + * Indent | Outdent | Newline. + */ + + indent: function() { + var captures, re; + + // established regexp + if (this.indentRe) { + captures = this.indentRe.exec(this.input); + // determine regexp + } else { + // tabs + re = /^\n(\t*) */; + captures = re.exec(this.input); + + // spaces + if (captures && !captures[1].length) { + re = /^\n( *)/; + captures = re.exec(this.input); + } + + // established + if (captures && captures[1].length) this.indentRe = re; + } + + if (captures) { + var tok + , indents = captures[1].length; + + ++this.lineno; + this.consume(indents + 1); + + if (' ' == this.input[0] || '\t' == this.input[0]) { + throw new Error('Invalid indentation, you can use tabs or spaces but not both'); + } + + // blank line + if ('\n' == this.input[0]) return this.tok('newline'); + + // outdent + if (this.indentStack.length && indents < this.indentStack[0]) { + while (this.indentStack.length && this.indentStack[0] > indents) { + this.stash.push(this.tok('outdent')); + this.indentStack.shift(); + } + tok = this.stash.pop(); + // indent + } else if (indents && indents != this.indentStack[0]) { + this.indentStack.unshift(indents); + tok = this.tok('indent', indents); + // newline + } else { + tok = this.tok('newline'); + } + + return tok; + } + }, + + /** + * Pipe-less text consumed only when + * pipeless is true; + */ + + pipelessText: function() { + if (this.pipeless) { + if ('\n' == this.input[0]) return; + var i = this.input.indexOf('\n'); + if (-1 == i) i = this.input.length; + var str = this.input.substr(0, i); + this.consume(str.length); + return this.tok('text', str); + } + }, + + /** + * ':' + */ + + colon: function() { + return this.scan(/^: */, ':'); + }, + + /** + * Return the next token object, or those + * previously stashed by lookahead. + * + * @return {Object} + * @api private + */ + + advance: function(){ + return this.stashed() + || this.next(); + }, + + /** + * Return the next token object. + * + * @return {Object} + * @api private + */ + + next: function() { + return this.deferred() + || this.eos() + || this.pipelessText() + || this.yield() + || this.doctype() + || this.case() + || this.when() + || this.default() + || this.extends() + || this.append() + || this.prepend() + || this.block() + || this.include() + || this.mixin() + || this.conditional() + || this.each() + || this.while() + || this.assignment() + || this.tag() + || this.filter() + || this.code() + || this.id() + || this.className() + || this.attrs() + || this.indent() + || this.comment() + || this.colon() + || this.text(); + } +}; + +}); // module: lexer.js + +require.register("nodes/block-comment.js", function(module, exports, require){ + +/*! + * Jade - nodes - BlockComment + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `BlockComment` with the given `block`. + * + * @param {String} val + * @param {Block} block + * @param {Boolean} buffer + * @api public + */ + +var BlockComment = module.exports = function BlockComment(val, block, buffer) { + this.block = block; + this.val = val; + this.buffer = buffer; +}; + +/** + * Inherit from `Node`. + */ + +BlockComment.prototype = new Node; +BlockComment.prototype.constructor = BlockComment; + +}); // module: nodes/block-comment.js + +require.register("nodes/block.js", function(module, exports, require){ + +/*! + * Jade - nodes - Block + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a new `Block` with an optional `node`. + * + * @param {Node} node + * @api public + */ + +var Block = module.exports = function Block(node){ + this.nodes = []; + if (node) this.push(node); +}; + +/** + * Inherit from `Node`. + */ + +Block.prototype = new Node; +Block.prototype.constructor = Block; + + +/** + * Replace the nodes in `other` with the nodes + * in `this` block. + * + * @param {Block} other + * @api private + */ + +Block.prototype.replace = function(other){ + other.nodes = this.nodes; +}; + +/** + * Pust the given `node`. + * + * @param {Node} node + * @return {Number} + * @api public + */ + +Block.prototype.push = function(node){ + return this.nodes.push(node); +}; + +/** + * Check if this block is empty. + * + * @return {Boolean} + * @api public + */ + +Block.prototype.isEmpty = function(){ + return 0 == this.nodes.length; +}; + +/** + * Unshift the given `node`. + * + * @param {Node} node + * @return {Number} + * @api public + */ + +Block.prototype.unshift = function(node){ + return this.nodes.unshift(node); +}; + +/** + * Return the "last" block, or the first `yield` node. + * + * @return {Block} + * @api private + */ + +Block.prototype.includeBlock = function(){ + var ret = this + , node; + + for (var i = 0, len = this.nodes.length; i < len; ++i) { + node = this.nodes[i]; + if (node.yield) return node; + else if (node.includeBlock) ret = node.includeBlock(); + else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock(); + } + + return ret; +}; + + +}); // module: nodes/block.js + +require.register("nodes/case.js", function(module, exports, require){ + +/*! + * Jade - nodes - Case + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a new `Case` with `expr`. + * + * @param {String} expr + * @api public + */ + +var Case = exports = module.exports = function Case(expr, block){ + this.expr = expr; + this.block = block; +}; + +/** + * Inherit from `Node`. + */ + +Case.prototype = new Node; +Case.prototype.constructor = Case; + + +var When = exports.When = function When(expr, block){ + this.expr = expr; + this.block = block; + this.debug = false; +}; + +/** + * Inherit from `Node`. + */ + +When.prototype = new Node; +When.prototype.constructor = When; + + + +}); // module: nodes/case.js + +require.register("nodes/code.js", function(module, exports, require){ + +/*! + * Jade - nodes - Code + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Code` node with the given code `val`. + * Code may also be optionally buffered and escaped. + * + * @param {String} val + * @param {Boolean} buffer + * @param {Boolean} escape + * @api public + */ + +var Code = module.exports = function Code(val, buffer, escape) { + this.val = val; + this.buffer = buffer; + this.escape = escape; + if (val.match(/^ *else/)) this.debug = false; +}; + +/** + * Inherit from `Node`. + */ + +Code.prototype = new Node; +Code.prototype.constructor = Code; + +}); // module: nodes/code.js + +require.register("nodes/comment.js", function(module, exports, require){ + +/*! + * Jade - nodes - Comment + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Comment` with the given `val`, optionally `buffer`, + * otherwise the comment may render in the output. + * + * @param {String} val + * @param {Boolean} buffer + * @api public + */ + +var Comment = module.exports = function Comment(val, buffer) { + this.val = val; + this.buffer = buffer; +}; + +/** + * Inherit from `Node`. + */ + +Comment.prototype = new Node; +Comment.prototype.constructor = Comment; + +}); // module: nodes/comment.js + +require.register("nodes/doctype.js", function(module, exports, require){ + +/*! + * Jade - nodes - Doctype + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Doctype` with the given `val`. + * + * @param {String} val + * @api public + */ + +var Doctype = module.exports = function Doctype(val) { + this.val = val; +}; + +/** + * Inherit from `Node`. + */ + +Doctype.prototype = new Node; +Doctype.prototype.constructor = Doctype; + +}); // module: nodes/doctype.js + +require.register("nodes/each.js", function(module, exports, require){ + +/*! + * Jade - nodes - Each + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize an `Each` node, representing iteration + * + * @param {String} obj + * @param {String} val + * @param {String} key + * @param {Block} block + * @api public + */ + +var Each = module.exports = function Each(obj, val, key, block) { + this.obj = obj; + this.val = val; + this.key = key; + this.block = block; +}; + +/** + * Inherit from `Node`. + */ + +Each.prototype = new Node; +Each.prototype.constructor = Each; + +}); // module: nodes/each.js + +require.register("nodes/filter.js", function(module, exports, require){ + +/*! + * Jade - nodes - Filter + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node') + , Block = require('./block'); + +/** + * Initialize a `Filter` node with the given + * filter `name` and `block`. + * + * @param {String} name + * @param {Block|Node} block + * @api public + */ + +var Filter = module.exports = function Filter(name, block, attrs) { + this.name = name; + this.block = block; + this.attrs = attrs; + this.isASTFilter = block instanceof Block; +}; + +/** + * Inherit from `Node`. + */ + +Filter.prototype = new Node; +Filter.prototype.constructor = Filter; + +}); // module: nodes/filter.js + +require.register("nodes/index.js", function(module, exports, require){ + +/*! + * Jade - nodes + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +exports.Node = require('./node'); +exports.Tag = require('./tag'); +exports.Code = require('./code'); +exports.Each = require('./each'); +exports.Case = require('./case'); +exports.Text = require('./text'); +exports.Block = require('./block'); +exports.Mixin = require('./mixin'); +exports.Filter = require('./filter'); +exports.Comment = require('./comment'); +exports.Literal = require('./literal'); +exports.BlockComment = require('./block-comment'); +exports.Doctype = require('./doctype'); + +}); // module: nodes/index.js + +require.register("nodes/literal.js", function(module, exports, require){ + +/*! + * Jade - nodes - Literal + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Literal` node with the given `str. + * + * @param {String} str + * @api public + */ + +var Literal = module.exports = function Literal(str) { + this.str = str + .replace(/\n/g, "\\n") + .replace(/'/g, "\\'"); +}; + +/** + * Inherit from `Node`. + */ + +Literal.prototype = new Node; +Literal.prototype.constructor = Literal; + + +}); // module: nodes/literal.js + +require.register("nodes/mixin.js", function(module, exports, require){ + +/*! + * Jade - nodes - Mixin + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a new `Mixin` with `name` and `block`. + * + * @param {String} name + * @param {String} args + * @param {Block} block + * @api public + */ + +var Mixin = module.exports = function Mixin(name, args, block){ + this.name = name; + this.args = args; + this.block = block; +}; + +/** + * Inherit from `Node`. + */ + +Mixin.prototype = new Node; +Mixin.prototype.constructor = Mixin; + + + +}); // module: nodes/mixin.js + +require.register("nodes/node.js", function(module, exports, require){ + +/*! + * Jade - nodes - Node + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Initialize a `Node`. + * + * @api public + */ + +var Node = module.exports = function Node(){}; +}); // module: nodes/node.js + +require.register("nodes/tag.js", function(module, exports, require){ + +/*! + * Jade - nodes - Tag + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'), + Block = require('./block'); + +/** + * Initialize a `Tag` node with the given tag `name` and optional `block`. + * + * @param {String} name + * @param {Block} block + * @api public + */ + +var Tag = module.exports = function Tag(name, block) { + this.name = name; + this.attrs = []; + this.block = block || new Block; +}; + +/** + * Inherit from `Node`. + */ + +Tag.prototype = new Node; +Tag.prototype.constructor = Tag; + + +/** + * Set attribute `name` to `val`, keep in mind these become + * part of a raw js object literal, so to quote a value you must + * '"quote me"', otherwise or example 'user.name' is literal JavaScript. + * + * @param {String} name + * @param {String} val + * @return {Tag} for chaining + * @api public + */ + +Tag.prototype.setAttribute = function(name, val){ + this.attrs.push({ name: name, val: val }); + return this; +}; + +/** + * Remove attribute `name` when present. + * + * @param {String} name + * @api public + */ + +Tag.prototype.removeAttribute = function(name){ + for (var i = 0, len = this.attrs.length; i < len; ++i) { + if (this.attrs[i] && this.attrs[i].name == name) { + delete this.attrs[i]; + } + } +}; + +/** + * Get attribute value by `name`. + * + * @param {String} name + * @return {String} + * @api public + */ + +Tag.prototype.getAttribute = function(name){ + for (var i = 0, len = this.attrs.length; i < len; ++i) { + if (this.attrs[i] && this.attrs[i].name == name) { + return this.attrs[i].val; + } + } +}; + +}); // module: nodes/tag.js + +require.register("nodes/text.js", function(module, exports, require){ + +/*! + * Jade - nodes - Text + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Text` node with optional `line`. + * + * @param {String} line + * @api public + */ + +var Text = module.exports = function Text(line) { + this.nodes = []; + if ('string' == typeof line) this.push(line); +}; + +/** + * Inherit from `Node`. + */ + +Text.prototype = new Node; +Text.prototype.constructor = Text; + + +/** + * Push the given `node.` + * + * @param {Node} node + * @return {Number} + * @api public + */ + +Text.prototype.push = function(node){ + return this.nodes.push(node); +}; + +}); // module: nodes/text.js + +require.register("parser.js", function(module, exports, require){ + +/*! + * Jade - Parser + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Lexer = require('./lexer') + , nodes = require('./nodes'); + +/** + * Initialize `Parser` with the given input `str` and `filename`. + * + * @param {String} str + * @param {String} filename + * @param {Object} options + * @api public + */ + +var Parser = exports = module.exports = function Parser(str, filename, options){ + this.input = str; + this.lexer = new Lexer(str, options); + this.filename = filename; + this.blocks = {}; + this.options = options; + this.contexts = [this]; +}; + +/** + * Tags that may not contain tags. + */ + +var textOnly = exports.textOnly = ['script', 'style']; + +/** + * Parser prototype. + */ + +Parser.prototype = { + + /** + * Push `parser` onto the context stack, + * or pop and return a `Parser`. + */ + + context: function(parser){ + if (parser) { + this.contexts.push(parser); + } else { + return this.contexts.pop(); + } + }, + + /** + * Return the next token object. + * + * @return {Object} + * @api private + */ + + advance: function(){ + return this.lexer.advance(); + }, + + /** + * Skip `n` tokens. + * + * @param {Number} n + * @api private + */ + + skip: function(n){ + while (n--) this.advance(); + }, + + /** + * Single token lookahead. + * + * @return {Object} + * @api private + */ + + peek: function() { + return this.lookahead(1); + }, + + /** + * Return lexer lineno. + * + * @return {Number} + * @api private + */ + + line: function() { + return this.lexer.lineno; + }, + + /** + * `n` token lookahead. + * + * @param {Number} n + * @return {Object} + * @api private + */ + + lookahead: function(n){ + return this.lexer.lookahead(n); + }, + + /** + * Parse input returning a string of js for evaluation. + * + * @return {String} + * @api public + */ + + parse: function(){ + var block = new nodes.Block, parser; + block.line = this.line(); + + while ('eos' != this.peek().type) { + if ('newline' == this.peek().type) { + this.advance(); + } else { + block.push(this.parseExpr()); + } + } + + if (parser = this.extending) { + this.context(parser); + var ast = parser.parse(); + this.context(); + return ast; + } + + return block; + }, + + /** + * Expect the given type, or throw an exception. + * + * @param {String} type + * @api private + */ + + expect: function(type){ + if (this.peek().type === type) { + return this.advance(); + } else { + throw new Error('expected "' + type + '", but got "' + this.peek().type + '"'); + } + }, + + /** + * Accept the given `type`. + * + * @param {String} type + * @api private + */ + + accept: function(type){ + if (this.peek().type === type) { + return this.advance(); + } + }, + + /** + * tag + * | doctype + * | mixin + * | include + * | filter + * | comment + * | text + * | each + * | code + * | yield + * | id + * | class + */ + + parseExpr: function(){ + switch (this.peek().type) { + case 'tag': + return this.parseTag(); + case 'mixin': + return this.parseMixin(); + case 'block': + return this.parseBlock(); + case 'case': + return this.parseCase(); + case 'when': + return this.parseWhen(); + case 'default': + return this.parseDefault(); + case 'extends': + return this.parseExtends(); + case 'include': + return this.parseInclude(); + case 'doctype': + return this.parseDoctype(); + case 'filter': + return this.parseFilter(); + case 'comment': + return this.parseComment(); + case 'text': + return this.parseText(); + case 'each': + return this.parseEach(); + case 'code': + return this.parseCode(); + case 'yield': + this.advance(); + var block = new nodes.Block; + block.yield = true; + return block; + case 'id': + case 'class': + var tok = this.advance(); + this.lexer.defer(this.lexer.tok('tag', 'div')); + this.lexer.defer(tok); + return this.parseExpr(); + default: + throw new Error('unexpected token "' + this.peek().type + '"'); + } + }, + + /** + * Text + */ + + parseText: function(){ + var tok = this.expect('text') + , node = new nodes.Text(tok.val); + node.line = this.line(); + return node; + }, + + /** + * ':' expr + * | block + */ + + parseBlockExpansion: function(){ + if (':' == this.peek().type) { + this.advance(); + return new nodes.Block(this.parseExpr()); + } else { + return this.block(); + } + }, + + /** + * case + */ + + parseCase: function(){ + var val = this.expect('case').val + , node = new nodes.Case(val); + node.line = this.line(); + node.block = this.block(); + return node; + }, + + /** + * when + */ + + parseWhen: function(){ + var val = this.expect('when').val + return new nodes.Case.When(val, this.parseBlockExpansion()); + }, + + /** + * default + */ + + parseDefault: function(){ + this.expect('default'); + return new nodes.Case.When('default', this.parseBlockExpansion()); + }, + + /** + * code + */ + + parseCode: function(){ + var tok = this.expect('code') + , node = new nodes.Code(tok.val, tok.buffer, tok.escape) + , block + , i = 1; + node.line = this.line(); + while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i; + block = 'indent' == this.lookahead(i).type; + if (block) { + this.skip(i-1); + node.block = this.block(); + } + return node; + }, + + /** + * comment + */ + + parseComment: function(){ + var tok = this.expect('comment') + , node; + + if ('indent' == this.peek().type) { + node = new nodes.BlockComment(tok.val, this.block(), tok.buffer); + } else { + node = new nodes.Comment(tok.val, tok.buffer); + } + + node.line = this.line(); + return node; + }, + + /** + * doctype + */ + + parseDoctype: function(){ + var tok = this.expect('doctype') + , node = new nodes.Doctype(tok.val); + node.line = this.line(); + return node; + }, + + /** + * filter attrs? text-block + */ + + parseFilter: function(){ + var block + , tok = this.expect('filter') + , attrs = this.accept('attrs'); + + this.lexer.pipeless = true; + block = this.parseTextBlock(); + this.lexer.pipeless = false; + + var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs); + node.line = this.line(); + return node; + }, + + /** + * tag ':' attrs? block + */ + + parseASTFilter: function(){ + var block + , tok = this.expect('tag') + , attrs = this.accept('attrs'); + + this.expect(':'); + block = this.block(); + + var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs); + node.line = this.line(); + return node; + }, + + /** + * each block + */ + + parseEach: function(){ + var tok = this.expect('each') + , node = new nodes.Each(tok.code, tok.val, tok.key); + node.line = this.line(); + node.block = this.block(); + return node; + }, + + /** + * 'extends' name + */ + + parseExtends: function(){ + var path = require('path') + , fs = require('fs') + , dirname = path.dirname + , basename = path.basename + , join = path.join; + + if (!this.filename) + throw new Error('the "filename" option is required to extend templates'); + + var path = this.expect('extends').val.trim() + , dir = dirname(this.filename); + + var path = join(dir, path + '.jade') + , str = fs.readFileSync(path, 'utf8') + , parser = new Parser(str, path, this.options); + + parser.blocks = this.blocks; + parser.contexts = this.contexts; + this.extending = parser; + + // TODO: null node + return new nodes.Literal(''); + }, + + /** + * 'block' name block + */ + + parseBlock: function(){ + var block = this.expect('block') + , mode = block.mode + , name = block.val.trim(); + + block = 'indent' == this.peek().type + ? this.block() + : new nodes.Block(new nodes.Literal('')); + + var prev = this.blocks[name]; + + if (prev) { + switch (prev.mode) { + case 'append': + block.nodes = block.nodes.concat(prev.nodes); + prev = block; + break; + case 'prepend': + block.nodes = prev.nodes.concat(block.nodes); + prev = block; + break; + } + } + + block.mode = mode; + return this.blocks[name] = prev || block; + }, + + /** + * include block? + */ + + parseInclude: function(){ + var path = require('path') + , fs = require('fs') + , dirname = path.dirname + , basename = path.basename + , join = path.join; + + var path = this.expect('include').val.trim() + , dir = dirname(this.filename); + + if (!this.filename) + throw new Error('the "filename" option is required to use includes'); + + // no extension + if (!~basename(path).indexOf('.')) { + path += '.jade'; + } + + // non-jade + if ('.jade' != path.substr(-5)) { + var path = join(dir, path) + , str = fs.readFileSync(path, 'utf8'); + return new nodes.Literal(str); + } + + var path = join(dir, path) + , str = fs.readFileSync(path, 'utf8') + , parser = new Parser(str, path, this.options); + + this.context(parser); + var ast = parser.parse(); + this.context(); + ast.filename = path; + + if ('indent' == this.peek().type) { + ast.includeBlock().push(this.block()); + } + + return ast; + }, + + /** + * mixin block + */ + + parseMixin: function(){ + var tok = this.expect('mixin') + , name = tok.val + , args = tok.args; + var block = 'indent' == this.peek().type + ? this.block() + : null; + return new nodes.Mixin(name, args, block); + }, + + /** + * indent (text | newline)* outdent + */ + + parseTextBlock: function(){ + var text = new nodes.Text; + text.line = this.line(); + var spaces = this.expect('indent').val; + if (null == this._spaces) this._spaces = spaces; + var indent = Array(spaces - this._spaces + 1).join(' '); + while ('outdent' != this.peek().type) { + switch (this.peek().type) { + case 'newline': + text.push('\\n'); + this.advance(); + break; + case 'indent': + text.push('\\n'); + this.parseTextBlock().nodes.forEach(function(node){ + text.push(node); + }); + text.push('\\n'); + break; + default: + text.push(indent + this.advance().val); + } + } + + if (spaces == this._spaces) this._spaces = null; + this.expect('outdent'); + return text; + }, + + /** + * indent expr* outdent + */ + + block: function(){ + var block = new nodes.Block; + block.line = this.line(); + this.expect('indent'); + while ('outdent' != this.peek().type) { + if ('newline' == this.peek().type) { + this.advance(); + } else { + block.push(this.parseExpr()); + } + } + this.expect('outdent'); + return block; + }, + + /** + * tag (attrs | class | id)* (text | code | ':')? newline* block? + */ + + parseTag: function(){ + // ast-filter look-ahead + var i = 2; + if ('attrs' == this.lookahead(i).type) ++i; + if (':' == this.lookahead(i).type) { + if ('indent' == this.lookahead(++i).type) { + return this.parseASTFilter(); + } + } + + var name = this.advance().val + , tag = new nodes.Tag(name) + , dot; + + tag.line = this.line(); + + // (attrs | class | id)* + out: + while (true) { + switch (this.peek().type) { + case 'id': + case 'class': + var tok = this.advance(); + tag.setAttribute(tok.type, "'" + tok.val + "'"); + continue; + case 'attrs': + var obj = this.advance().attrs + , names = Object.keys(obj); + for (var i = 0, len = names.length; i < len; ++i) { + var name = names[i] + , val = obj[name]; + tag.setAttribute(name, val); + } + continue; + default: + break out; + } + } + + // check immediate '.' + if ('.' == this.peek().val) { + dot = tag.textOnly = true; + this.advance(); + } + + // (text | code | ':')? + switch (this.peek().type) { + case 'text': + tag.text = this.parseText(); + break; + case 'code': + tag.code = this.parseCode(); + break; + case ':': + this.advance(); + tag.block = new nodes.Block; + tag.block.push(this.parseTag()); + break; + } + + // newline* + while ('newline' == this.peek().type) this.advance(); + + tag.textOnly = tag.textOnly || ~textOnly.indexOf(tag.name); + + // script special-case + if ('script' == tag.name) { + var type = tag.getAttribute('type'); + if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) { + tag.textOnly = false; + } + } + + // block? + if ('indent' == this.peek().type) { + if (tag.textOnly) { + this.lexer.pipeless = true; + tag.block = this.parseTextBlock(); + this.lexer.pipeless = false; + } else { + var block = this.block(); + if (tag.block) { + for (var i = 0, len = block.nodes.length; i < len; ++i) { + tag.block.push(block.nodes[i]); + } + } else { + tag.block = block; + } + } + } + + return tag; + } +}; + +}); // module: parser.js + +require.register("runtime.js", function(module, exports, require){ + +/*! + * Jade - runtime + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Lame Array.isArray() polyfill for now. + */ + +if (!Array.isArray) { + Array.isArray = function(arr){ + return '[object Array]' == Object.prototype.toString.call(arr); + }; +} + +/** + * Lame Object.keys() polyfill for now. + */ + +if (!Object.keys) { + Object.keys = function(obj){ + var arr = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + arr.push(key); + } + } + return arr; + } +} + +/** + * Render the given attributes object. + * + * @param {Object} obj + * @return {String} + * @api private + */ + +exports.attrs = function attrs(obj){ + var buf = [] + , terse = obj.terse; + delete obj.terse; + var keys = Object.keys(obj) + , len = keys.length; + if (len) { + buf.push(''); + for (var i = 0; i < len; ++i) { + var key = keys[i] + , val = obj[key]; + if ('boolean' == typeof val || null == val) { + if (val) { + terse + ? buf.push(key) + : buf.push(key + '="' + key + '"'); + } + } else if ('class' == key && Array.isArray(val)) { + buf.push(key + '="' + exports.escape(val.join(' ')) + '"'); + } else { + buf.push(key + '="' + exports.escape(val) + '"'); + } + } + } + return buf.join(' '); +}; + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function escape(html){ + return String(html) + .replace(/&(?!\w+;)/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +}; + +/** + * Re-throw the given `err` in context to the + * the jade in `filename` at the given `lineno`. + * + * @param {Error} err + * @param {String} filename + * @param {String} lineno + * @api private + */ + +exports.rethrow = function rethrow(err, filename, lineno){ + if (!filename) throw err; + + var context = 3 + , str = require('fs').readFileSync(filename, 'utf8') + , lines = str.split('\n') + , start = Math.max(lineno - context, 0) + , end = Math.min(lines.length, lineno + context); + + // Error context + var context = lines.slice(start, end).map(function(line, i){ + var curr = i + start + 1; + return (curr == lineno ? ' > ' : ' ') + + curr + + '| ' + + line; + }).join('\n'); + + // Alter exception message + err.path = filename; + err.message = (filename || 'Jade') + ':' + lineno + + '\n' + context + '\n\n' + err.message; + throw err; +}; + +}); // module: runtime.js + +require.register("self-closing.js", function(module, exports, require){ + +/*! + * Jade - self closing tags + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +module.exports = [ + 'meta' + , 'img' + , 'link' + , 'input' + , 'area' + , 'base' + , 'col' + , 'br' + , 'hr' +]; +}); // module: self-closing.js + +require.register("utils.js", function(module, exports, require){ + +/*! + * Jade - utils + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Convert interpolation in the given string to JavaScript. + * + * @param {String} str + * @return {String} + * @api private + */ + +var interpolate = exports.interpolate = function(str){ + return str.replace(/(\\)?([#!]){(.*?)}/g, function(str, escape, flag, code){ + return escape + ? str + : "' + " + + ('!' == flag ? '' : 'escape') + + "((interp = " + code.replace(/\\'/g, "'") + + ") == null ? '' : interp) + '"; + }); +}; + +/** + * Escape single quotes in `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +var escape = exports.escape = function(str) { + return str.replace(/'/g, "\\'"); +}; + +/** + * Interpolate, and escape the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.text = function(str){ + return interpolate(escape(str)); +}; +}); // module: utils.js + +window.jade = require("jade"); +})(); diff --git a/node_modules/jade/jade.min.js b/node_modules/jade/jade.min.js new file mode 100644 index 0000000..067b8b5 --- /dev/null +++ b/node_modules/jade/jade.min.js @@ -0,0 +1,2 @@ +(function(){function require(p){var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');return mod.exports||(mod.exports={},mod.call(mod.exports,mod,mod.exports,require.relative(path))),mod.exports}require.modules={},require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[reg]&®||require.modules[index]&&index||orig},require.register=function(path,fn){require.modules[path]=fn},require.relative=function(parent){return function(p){if("."!=p[0])return require(p);var path=parent.split("/"),segs=p.split("/");path.pop();for(var i=0;i",this.doctype=doctype,this.terse="5"==name||"html"==name,this.xml=0==this.doctype.indexOf(""):this.buffer("/>")):(tag.attrs.length?(this.buffer("<"+name),tag.attrs.length&&this.visitAttributes(tag.attrs),this.buffer(">")):this.buffer("<"+name+">"),tag.code&&this.visitCode(tag.code),tag.text&&this.buffer(utils.text(tag.text.nodes[0].trimLeft())),this.escape="pre"==tag.name,this.visit(tag.block),this.pp&&!~inlineTags.indexOf(name)&&!tag.textOnly&&this.buffer("\\n"+Array(this.indents).join(" ")),this.buffer("")),this.indents--},visitFilter:function(filter){var fn=filters[filter.name];if(!fn)throw filter.isASTFilter?new Error('unknown ast filter "'+filter.name+':"'):new Error('unknown filter ":'+filter.name+'"');if(filter.isASTFilter)this.buf.push(fn(filter.block,this,filter.attrs));else{var text=filter.block.nodes.join("");this.buffer(utils.text(fn(text,filter.attrs)))}},visitText:function(text){text=utils.text(text.nodes.join("")),this.escape&&(text=escape(text)),this.buffer(text),this.buffer("\\n")},visitComment:function(comment){if(!comment.buffer)return;this.pp&&this.buffer("\\n"+Array(this.indents+1).join(" ")),this.buffer("")},visitBlockComment:function(comment){if(!comment.buffer)return;0==comment.val.trim().indexOf("if")?(this.buffer("")):(this.buffer(""))},visitCode:function(code){if(code.buffer){var val=code.val.trimLeft();this.buf.push("var __val__ = "+val),val='null == __val__ ? "" : __val__',code.escape&&(val="escape("+val+")"),this.buf.push("buf.push("+val+");")}else this.buf.push(code.val);code.block&&(code.buffer||this.buf.push("{"),this.visit(code.block),code.buffer||this.buf.push("}"))},visitEach:function(each){this.buf.push("// iterate "+each.obj+"\n"+"(function(){\n"+" if ('number' == typeof "+each.obj+".length) {\n"+" for (var "+each.key+" = 0, $$l = "+each.obj+".length; "+each.key+" < $$l; "+each.key+"++) {\n"+" var "+each.val+" = "+each.obj+"["+each.key+"];\n"),this.visit(each.block),this.buf.push(" }\n } else {\n for (var "+each.key+" in "+each.obj+") {\n"+" if ("+each.obj+".hasOwnProperty("+each.key+")){"+" var "+each.val+" = "+each.obj+"["+each.key+"];\n"),this.visit(each.block),this.buf.push(" }\n"),this.buf.push(" }\n }\n}).call(this);\n")},visitAttributes:function(attrs){var buf=[],classes=[];this.terse&&buf.push("terse: true"),attrs.forEach(function(attr){if(attr.name=="class")classes.push("("+attr.val+")");else{var pair="'"+attr.name+"':("+attr.val+")";buf.push(pair)}}),classes.length&&(classes=classes.join(" + ' ' + "),buf.push("class: "+classes)),buf=buf.join(", ").replace("class:",'"class":'),this.buf.push("buf.push(attrs({ "+buf+" }));")}};function escape(html){return String(html).replace(/&(?!\w+;)/g,"&").replace(//g,">").replace(/"/g,""")}}),require.register("doctypes.js",function(module,exports,require){module.exports={5:"",xml:'',"default":'',transitional:'',strict:'',frameset:'',1.1:'',basic:'',mobile:''}}),require.register("filters.js",function(module,exports,require){module.exports={cdata:function(str){return""},sass:function(str){str=str.replace(/\\n/g,"\n");var sass=require("sass").render(str).replace(/\n/g,"\\n");return'"},stylus:function(str,options){var ret;str=str.replace(/\\n/g,"\n");var stylus=require("stylus");return stylus(str,options).render(function(err,css){if(err)throw err;ret=css.replace(/\n/g,"\\n")}),'"},less:function(str){var ret;return str=str.replace(/\\n/g,"\n"),require("less").render(str,function(err,css){if(err)throw err;ret='"}),ret},markdown:function(str){var md;try{md=require("markdown")}catch(err){try{md=require("discount")}catch(err){try{md=require("markdown-js")}catch(err){throw new Error("Cannot find markdown library, install markdown or discount")}}}return str=str.replace(/\\n/g,"\n"),md.parse(str).replace(/\n/g,"\\n").replace(/'/g,"'")},coffeescript:function(str){str=str.replace(/\\n/g,"\n");var js=require("coffee-script").compile(str).replace(/\n/g,"\\n");return'"}}}),require.register("inline-tags.js",function(module,exports,require){module.exports=["a","abbr","acronym","b","br","code","em","font","i","img","ins","kbd","map","samp","small","span","strong","sub","sup"]}),require.register("jade.js",function(module,exports,require){var Parser=require("./parser"),Lexer=require("./lexer"),Compiler=require("./compiler"),runtime=require("./runtime");exports.version="0.19.0",exports.selfClosing=require("./self-closing"),exports.doctypes=require("./doctypes"),exports.filters=require("./filters"),exports.utils=require("./utils"),exports.Compiler=Compiler,exports.Parser=Parser,exports.Lexer=Lexer,exports.nodes=require("./nodes"),exports.runtime=runtime,exports.cache={};function parse(str,options){try{var parser=new Parser(str,options.filename,options),compiler=new(options.compiler||Compiler)(parser.parse(),options),js=compiler.compile();return options.debug&&console.error("\nCompiled Function:\n\n%s",js.replace(/^/gm," ")),"var buf = [];\n"+(options.self?"var self = locals || {};\n"+js:"with (locals || {}) {\n"+js+"\n}\n")+'return buf.join("");'}catch(err){parser=parser.context(),runtime.rethrow(err,parser.filename,parser.lexer.lineno)}}exports.compile=function(str,options){var options=options||{},client=options.client,filename=options.filename?JSON.stringify(options.filename):"undefined",fn;return options.compileDebug!==!1?fn=["var __jade = [{ lineno: 1, filename: "+filename+" }];","try {",parse(String(str),options||{}),"} catch (err) {"," rethrow(err, __jade[0].filename, __jade[0].lineno);","}"].join("\n"):fn=parse(String(str),options||{}),client&&(fn="var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;\n"+fn),fn=new Function("locals, attrs, escape, rethrow",fn),client?fn:function(locals){return fn(locals,runtime.attrs,runtime.escape,runtime.rethrow)}},exports.render=function(str,options,fn){"function"==typeof options&&(fn=options,options={});if(options.cache&&!options.filename)return fn(new Error('the "filename" option is required for caching'));try{var path=options.filename,tmpl=options.cache?exports.cache[path]||(exports.cache[path]=exports.compile(str,options)):exports.compile(str,options);fn(null,tmpl(options))}catch(err){fn(err)}},exports.renderFile=function(path,options,fn){var key=path+":string";"function"==typeof options&&(fn=options,options={});try{options.filename=path;var str=options.cache?exports.cache[key]||(exports.cache[key]=fs.readFileSync(path,"utf8")):fs.readFileSync(path,"utf8");exports.render(str,options,fn)}catch(err){fn(err)}},exports.__express=exports.renderFile}),require.register("lexer.js",function(module,exports,require){var Lexer=module.exports=function(str,options){options=options||{},this.input=str.replace(/\r\n|\r/g,"\n"),this.colons=options.colons,this.deferredTokens=[],this.lastIndents=0,this.lineno=1,this.stash=[],this.indentStack=[],this.indentRe=null,this.pipeless=!1};Lexer.prototype={tok:function(type,val){return{type:type,line:this.lineno,val:val}},consume:function(len){this.input=this.input.substr(len)},scan:function(regexp,type){var captures;if(captures=regexp.exec(this.input))return this.consume(captures[0].length),this.tok(type,captures[1])},defer:function(tok){this.deferredTokens.push(tok)},lookahead:function(n){var fetch=n-this.stash.length;while(fetch-->0)this.stash.push(this.next());return this.stash[--n]},indexOfDelimiters:function(start,end){var str=this.input,nstart=0,nend=0,pos=0;for(var i=0,len=str.length;iindents)this.stash.push(this.tok("outdent")),this.indentStack.shift();tok=this.stash.pop()}else indents&&indents!=this.indentStack[0]?(this.indentStack.unshift(indents),tok=this.tok("indent",indents)):tok=this.tok("newline");return tok}},pipelessText:function(){if(this.pipeless){if("\n"==this.input[0])return;var i=this.input.indexOf("\n");-1==i&&(i=this.input.length);var str=this.input.substr(0,i);return this.consume(str.length),this.tok("text",str)}},colon:function(){return this.scan(/^: */,":")},advance:function(){return this.stashed()||this.next()},next:function(){return this.deferred()||this.eos()||this.pipelessText()||this.yield()||this.doctype()||this.case()||this.when()||this.default()||this.extends()||this.append()||this.prepend()||this.block()||this.include()||this.mixin()||this.conditional()||this.each()||this.while()||this.assignment()||this.tag()||this.filter()||this.code()||this.id()||this.className()||this.attrs()||this.indent()||this.comment()||this.colon()||this.text()}}}),require.register("nodes/block-comment.js",function(module,exports,require){var Node=require("./node"),BlockComment=module.exports=function(val,block,buffer){this.block=block,this.val=val,this.buffer=buffer};BlockComment.prototype=new Node,BlockComment.prototype.constructor=BlockComment}),require.register("nodes/block.js",function(module,exports,require){var Node=require("./node"),Block=module.exports=function(node){this.nodes=[],node&&this.push(node)};Block.prototype=new Node,Block.prototype.constructor=Block,Block.prototype.replace=function(other){other.nodes=this.nodes},Block.prototype.push=function(node){return this.nodes.push(node)},Block.prototype.isEmpty=function(){return 0==this.nodes.length},Block.prototype.unshift=function(node){return this.nodes.unshift(node)},Block.prototype.includeBlock=function(){var ret=this,node;for(var i=0,len=this.nodes.length;i/g,">").replace(/"/g,""")},exports.rethrow=function(err,filename,lineno){if(!filename)throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message,err}}),require.register("self-closing.js",function(module,exports,require){module.exports=["meta","img","link","input","area","base","col","br","hr"]}),require.register("utils.js",function(module,exports,require){var interpolate=exports.interpolate=function(str){return str.replace(/(\\)?([#!]){(.*?)}/g,function(str,escape,flag,code){return escape?str:"' + "+("!"==flag?"":"escape")+"((interp = "+code.replace(/\\'/g,"'")+") == null ? '' : interp) + '"})},escape=exports.escape=function(str){return str.replace(/'/g,"\\'")};exports.text=function(str){return interpolate(escape(str))}}),window.jade=require("jade")})(); \ No newline at end of file diff --git a/node_modules/jade/lib/compiler.js b/node_modules/jade/lib/compiler.js new file mode 100644 index 0000000..7ca543c --- /dev/null +++ b/node_modules/jade/lib/compiler.js @@ -0,0 +1,501 @@ + +/*! + * Jade - Compiler + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var nodes = require('./nodes') + , filters = require('./filters') + , doctypes = require('./doctypes') + , selfClosing = require('./self-closing') + , inlineTags = require('./inline-tags') + , utils = require('./utils'); + +// if browser +// +// if (!Object.keys) { +// Object.keys = function(obj){ +// var arr = []; +// for (var key in obj) { +// if (obj.hasOwnProperty(key)) { +// arr.push(key); +// } +// } +// return arr; +// } +// } +// +// if (!String.prototype.trimLeft) { +// String.prototype.trimLeft = function(){ +// return this.replace(/^\s+/, ''); +// } +// } +// +// end + + +/** + * Initialize `Compiler` with the given `node`. + * + * @param {Node} node + * @param {Object} options + * @api public + */ + +var Compiler = module.exports = function Compiler(node, options) { + this.options = options = options || {}; + this.node = node; + this.hasCompiledDoctype = false; + this.hasCompiledTag = false; + this.pp = options.pretty || false; + this.debug = false !== options.compileDebug; + this.indents = 0; + if (options.doctype) this.setDoctype(options.doctype); +}; + +/** + * Compiler prototype. + */ + +Compiler.prototype = { + + /** + * Compile parse tree to JavaScript. + * + * @api public + */ + + compile: function(){ + this.buf = ['var interp;']; + this.lastBufferedIdx = -1 + this.visit(this.node); + return this.buf.join('\n'); + }, + + /** + * Sets the default doctype `name`. Sets terse mode to `true` when + * html 5 is used, causing self-closing tags to end with ">" vs "/>", + * and boolean attributes are not mirrored. + * + * @param {string} name + * @api public + */ + + setDoctype: function(name){ + var doctype = doctypes[(name || 'default').toLowerCase()]; + doctype = doctype || ''; + this.doctype = doctype; + this.terse = '5' == name || 'html' == name; + this.xml = 0 == this.doctype.indexOf('" vs "/>", + * and boolean attributes are not mirrored. + * + * @param {Doctype} doctype + * @api public + */ + + visitDoctype: function(doctype){ + if (doctype && (doctype.val || !this.doctype)) { + this.setDoctype(doctype.val || 'default'); + } + + if (this.doctype) this.buffer(this.doctype); + this.hasCompiledDoctype = true; + }, + + /** + * Visit `mixin`, generating a function that + * may be called within the template. + * + * @param {Mixin} mixin + * @api public + */ + + visitMixin: function(mixin){ + var name = mixin.name.replace(/-/g, '_') + '_mixin' + , args = mixin.args || ''; + + if (mixin.block) { + this.buf.push('var ' + name + ' = function(' + args + '){'); + this.visit(mixin.block); + this.buf.push('}'); + } else { + this.buf.push(name + '(' + args + ');'); + } + }, + + /** + * Visit `tag` buffering tag markup, generating + * attributes, visiting the `tag`'s code and block. + * + * @param {Tag} tag + * @api public + */ + + visitTag: function(tag){ + this.indents++; + var name = tag.name; + + if (!this.hasCompiledTag) { + if (!this.hasCompiledDoctype && 'html' == name) { + this.visitDoctype(); + } + this.hasCompiledTag = true; + } + + // pretty print + if (this.pp && inlineTags.indexOf(name) == -1) { + this.buffer('\\n' + Array(this.indents).join(' ')); + } + + if (~selfClosing.indexOf(name) && !this.xml) { + this.buffer('<' + name); + this.visitAttributes(tag.attrs); + this.terse + ? this.buffer('>') + : this.buffer('/>'); + } else { + // Optimize attributes buffering + if (tag.attrs.length) { + this.buffer('<' + name); + if (tag.attrs.length) this.visitAttributes(tag.attrs); + this.buffer('>'); + } else { + this.buffer('<' + name + '>'); + } + if (tag.code) this.visitCode(tag.code); + if (tag.text) this.buffer(utils.text(tag.text.nodes[0].trimLeft())); + this.escape = 'pre' == tag.name; + this.visit(tag.block); + + // pretty print + if (this.pp && !~inlineTags.indexOf(name) && !tag.textOnly) { + this.buffer('\\n' + Array(this.indents).join(' ')); + } + + this.buffer(''); + } + this.indents--; + }, + + /** + * Visit `filter`, throwing when the filter does not exist. + * + * @param {Filter} filter + * @api public + */ + + visitFilter: function(filter){ + var fn = filters[filter.name]; + + // unknown filter + if (!fn) { + if (filter.isASTFilter) { + throw new Error('unknown ast filter "' + filter.name + ':"'); + } else { + throw new Error('unknown filter ":' + filter.name + '"'); + } + } + if (filter.isASTFilter) { + this.buf.push(fn(filter.block, this, filter.attrs)); + } else { + var text = filter.block.nodes.join(''); + this.buffer(utils.text(fn(text, filter.attrs))); + } + }, + + /** + * Visit `text` node. + * + * @param {Text} text + * @api public + */ + + visitText: function(text){ + text = utils.text(text.nodes.join('')); + if (this.escape) text = escape(text); + this.buffer(text); + this.buffer('\\n'); + }, + + /** + * Visit a `comment`, only buffering when the buffer flag is set. + * + * @param {Comment} comment + * @api public + */ + + visitComment: function(comment){ + if (!comment.buffer) return; + if (this.pp) this.buffer('\\n' + Array(this.indents + 1).join(' ')); + this.buffer(''); + }, + + /** + * Visit a `BlockComment`. + * + * @param {Comment} comment + * @api public + */ + + visitBlockComment: function(comment){ + if (!comment.buffer) return; + if (0 == comment.val.trim().indexOf('if')) { + this.buffer(''); + } else { + this.buffer(''); + } + }, + + /** + * Visit `code`, respecting buffer / escape flags. + * If the code is followed by a block, wrap it in + * a self-calling function. + * + * @param {Code} code + * @api public + */ + + visitCode: function(code){ + // Wrap code blocks with {}. + // we only wrap unbuffered code blocks ATM + // since they are usually flow control + + // Buffer code + if (code.buffer) { + var val = code.val.trimLeft(); + this.buf.push('var __val__ = ' + val); + val = 'null == __val__ ? "" : __val__'; + if (code.escape) val = 'escape(' + val + ')'; + this.buf.push("buf.push(" + val + ");"); + } else { + this.buf.push(code.val); + } + + // Block support + if (code.block) { + if (!code.buffer) this.buf.push('{'); + this.visit(code.block); + if (!code.buffer) this.buf.push('}'); + } + }, + + /** + * Visit `each` block. + * + * @param {Each} each + * @api public + */ + + visitEach: function(each){ + this.buf.push('' + + '// iterate ' + each.obj + '\n' + + '(function(){\n' + + ' if (\'number\' == typeof ' + each.obj + '.length) {\n' + + ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n' + + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n'); + + this.visit(each.block); + + this.buf.push('' + + ' }\n' + + ' } else {\n' + + ' for (var ' + each.key + ' in ' + each.obj + ') {\n' + // if browser + // + ' if (' + each.obj + '.hasOwnProperty(' + each.key + ')){' + // end + + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n'); + + this.visit(each.block); + + // if browser + // this.buf.push(' }\n'); + // end + + this.buf.push(' }\n }\n}).call(this);\n'); + }, + + /** + * Visit `attrs`. + * + * @param {Array} attrs + * @api public + */ + + visitAttributes: function(attrs){ + var buf = [] + , classes = []; + + if (this.terse) buf.push('terse: true'); + + attrs.forEach(function(attr){ + if (attr.name == 'class') { + classes.push('(' + attr.val + ')'); + } else { + var pair = "'" + attr.name + "':(" + attr.val + ')'; + buf.push(pair); + } + }); + + if (classes.length) { + classes = classes.join(" + ' ' + "); + buf.push("class: " + classes); + } + + buf = buf.join(', ').replace('class:', '"class":'); + + this.buf.push("buf.push(attrs({ " + buf + " }));"); + } +}; + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api private + */ + +function escape(html){ + return String(html) + .replace(/&(?!\w+;)/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} diff --git a/node_modules/jade/lib/doctypes.js b/node_modules/jade/lib/doctypes.js new file mode 100644 index 0000000..feeb560 --- /dev/null +++ b/node_modules/jade/lib/doctypes.js @@ -0,0 +1,18 @@ + +/*! + * Jade - doctypes + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +module.exports = { + '5': '' + , 'xml': '' + , 'default': '' + , 'transitional': '' + , 'strict': '' + , 'frameset': '' + , '1.1': '' + , 'basic': '' + , 'mobile': '' +}; \ No newline at end of file diff --git a/node_modules/jade/lib/filters.js b/node_modules/jade/lib/filters.js new file mode 100644 index 0000000..dd1b99d --- /dev/null +++ b/node_modules/jade/lib/filters.js @@ -0,0 +1,92 @@ + +/*! + * Jade - filters + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +module.exports = { + + /** + * Wrap text with CDATA block. + */ + + cdata: function(str){ + return ''; + }, + + /** + * Transform sass to css, wrapped in style tags. + */ + + sass: function(str){ + str = str.replace(/\\n/g, '\n'); + var sass = require('sass').render(str).replace(/\n/g, '\\n'); + return ''; + }, + + /** + * Transform stylus to css, wrapped in style tags. + */ + + stylus: function(str, options){ + var ret; + str = str.replace(/\\n/g, '\n'); + var stylus = require('stylus'); + stylus(str, options).render(function(err, css){ + if (err) throw err; + ret = css.replace(/\n/g, '\\n'); + }); + return ''; + }, + + /** + * Transform less to css, wrapped in style tags. + */ + + less: function(str){ + var ret; + str = str.replace(/\\n/g, '\n'); + require('less').render(str, function(err, css){ + if (err) throw err; + ret = ''; + }); + return ret; + }, + + /** + * Transform markdown to html. + */ + + markdown: function(str){ + var md; + + // support markdown / discount + try { + md = require('markdown'); + } catch (err){ + try { + md = require('discount'); + } catch (err) { + try { + md = require('markdown-js'); + } catch (err) { + throw new Error('Cannot find markdown library, install markdown or discount'); + } + } + } + + str = str.replace(/\\n/g, '\n'); + return md.parse(str).replace(/\n/g, '\\n').replace(/'/g,'''); + }, + + /** + * Transform coffeescript to javascript. + */ + + coffeescript: function(str){ + str = str.replace(/\\n/g, '\n'); + var js = require('coffee-script').compile(str).replace(/\n/g, '\\n'); + return ''; + } +}; diff --git a/node_modules/jade/lib/index.js b/node_modules/jade/lib/index.js new file mode 120000 index 0000000..6a783c2 --- /dev/null +++ b/node_modules/jade/lib/index.js @@ -0,0 +1 @@ +jade.js \ No newline at end of file diff --git a/node_modules/jade/lib/inline-tags.js b/node_modules/jade/lib/inline-tags.js new file mode 100644 index 0000000..491de0b --- /dev/null +++ b/node_modules/jade/lib/inline-tags.js @@ -0,0 +1,28 @@ + +/*! + * Jade - inline tags + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +module.exports = [ + 'a' + , 'abbr' + , 'acronym' + , 'b' + , 'br' + , 'code' + , 'em' + , 'font' + , 'i' + , 'img' + , 'ins' + , 'kbd' + , 'map' + , 'samp' + , 'small' + , 'span' + , 'strong' + , 'sub' + , 'sup' +]; \ No newline at end of file diff --git a/node_modules/jade/lib/jade.js b/node_modules/jade/lib/jade.js new file mode 100644 index 0000000..45a568a --- /dev/null +++ b/node_modules/jade/lib/jade.js @@ -0,0 +1,238 @@ + +/*! + * Jade + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Parser = require('./parser') + , Lexer = require('./lexer') + , Compiler = require('./compiler') + , runtime = require('./runtime') +// if node + , fs = require('fs'); +// end + +/** + * Library version. + */ + +exports.version = '0.20.0'; + +/** + * Expose self closing tags. + */ + +exports.selfClosing = require('./self-closing'); + +/** + * Default supported doctypes. + */ + +exports.doctypes = require('./doctypes'); + +/** + * Text filters. + */ + +exports.filters = require('./filters'); + +/** + * Utilities. + */ + +exports.utils = require('./utils'); + +/** + * Expose `Compiler`. + */ + +exports.Compiler = Compiler; + +/** + * Expose `Parser`. + */ + +exports.Parser = Parser; + +/** + * Expose `Lexer`. + */ + +exports.Lexer = Lexer; + +/** + * Nodes. + */ + +exports.nodes = require('./nodes'); + +/** + * Jade runtime helpers. + */ + +exports.runtime = runtime; + +/** + * Template function cache. + */ + +exports.cache = {}; + +/** + * Parse the given `str` of jade and return a function body. + * + * @param {String} str + * @param {Object} options + * @return {String} + * @api private + */ + +function parse(str, options){ + try { + // Parse + var parser = new Parser(str, options.filename, options); + + // Compile + var compiler = new (options.compiler || Compiler)(parser.parse(), options) + , js = compiler.compile(); + + // Debug compiler + if (options.debug) { + console.error('\nCompiled Function:\n\n\033[90m%s\033[0m', js.replace(/^/gm, ' ')); + } + + return '' + + 'var buf = [];\n' + + (options.self + ? 'var self = locals || {};\n' + js + : 'with (locals || {}) {\n' + js + '\n}\n') + + 'return buf.join("");'; + } catch (err) { + parser = parser.context(); + runtime.rethrow(err, parser.filename, parser.lexer.lineno); + } +} + +/** + * Compile a `Function` representation of the given jade `str`. + * + * Options: + * + * - `compileDebug` when `false` debugging code is stripped from the compiled template + * - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()` + * for use with the Jade client-side runtime.js + * + * @param {String} str + * @param {Options} options + * @return {Function} + * @api public + */ + +exports.compile = function(str, options){ + var options = options || {} + , client = options.client + , filename = options.filename + ? JSON.stringify(options.filename) + : 'undefined' + , fn; + + if (options.compileDebug !== false) { + fn = [ + 'var __jade = [{ lineno: 1, filename: ' + filename + ' }];' + , 'try {' + , parse(String(str), options || {}) + , '} catch (err) {' + , ' rethrow(err, __jade[0].filename, __jade[0].lineno);' + , '}' + ].join('\n'); + } else { + fn = parse(String(str), options || {}); + } + + if (client) { + fn = 'var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;\n' + fn; + } + + fn = new Function('locals, attrs, escape, rethrow', fn); + + if (client) return fn; + + return function(locals){ + return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow); + }; +}; + +/** + * Render the given `str` of jade and invoke + * the callback `fn(err, str)`. + * + * Options: + * + * - `cache` enable template caching + * - `filename` filename required for `include` / `extends` and caching + * + * @param {String} str + * @param {Object|Function} options or fn + * @param {Function} fn + * @api public + */ + +exports.render = function(str, options, fn){ + // swap args + if ('function' == typeof options) { + fn = options, options = {}; + } + + // cache requires .filename + if (options.cache && !options.filename) { + return fn(new Error('the "filename" option is required for caching')); + } + + try { + var path = options.filename; + var tmpl = options.cache + ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options)) + : exports.compile(str, options); + fn(null, tmpl(options)); + } catch (err) { + fn(err); + } +}; + +/** + * Render a Jade file at the given `path` and callback `fn(err, str)`. + * + * @param {String} path + * @param {Object|Function} options or callback + * @param {Function} fn + * @api public + */ + +exports.renderFile = function(path, options, fn){ + var key = path + ':string'; + + if ('function' == typeof options) { + fn = options, options = {}; + } + + try { + options.filename = path; + var str = options.cache + ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8')) + : fs.readFileSync(path, 'utf8'); + exports.render(str, options, fn); + } catch (err) { + fn(err); + } +}; + +/** + * Express support. + */ + +exports.__express = exports.renderFile; diff --git a/node_modules/jade/lib/lexer.js b/node_modules/jade/lib/lexer.js new file mode 100644 index 0000000..aac94f5 --- /dev/null +++ b/node_modules/jade/lib/lexer.js @@ -0,0 +1,707 @@ + +/*! + * Jade - Lexer + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Initialize `Lexer` with the given `str`. + * + * Options: + * + * - `colons` allow colons for attr delimiters + * + * @param {String} str + * @param {Object} options + * @api private + */ + +var Lexer = module.exports = function Lexer(str, options) { + options = options || {}; + this.input = str.replace(/\r\n|\r/g, '\n'); + this.colons = options.colons; + this.deferredTokens = []; + this.lastIndents = 0; + this.lineno = 1; + this.stash = []; + this.indentStack = []; + this.indentRe = null; + this.pipeless = false; +}; + +/** + * Lexer prototype. + */ + +Lexer.prototype = { + + /** + * Construct a token with the given `type` and `val`. + * + * @param {String} type + * @param {String} val + * @return {Object} + * @api private + */ + + tok: function(type, val){ + return { + type: type + , line: this.lineno + , val: val + } + }, + + /** + * Consume the given `len` of input. + * + * @param {Number} len + * @api private + */ + + consume: function(len){ + this.input = this.input.substr(len); + }, + + /** + * Scan for `type` with the given `regexp`. + * + * @param {String} type + * @param {RegExp} regexp + * @return {Object} + * @api private + */ + + scan: function(regexp, type){ + var captures; + if (captures = regexp.exec(this.input)) { + this.consume(captures[0].length); + return this.tok(type, captures[1]); + } + }, + + /** + * Defer the given `tok`. + * + * @param {Object} tok + * @api private + */ + + defer: function(tok){ + this.deferredTokens.push(tok); + }, + + /** + * Lookahead `n` tokens. + * + * @param {Number} n + * @return {Object} + * @api private + */ + + lookahead: function(n){ + var fetch = n - this.stash.length; + while (fetch-- > 0) this.stash.push(this.next()); + return this.stash[--n]; + }, + + /** + * Return the indexOf `start` / `end` delimiters. + * + * @param {String} start + * @param {String} end + * @return {Number} + * @api private + */ + + indexOfDelimiters: function(start, end){ + var str = this.input + , nstart = 0 + , nend = 0 + , pos = 0; + for (var i = 0, len = str.length; i < len; ++i) { + if (start == str[i]) { + ++nstart; + } else if (end == str[i]) { + if (++nend == nstart) { + pos = i; + break; + } + } + } + return pos; + }, + + /** + * Stashed token. + */ + + stashed: function() { + return this.stash.length + && this.stash.shift(); + }, + + /** + * Deferred token. + */ + + deferred: function() { + return this.deferredTokens.length + && this.deferredTokens.shift(); + }, + + /** + * end-of-source. + */ + + eos: function() { + if (this.input.length) return; + if (this.indentStack.length) { + this.indentStack.shift(); + return this.tok('outdent'); + } else { + return this.tok('eos'); + } + }, + + /** + * Comment. + */ + + comment: function() { + var captures; + if (captures = /^ *\/\/(-)?([^\n]*)/.exec(this.input)) { + this.consume(captures[0].length); + var tok = this.tok('comment', captures[2]); + tok.buffer = '-' != captures[1]; + return tok; + } + }, + + /** + * Tag. + */ + + tag: function() { + var captures; + if (captures = /^(\w[-:\w]*)/.exec(this.input)) { + this.consume(captures[0].length); + var tok, name = captures[1]; + if (':' == name[name.length - 1]) { + name = name.slice(0, -1); + tok = this.tok('tag', name); + this.defer(this.tok(':')); + while (' ' == this.input[0]) this.input = this.input.substr(1); + } else { + tok = this.tok('tag', name); + } + return tok; + } + }, + + /** + * Filter. + */ + + filter: function() { + return this.scan(/^:(\w+)/, 'filter'); + }, + + /** + * Doctype. + */ + + doctype: function() { + return this.scan(/^(?:!!!|doctype) *([^\n]+)?/, 'doctype'); + }, + + /** + * Id. + */ + + id: function() { + return this.scan(/^#([\w-]+)/, 'id'); + }, + + /** + * Class. + */ + + className: function() { + return this.scan(/^\.([\w-]+)/, 'class'); + }, + + /** + * Text. + */ + + text: function() { + return this.scan(/^(?:\| ?)?([^\n]+)/, 'text'); + }, + + /** + * Extends. + */ + + extends: function() { + return this.scan(/^extends +([^\n]+)/, 'extends'); + }, + + /** + * Block prepend. + */ + + prepend: function() { + var captures; + if (captures = /^prepend +([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var mode = 'prepend' + , name = captures[1] + , tok = this.tok('block', name); + tok.mode = mode; + return tok; + } + }, + + /** + * Block append. + */ + + append: function() { + var captures; + if (captures = /^append +([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var mode = 'append' + , name = captures[1] + , tok = this.tok('block', name); + tok.mode = mode; + return tok; + } + }, + + /** + * Block. + */ + + block: function() { + var captures; + if (captures = /^block +(?:(prepend|append) +)?([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var mode = captures[1] || 'replace' + , name = captures[2] + , tok = this.tok('block', name); + tok.mode = mode; + return tok; + } + }, + + /** + * Yield. + */ + + yield: function() { + return this.scan(/^yield */, 'yield'); + }, + + /** + * Include. + */ + + include: function() { + return this.scan(/^include +([^\n]+)/, 'include'); + }, + + /** + * Case. + */ + + case: function() { + return this.scan(/^case +([^\n]+)/, 'case'); + }, + + /** + * When. + */ + + when: function() { + return this.scan(/^when +([^:\n]+)/, 'when'); + }, + + /** + * Default. + */ + + default: function() { + return this.scan(/^default */, 'default'); + }, + + /** + * Assignment. + */ + + assignment: function() { + var captures; + if (captures = /^(\w+) += *([^;\n]+)( *;? *)/.exec(this.input)) { + this.consume(captures[0].length); + var name = captures[1] + , val = captures[2]; + return this.tok('code', 'var ' + name + ' = (' + val + ');'); + } + }, + + /** + * Mixin. + */ + + mixin: function(){ + var captures; + if (captures = /^mixin +([-\w]+)(?: *\((.*)\))?/.exec(this.input)) { + this.consume(captures[0].length); + var tok = this.tok('mixin', captures[1]); + tok.args = captures[2]; + return tok; + } + }, + + /** + * Conditional. + */ + + conditional: function() { + var captures; + if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) { + this.consume(captures[0].length); + var type = captures[1] + , js = captures[2]; + + switch (type) { + case 'if': js = 'if (' + js + ')'; break; + case 'unless': js = 'if (!(' + js + '))'; break; + case 'else if': js = 'else if (' + js + ')'; break; + case 'else': js = 'else'; break; + } + + return this.tok('code', js); + } + }, + + /** + * While. + */ + + while: function() { + var captures; + if (captures = /^while +([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + return this.tok('code', 'while (' + captures[1] + ')'); + } + }, + + /** + * Each. + */ + + each: function() { + var captures; + if (captures = /^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var tok = this.tok('each', captures[1]); + tok.key = captures[2] || '$index'; + tok.code = captures[3]; + return tok; + } + }, + + /** + * Code. + */ + + code: function() { + var captures; + if (captures = /^(!?=|-)([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var flags = captures[1]; + captures[1] = captures[2]; + var tok = this.tok('code', captures[1]); + tok.escape = flags[0] === '='; + tok.buffer = flags[0] === '=' || flags[1] === '='; + return tok; + } + }, + + /** + * Attributes. + */ + + attrs: function() { + if ('(' == this.input[0]) { + var index = this.indexOfDelimiters('(', ')') + , str = this.input.substr(1, index-1) + , tok = this.tok('attrs') + , len = str.length + , colons = this.colons + , states = ['key'] + , key = '' + , val = '' + , quote + , c; + + function state(){ + return states[states.length - 1]; + } + + function interpolate(attr) { + return attr.replace(/#\{([^}]+)\}/g, function(_, expr){ + return quote + " + (" + expr + ") + " + quote; + }); + } + + this.consume(index + 1); + tok.attrs = {}; + + function parse(c) { + var real = c; + // TODO: remove when people fix ":" + if (colons && ':' == c) c = '='; + switch (c) { + case ',': + case '\n': + switch (state()) { + case 'expr': + case 'array': + case 'string': + case 'object': + val += c; + break; + default: + states.push('key'); + val = val.trim(); + key = key.trim(); + if ('' == key) return; + tok.attrs[key.replace(/^['"]|['"]$/g, '')] = '' == val + ? true + : interpolate(val); + key = val = ''; + } + break; + case '=': + switch (state()) { + case 'key char': + key += real; + break; + case 'val': + case 'expr': + case 'array': + case 'string': + case 'object': + val += real; + break; + default: + states.push('val'); + } + break; + case '(': + if ('val' == state() + || 'expr' == state()) states.push('expr'); + val += c; + break; + case ')': + if ('expr' == state() + || 'val' == state()) states.pop(); + val += c; + break; + case '{': + if ('val' == state()) states.push('object'); + val += c; + break; + case '}': + if ('object' == state()) states.pop(); + val += c; + break; + case '[': + if ('val' == state()) states.push('array'); + val += c; + break; + case ']': + if ('array' == state()) states.pop(); + val += c; + break; + case '"': + case "'": + switch (state()) { + case 'key': + states.push('key char'); + break; + case 'key char': + states.pop(); + break; + case 'string': + if (c == quote) states.pop(); + val += c; + break; + default: + states.push('string'); + val += c; + quote = c; + } + break; + case '': + break; + default: + switch (state()) { + case 'key': + case 'key char': + key += c; + break; + default: + val += c; + } + } + } + + for (var i = 0; i < len; ++i) { + parse(str[i]); + } + + parse(','); + + return tok; + } + }, + + /** + * Indent | Outdent | Newline. + */ + + indent: function() { + var captures, re; + + // established regexp + if (this.indentRe) { + captures = this.indentRe.exec(this.input); + // determine regexp + } else { + // tabs + re = /^\n(\t*) */; + captures = re.exec(this.input); + + // spaces + if (captures && !captures[1].length) { + re = /^\n( *)/; + captures = re.exec(this.input); + } + + // established + if (captures && captures[1].length) this.indentRe = re; + } + + if (captures) { + var tok + , indents = captures[1].length; + + ++this.lineno; + this.consume(indents + 1); + + if (' ' == this.input[0] || '\t' == this.input[0]) { + throw new Error('Invalid indentation, you can use tabs or spaces but not both'); + } + + // blank line + if ('\n' == this.input[0]) return this.tok('newline'); + + // outdent + if (this.indentStack.length && indents < this.indentStack[0]) { + while (this.indentStack.length && this.indentStack[0] > indents) { + this.stash.push(this.tok('outdent')); + this.indentStack.shift(); + } + tok = this.stash.pop(); + // indent + } else if (indents && indents != this.indentStack[0]) { + this.indentStack.unshift(indents); + tok = this.tok('indent', indents); + // newline + } else { + tok = this.tok('newline'); + } + + return tok; + } + }, + + /** + * Pipe-less text consumed only when + * pipeless is true; + */ + + pipelessText: function() { + if (this.pipeless) { + if ('\n' == this.input[0]) return; + var i = this.input.indexOf('\n'); + if (-1 == i) i = this.input.length; + var str = this.input.substr(0, i); + this.consume(str.length); + return this.tok('text', str); + } + }, + + /** + * ':' + */ + + colon: function() { + return this.scan(/^: */, ':'); + }, + + /** + * Return the next token object, or those + * previously stashed by lookahead. + * + * @return {Object} + * @api private + */ + + advance: function(){ + return this.stashed() + || this.next(); + }, + + /** + * Return the next token object. + * + * @return {Object} + * @api private + */ + + next: function() { + return this.deferred() + || this.eos() + || this.pipelessText() + || this.yield() + || this.doctype() + || this.case() + || this.when() + || this.default() + || this.extends() + || this.append() + || this.prepend() + || this.block() + || this.include() + || this.mixin() + || this.conditional() + || this.each() + || this.while() + || this.assignment() + || this.tag() + || this.filter() + || this.code() + || this.id() + || this.className() + || this.attrs() + || this.indent() + || this.comment() + || this.colon() + || this.text(); + } +}; diff --git a/node_modules/jade/lib/nodes/block-comment.js b/node_modules/jade/lib/nodes/block-comment.js new file mode 100644 index 0000000..4f41e4a --- /dev/null +++ b/node_modules/jade/lib/nodes/block-comment.js @@ -0,0 +1,33 @@ + +/*! + * Jade - nodes - BlockComment + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `BlockComment` with the given `block`. + * + * @param {String} val + * @param {Block} block + * @param {Boolean} buffer + * @api public + */ + +var BlockComment = module.exports = function BlockComment(val, block, buffer) { + this.block = block; + this.val = val; + this.buffer = buffer; +}; + +/** + * Inherit from `Node`. + */ + +BlockComment.prototype.__proto__ = Node.prototype; \ No newline at end of file diff --git a/node_modules/jade/lib/nodes/block.js b/node_modules/jade/lib/nodes/block.js new file mode 100644 index 0000000..d3f2343 --- /dev/null +++ b/node_modules/jade/lib/nodes/block.js @@ -0,0 +1,99 @@ + +/*! + * Jade - nodes - Block + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a new `Block` with an optional `node`. + * + * @param {Node} node + * @api public + */ + +var Block = module.exports = function Block(node){ + this.nodes = []; + if (node) this.push(node); +}; + +/** + * Inherit from `Node`. + */ + +Block.prototype.__proto__ = Node.prototype; + +/** + * Replace the nodes in `other` with the nodes + * in `this` block. + * + * @param {Block} other + * @api private + */ + +Block.prototype.replace = function(other){ + other.nodes = this.nodes; +}; + +/** + * Pust the given `node`. + * + * @param {Node} node + * @return {Number} + * @api public + */ + +Block.prototype.push = function(node){ + return this.nodes.push(node); +}; + +/** + * Check if this block is empty. + * + * @return {Boolean} + * @api public + */ + +Block.prototype.isEmpty = function(){ + return 0 == this.nodes.length; +}; + +/** + * Unshift the given `node`. + * + * @param {Node} node + * @return {Number} + * @api public + */ + +Block.prototype.unshift = function(node){ + return this.nodes.unshift(node); +}; + +/** + * Return the "last" block, or the first `yield` node. + * + * @return {Block} + * @api private + */ + +Block.prototype.includeBlock = function(){ + var ret = this + , node; + + for (var i = 0, len = this.nodes.length; i < len; ++i) { + node = this.nodes[i]; + if (node.yield) return node; + else if (node.includeBlock) ret = node.includeBlock(); + else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock(); + } + + return ret; +}; + diff --git a/node_modules/jade/lib/nodes/case.js b/node_modules/jade/lib/nodes/case.js new file mode 100644 index 0000000..08ff033 --- /dev/null +++ b/node_modules/jade/lib/nodes/case.js @@ -0,0 +1,43 @@ + +/*! + * Jade - nodes - Case + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a new `Case` with `expr`. + * + * @param {String} expr + * @api public + */ + +var Case = exports = module.exports = function Case(expr, block){ + this.expr = expr; + this.block = block; +}; + +/** + * Inherit from `Node`. + */ + +Case.prototype.__proto__ = Node.prototype; + +var When = exports.When = function When(expr, block){ + this.expr = expr; + this.block = block; + this.debug = false; +}; + +/** + * Inherit from `Node`. + */ + +When.prototype.__proto__ = Node.prototype; + diff --git a/node_modules/jade/lib/nodes/code.js b/node_modules/jade/lib/nodes/code.js new file mode 100644 index 0000000..babc675 --- /dev/null +++ b/node_modules/jade/lib/nodes/code.js @@ -0,0 +1,35 @@ + +/*! + * Jade - nodes - Code + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Code` node with the given code `val`. + * Code may also be optionally buffered and escaped. + * + * @param {String} val + * @param {Boolean} buffer + * @param {Boolean} escape + * @api public + */ + +var Code = module.exports = function Code(val, buffer, escape) { + this.val = val; + this.buffer = buffer; + this.escape = escape; + if (val.match(/^ *else/)) this.debug = false; +}; + +/** + * Inherit from `Node`. + */ + +Code.prototype.__proto__ = Node.prototype; \ No newline at end of file diff --git a/node_modules/jade/lib/nodes/comment.js b/node_modules/jade/lib/nodes/comment.js new file mode 100644 index 0000000..2e1469e --- /dev/null +++ b/node_modules/jade/lib/nodes/comment.js @@ -0,0 +1,32 @@ + +/*! + * Jade - nodes - Comment + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Comment` with the given `val`, optionally `buffer`, + * otherwise the comment may render in the output. + * + * @param {String} val + * @param {Boolean} buffer + * @api public + */ + +var Comment = module.exports = function Comment(val, buffer) { + this.val = val; + this.buffer = buffer; +}; + +/** + * Inherit from `Node`. + */ + +Comment.prototype.__proto__ = Node.prototype; \ No newline at end of file diff --git a/node_modules/jade/lib/nodes/doctype.js b/node_modules/jade/lib/nodes/doctype.js new file mode 100644 index 0000000..b8f33e5 --- /dev/null +++ b/node_modules/jade/lib/nodes/doctype.js @@ -0,0 +1,29 @@ + +/*! + * Jade - nodes - Doctype + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Doctype` with the given `val`. + * + * @param {String} val + * @api public + */ + +var Doctype = module.exports = function Doctype(val) { + this.val = val; +}; + +/** + * Inherit from `Node`. + */ + +Doctype.prototype.__proto__ = Node.prototype; \ No newline at end of file diff --git a/node_modules/jade/lib/nodes/each.js b/node_modules/jade/lib/nodes/each.js new file mode 100644 index 0000000..f54101f --- /dev/null +++ b/node_modules/jade/lib/nodes/each.js @@ -0,0 +1,35 @@ + +/*! + * Jade - nodes - Each + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize an `Each` node, representing iteration + * + * @param {String} obj + * @param {String} val + * @param {String} key + * @param {Block} block + * @api public + */ + +var Each = module.exports = function Each(obj, val, key, block) { + this.obj = obj; + this.val = val; + this.key = key; + this.block = block; +}; + +/** + * Inherit from `Node`. + */ + +Each.prototype.__proto__ = Node.prototype; \ No newline at end of file diff --git a/node_modules/jade/lib/nodes/filter.js b/node_modules/jade/lib/nodes/filter.js new file mode 100644 index 0000000..5a0a237 --- /dev/null +++ b/node_modules/jade/lib/nodes/filter.js @@ -0,0 +1,35 @@ + +/*! + * Jade - nodes - Filter + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node') + , Block = require('./block'); + +/** + * Initialize a `Filter` node with the given + * filter `name` and `block`. + * + * @param {String} name + * @param {Block|Node} block + * @api public + */ + +var Filter = module.exports = function Filter(name, block, attrs) { + this.name = name; + this.block = block; + this.attrs = attrs; + this.isASTFilter = block instanceof Block; +}; + +/** + * Inherit from `Node`. + */ + +Filter.prototype.__proto__ = Node.prototype; \ No newline at end of file diff --git a/node_modules/jade/lib/nodes/index.js b/node_modules/jade/lib/nodes/index.js new file mode 100644 index 0000000..386ad2f --- /dev/null +++ b/node_modules/jade/lib/nodes/index.js @@ -0,0 +1,20 @@ + +/*! + * Jade - nodes + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +exports.Node = require('./node'); +exports.Tag = require('./tag'); +exports.Code = require('./code'); +exports.Each = require('./each'); +exports.Case = require('./case'); +exports.Text = require('./text'); +exports.Block = require('./block'); +exports.Mixin = require('./mixin'); +exports.Filter = require('./filter'); +exports.Comment = require('./comment'); +exports.Literal = require('./literal'); +exports.BlockComment = require('./block-comment'); +exports.Doctype = require('./doctype'); diff --git a/node_modules/jade/lib/nodes/literal.js b/node_modules/jade/lib/nodes/literal.js new file mode 100644 index 0000000..3ddab65 --- /dev/null +++ b/node_modules/jade/lib/nodes/literal.js @@ -0,0 +1,31 @@ + +/*! + * Jade - nodes - Literal + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Literal` node with the given `str. + * + * @param {String} str + * @api public + */ + +var Literal = module.exports = function Literal(str) { + this.str = str + .replace(/\n/g, "\\n") + .replace(/'/g, "\\'"); +}; + +/** + * Inherit from `Node`. + */ + +Literal.prototype.__proto__ = Node.prototype; diff --git a/node_modules/jade/lib/nodes/mixin.js b/node_modules/jade/lib/nodes/mixin.js new file mode 100644 index 0000000..f007c84 --- /dev/null +++ b/node_modules/jade/lib/nodes/mixin.js @@ -0,0 +1,34 @@ + +/*! + * Jade - nodes - Mixin + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a new `Mixin` with `name` and `block`. + * + * @param {String} name + * @param {String} args + * @param {Block} block + * @api public + */ + +var Mixin = module.exports = function Mixin(name, args, block){ + this.name = name; + this.args = args; + this.block = block; +}; + +/** + * Inherit from `Node`. + */ + +Mixin.prototype.__proto__ = Node.prototype; + diff --git a/node_modules/jade/lib/nodes/node.js b/node_modules/jade/lib/nodes/node.js new file mode 100644 index 0000000..0669e67 --- /dev/null +++ b/node_modules/jade/lib/nodes/node.js @@ -0,0 +1,14 @@ + +/*! + * Jade - nodes - Node + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Initialize a `Node`. + * + * @api public + */ + +var Node = module.exports = function Node(){}; \ No newline at end of file diff --git a/node_modules/jade/lib/nodes/tag.js b/node_modules/jade/lib/nodes/tag.js new file mode 100644 index 0000000..35993c9 --- /dev/null +++ b/node_modules/jade/lib/nodes/tag.js @@ -0,0 +1,80 @@ + +/*! + * Jade - nodes - Tag + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'), + Block = require('./block'); + +/** + * Initialize a `Tag` node with the given tag `name` and optional `block`. + * + * @param {String} name + * @param {Block} block + * @api public + */ + +var Tag = module.exports = function Tag(name, block) { + this.name = name; + this.attrs = []; + this.block = block || new Block; +}; + +/** + * Inherit from `Node`. + */ + +Tag.prototype.__proto__ = Node.prototype; + +/** + * Set attribute `name` to `val`, keep in mind these become + * part of a raw js object literal, so to quote a value you must + * '"quote me"', otherwise or example 'user.name' is literal JavaScript. + * + * @param {String} name + * @param {String} val + * @return {Tag} for chaining + * @api public + */ + +Tag.prototype.setAttribute = function(name, val){ + this.attrs.push({ name: name, val: val }); + return this; +}; + +/** + * Remove attribute `name` when present. + * + * @param {String} name + * @api public + */ + +Tag.prototype.removeAttribute = function(name){ + for (var i = 0, len = this.attrs.length; i < len; ++i) { + if (this.attrs[i] && this.attrs[i].name == name) { + delete this.attrs[i]; + } + } +}; + +/** + * Get attribute value by `name`. + * + * @param {String} name + * @return {String} + * @api public + */ + +Tag.prototype.getAttribute = function(name){ + for (var i = 0, len = this.attrs.length; i < len; ++i) { + if (this.attrs[i] && this.attrs[i].name == name) { + return this.attrs[i].val; + } + } +}; diff --git a/node_modules/jade/lib/nodes/text.js b/node_modules/jade/lib/nodes/text.js new file mode 100644 index 0000000..3baff4b --- /dev/null +++ b/node_modules/jade/lib/nodes/text.js @@ -0,0 +1,42 @@ + +/*! + * Jade - nodes - Text + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Text` node with optional `line`. + * + * @param {String} line + * @api public + */ + +var Text = module.exports = function Text(line) { + this.nodes = []; + if ('string' == typeof line) this.push(line); +}; + +/** + * Inherit from `Node`. + */ + +Text.prototype.__proto__ = Node.prototype; + +/** + * Push the given `node.` + * + * @param {Node} node + * @return {Number} + * @api public + */ + +Text.prototype.push = function(node){ + return this.nodes.push(node); +}; diff --git a/node_modules/jade/lib/parser.js b/node_modules/jade/lib/parser.js new file mode 100644 index 0000000..3dcfb57 --- /dev/null +++ b/node_modules/jade/lib/parser.js @@ -0,0 +1,651 @@ + +/*! + * Jade - Parser + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Lexer = require('./lexer') + , nodes = require('./nodes'); + +/** + * Initialize `Parser` with the given input `str` and `filename`. + * + * @param {String} str + * @param {String} filename + * @param {Object} options + * @api public + */ + +var Parser = exports = module.exports = function Parser(str, filename, options){ + this.input = str; + this.lexer = new Lexer(str, options); + this.filename = filename; + this.blocks = {}; + this.options = options; + this.contexts = [this]; +}; + +/** + * Tags that may not contain tags. + */ + +var textOnly = exports.textOnly = ['script', 'style']; + +/** + * Parser prototype. + */ + +Parser.prototype = { + + /** + * Push `parser` onto the context stack, + * or pop and return a `Parser`. + */ + + context: function(parser){ + if (parser) { + this.contexts.push(parser); + } else { + return this.contexts.pop(); + } + }, + + /** + * Return the next token object. + * + * @return {Object} + * @api private + */ + + advance: function(){ + return this.lexer.advance(); + }, + + /** + * Skip `n` tokens. + * + * @param {Number} n + * @api private + */ + + skip: function(n){ + while (n--) this.advance(); + }, + + /** + * Single token lookahead. + * + * @return {Object} + * @api private + */ + + peek: function() { + return this.lookahead(1); + }, + + /** + * Return lexer lineno. + * + * @return {Number} + * @api private + */ + + line: function() { + return this.lexer.lineno; + }, + + /** + * `n` token lookahead. + * + * @param {Number} n + * @return {Object} + * @api private + */ + + lookahead: function(n){ + return this.lexer.lookahead(n); + }, + + /** + * Parse input returning a string of js for evaluation. + * + * @return {String} + * @api public + */ + + parse: function(){ + var block = new nodes.Block, parser; + block.line = this.line(); + + while ('eos' != this.peek().type) { + if ('newline' == this.peek().type) { + this.advance(); + } else { + block.push(this.parseExpr()); + } + } + + if (parser = this.extending) { + this.context(parser); + var ast = parser.parse(); + this.context(); + return ast; + } + + return block; + }, + + /** + * Expect the given type, or throw an exception. + * + * @param {String} type + * @api private + */ + + expect: function(type){ + if (this.peek().type === type) { + return this.advance(); + } else { + throw new Error('expected "' + type + '", but got "' + this.peek().type + '"'); + } + }, + + /** + * Accept the given `type`. + * + * @param {String} type + * @api private + */ + + accept: function(type){ + if (this.peek().type === type) { + return this.advance(); + } + }, + + /** + * tag + * | doctype + * | mixin + * | include + * | filter + * | comment + * | text + * | each + * | code + * | yield + * | id + * | class + */ + + parseExpr: function(){ + switch (this.peek().type) { + case 'tag': + return this.parseTag(); + case 'mixin': + return this.parseMixin(); + case 'block': + return this.parseBlock(); + case 'case': + return this.parseCase(); + case 'when': + return this.parseWhen(); + case 'default': + return this.parseDefault(); + case 'extends': + return this.parseExtends(); + case 'include': + return this.parseInclude(); + case 'doctype': + return this.parseDoctype(); + case 'filter': + return this.parseFilter(); + case 'comment': + return this.parseComment(); + case 'text': + return this.parseText(); + case 'each': + return this.parseEach(); + case 'code': + return this.parseCode(); + case 'yield': + this.advance(); + var block = new nodes.Block; + block.yield = true; + return block; + case 'id': + case 'class': + var tok = this.advance(); + this.lexer.defer(this.lexer.tok('tag', 'div')); + this.lexer.defer(tok); + return this.parseExpr(); + default: + throw new Error('unexpected token "' + this.peek().type + '"'); + } + }, + + /** + * Text + */ + + parseText: function(){ + var tok = this.expect('text') + , node = new nodes.Text(tok.val); + node.line = this.line(); + return node; + }, + + /** + * ':' expr + * | block + */ + + parseBlockExpansion: function(){ + if (':' == this.peek().type) { + this.advance(); + return new nodes.Block(this.parseExpr()); + } else { + return this.block(); + } + }, + + /** + * case + */ + + parseCase: function(){ + var val = this.expect('case').val + , node = new nodes.Case(val); + node.line = this.line(); + node.block = this.block(); + return node; + }, + + /** + * when + */ + + parseWhen: function(){ + var val = this.expect('when').val + return new nodes.Case.When(val, this.parseBlockExpansion()); + }, + + /** + * default + */ + + parseDefault: function(){ + this.expect('default'); + return new nodes.Case.When('default', this.parseBlockExpansion()); + }, + + /** + * code + */ + + parseCode: function(){ + var tok = this.expect('code') + , node = new nodes.Code(tok.val, tok.buffer, tok.escape) + , block + , i = 1; + node.line = this.line(); + while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i; + block = 'indent' == this.lookahead(i).type; + if (block) { + this.skip(i-1); + node.block = this.block(); + } + return node; + }, + + /** + * comment + */ + + parseComment: function(){ + var tok = this.expect('comment') + , node; + + if ('indent' == this.peek().type) { + node = new nodes.BlockComment(tok.val, this.block(), tok.buffer); + } else { + node = new nodes.Comment(tok.val, tok.buffer); + } + + node.line = this.line(); + return node; + }, + + /** + * doctype + */ + + parseDoctype: function(){ + var tok = this.expect('doctype') + , node = new nodes.Doctype(tok.val); + node.line = this.line(); + return node; + }, + + /** + * filter attrs? text-block + */ + + parseFilter: function(){ + var block + , tok = this.expect('filter') + , attrs = this.accept('attrs'); + + this.lexer.pipeless = true; + block = this.parseTextBlock(); + this.lexer.pipeless = false; + + var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs); + node.line = this.line(); + return node; + }, + + /** + * tag ':' attrs? block + */ + + parseASTFilter: function(){ + var block + , tok = this.expect('tag') + , attrs = this.accept('attrs'); + + this.expect(':'); + block = this.block(); + + var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs); + node.line = this.line(); + return node; + }, + + /** + * each block + */ + + parseEach: function(){ + var tok = this.expect('each') + , node = new nodes.Each(tok.code, tok.val, tok.key); + node.line = this.line(); + node.block = this.block(); + return node; + }, + + /** + * 'extends' name + */ + + parseExtends: function(){ + var path = require('path') + , fs = require('fs') + , dirname = path.dirname + , basename = path.basename + , join = path.join; + + if (!this.filename) + throw new Error('the "filename" option is required to extend templates'); + + var path = this.expect('extends').val.trim() + , dir = dirname(this.filename); + + var path = join(dir, path + '.jade') + , str = fs.readFileSync(path, 'utf8') + , parser = new Parser(str, path, this.options); + + parser.blocks = this.blocks; + parser.contexts = this.contexts; + this.extending = parser; + + // TODO: null node + return new nodes.Literal(''); + }, + + /** + * 'block' name block + */ + + parseBlock: function(){ + var block = this.expect('block') + , mode = block.mode + , name = block.val.trim(); + + block = 'indent' == this.peek().type + ? this.block() + : new nodes.Block(new nodes.Literal('')); + + var prev = this.blocks[name]; + + if (prev) { + switch (prev.mode) { + case 'append': + block.nodes = block.nodes.concat(prev.nodes); + prev = block; + break; + case 'prepend': + block.nodes = prev.nodes.concat(block.nodes); + prev = block; + break; + } + } + + block.mode = mode; + return this.blocks[name] = prev || block; + }, + + /** + * include block? + */ + + parseInclude: function(){ + var path = require('path') + , fs = require('fs') + , dirname = path.dirname + , basename = path.basename + , join = path.join; + + var path = this.expect('include').val.trim() + , dir = dirname(this.filename); + + if (!this.filename) + throw new Error('the "filename" option is required to use includes'); + + // no extension + if (!~basename(path).indexOf('.')) { + path += '.jade'; + } + + // non-jade + if ('.jade' != path.substr(-5)) { + var path = join(dir, path) + , str = fs.readFileSync(path, 'utf8'); + return new nodes.Literal(str); + } + + var path = join(dir, path) + , str = fs.readFileSync(path, 'utf8') + , parser = new Parser(str, path, this.options); + + this.context(parser); + var ast = parser.parse(); + this.context(); + ast.filename = path; + + if ('indent' == this.peek().type) { + ast.includeBlock().push(this.block()); + } + + return ast; + }, + + /** + * mixin block + */ + + parseMixin: function(){ + var tok = this.expect('mixin') + , name = tok.val + , args = tok.args; + var block = 'indent' == this.peek().type + ? this.block() + : null; + return new nodes.Mixin(name, args, block); + }, + + /** + * indent (text | newline)* outdent + */ + + parseTextBlock: function(){ + var text = new nodes.Text; + text.line = this.line(); + var spaces = this.expect('indent').val; + if (null == this._spaces) this._spaces = spaces; + var indent = Array(spaces - this._spaces + 1).join(' '); + while ('outdent' != this.peek().type) { + switch (this.peek().type) { + case 'newline': + text.push('\\n'); + this.advance(); + break; + case 'indent': + text.push('\\n'); + this.parseTextBlock().nodes.forEach(function(node){ + text.push(node); + }); + text.push('\\n'); + break; + default: + text.push(indent + this.advance().val); + } + } + + if (spaces == this._spaces) this._spaces = null; + this.expect('outdent'); + return text; + }, + + /** + * indent expr* outdent + */ + + block: function(){ + var block = new nodes.Block; + block.line = this.line(); + this.expect('indent'); + while ('outdent' != this.peek().type) { + if ('newline' == this.peek().type) { + this.advance(); + } else { + block.push(this.parseExpr()); + } + } + this.expect('outdent'); + return block; + }, + + /** + * tag (attrs | class | id)* (text | code | ':')? newline* block? + */ + + parseTag: function(){ + // ast-filter look-ahead + var i = 2; + if ('attrs' == this.lookahead(i).type) ++i; + if (':' == this.lookahead(i).type) { + if ('indent' == this.lookahead(++i).type) { + return this.parseASTFilter(); + } + } + + var name = this.advance().val + , tag = new nodes.Tag(name) + , dot; + + tag.line = this.line(); + + // (attrs | class | id)* + out: + while (true) { + switch (this.peek().type) { + case 'id': + case 'class': + var tok = this.advance(); + tag.setAttribute(tok.type, "'" + tok.val + "'"); + continue; + case 'attrs': + var obj = this.advance().attrs + , names = Object.keys(obj); + for (var i = 0, len = names.length; i < len; ++i) { + var name = names[i] + , val = obj[name]; + tag.setAttribute(name, val); + } + continue; + default: + break out; + } + } + + // check immediate '.' + if ('.' == this.peek().val) { + dot = tag.textOnly = true; + this.advance(); + } + + // (text | code | ':')? + switch (this.peek().type) { + case 'text': + tag.text = this.parseText(); + break; + case 'code': + tag.code = this.parseCode(); + break; + case ':': + this.advance(); + tag.block = new nodes.Block; + tag.block.push(this.parseTag()); + break; + } + + // newline* + while ('newline' == this.peek().type) this.advance(); + + tag.textOnly = tag.textOnly || ~textOnly.indexOf(tag.name); + + // script special-case + if ('script' == tag.name) { + var type = tag.getAttribute('type'); + if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) { + tag.textOnly = false; + } + } + + // block? + if ('indent' == this.peek().type) { + if (tag.textOnly) { + this.lexer.pipeless = true; + tag.block = this.parseTextBlock(); + this.lexer.pipeless = false; + } else { + var block = this.block(); + if (tag.block) { + for (var i = 0, len = block.nodes.length; i < len; ++i) { + tag.block.push(block.nodes[i]); + } + } else { + tag.block = block; + } + } + } + + return tag; + } +}; diff --git a/node_modules/jade/lib/runtime.js b/node_modules/jade/lib/runtime.js new file mode 100644 index 0000000..7b357ca --- /dev/null +++ b/node_modules/jade/lib/runtime.js @@ -0,0 +1,118 @@ + +/*! + * Jade - runtime + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Lame Array.isArray() polyfill for now. + */ + +if (!Array.isArray) { + Array.isArray = function(arr){ + return '[object Array]' == Object.prototype.toString.call(arr); + }; +} + +/** + * Lame Object.keys() polyfill for now. + */ + +if (!Object.keys) { + Object.keys = function(obj){ + var arr = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + arr.push(key); + } + } + return arr; + } +} + +/** + * Render the given attributes object. + * + * @param {Object} obj + * @return {String} + * @api private + */ + +exports.attrs = function attrs(obj){ + var buf = [] + , terse = obj.terse; + delete obj.terse; + var keys = Object.keys(obj) + , len = keys.length; + if (len) { + buf.push(''); + for (var i = 0; i < len; ++i) { + var key = keys[i] + , val = obj[key]; + if ('boolean' == typeof val || null == val) { + if (val) { + terse + ? buf.push(key) + : buf.push(key + '="' + key + '"'); + } + } else if ('class' == key && Array.isArray(val)) { + buf.push(key + '="' + exports.escape(val.join(' ')) + '"'); + } else { + buf.push(key + '="' + exports.escape(val) + '"'); + } + } + } + return buf.join(' '); +}; + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function escape(html){ + return String(html) + .replace(/&(?!\w+;)/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +}; + +/** + * Re-throw the given `err` in context to the + * the jade in `filename` at the given `lineno`. + * + * @param {Error} err + * @param {String} filename + * @param {String} lineno + * @api private + */ + +exports.rethrow = function rethrow(err, filename, lineno){ + if (!filename) throw err; + + var context = 3 + , str = require('fs').readFileSync(filename, 'utf8') + , lines = str.split('\n') + , start = Math.max(lineno - context, 0) + , end = Math.min(lines.length, lineno + context); + + // Error context + var context = lines.slice(start, end).map(function(line, i){ + var curr = i + start + 1; + return (curr == lineno ? ' > ' : ' ') + + curr + + '| ' + + line; + }).join('\n'); + + // Alter exception message + err.path = filename; + err.message = (filename || 'Jade') + ':' + lineno + + '\n' + context + '\n\n' + err.message; + throw err; +}; diff --git a/node_modules/jade/lib/self-closing.js b/node_modules/jade/lib/self-closing.js new file mode 100644 index 0000000..293e7f8 --- /dev/null +++ b/node_modules/jade/lib/self-closing.js @@ -0,0 +1,18 @@ + +/*! + * Jade - self closing tags + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +module.exports = [ + 'meta' + , 'img' + , 'link' + , 'input' + , 'area' + , 'base' + , 'col' + , 'br' + , 'hr' +]; \ No newline at end of file diff --git a/node_modules/jade/lib/utils.js b/node_modules/jade/lib/utils.js new file mode 100644 index 0000000..ff46d02 --- /dev/null +++ b/node_modules/jade/lib/utils.js @@ -0,0 +1,49 @@ + +/*! + * Jade - utils + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Convert interpolation in the given string to JavaScript. + * + * @param {String} str + * @return {String} + * @api private + */ + +var interpolate = exports.interpolate = function(str){ + return str.replace(/(\\)?([#!]){(.*?)}/g, function(str, escape, flag, code){ + return escape + ? str + : "' + " + + ('!' == flag ? '' : 'escape') + + "((interp = " + code.replace(/\\'/g, "'") + + ") == null ? '' : interp) + '"; + }); +}; + +/** + * Escape single quotes in `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +var escape = exports.escape = function(str) { + return str.replace(/'/g, "\\'"); +}; + +/** + * Interpolate, and escape the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.text = function(str){ + return interpolate(escape(str)); +}; \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/.gitignore b/node_modules/jade/node_modules/commander/.gitignore new file mode 100644 index 0000000..e0c8856 --- /dev/null +++ b/node_modules/jade/node_modules/commander/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +node_modules +*.sock diff --git a/node_modules/jade/node_modules/commander/.npmignore b/node_modules/jade/node_modules/commander/.npmignore new file mode 100644 index 0000000..f1250e5 --- /dev/null +++ b/node_modules/jade/node_modules/commander/.npmignore @@ -0,0 +1,4 @@ +support +test +examples +*.sock diff --git a/node_modules/jade/node_modules/commander/History.md b/node_modules/jade/node_modules/commander/History.md new file mode 100644 index 0000000..9d80834 --- /dev/null +++ b/node_modules/jade/node_modules/commander/History.md @@ -0,0 +1,42 @@ + +0.2.1 / 2011-10-24 +================== + + * "node": ">= 0.4.x < 0.7.0". Closes #20 + +0.2.0 / 2011-09-26 +================== + + * Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs] + +0.1.0 / 2011-08-24 +================== + + * Added support for custom `--help` output + +0.0.5 / 2011-08-18 +================== + + * Changed: when the user enters nothing prompt for password again + * Fixed issue with passwords beginning with numbers [NuckChorris] + +0.0.4 / 2011-08-15 +================== + + * Fixed `Commander#args` + +0.0.3 / 2011-08-15 +================== + + * Added default option value support + +0.0.2 / 2011-08-15 +================== + + * Added mask support to `Command#password(str[, mask], fn)` + * Added `Command#password(str, fn)` + +0.0.1 / 2010-01-03 +================== + + * Initial release diff --git a/node_modules/jade/node_modules/commander/Makefile b/node_modules/jade/node_modules/commander/Makefile new file mode 100644 index 0000000..0074625 --- /dev/null +++ b/node_modules/jade/node_modules/commander/Makefile @@ -0,0 +1,7 @@ + +TESTS = $(shell find test/test.*.js) + +test: + @./test/run $(TESTS) + +.PHONY: test \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/Readme.md b/node_modules/jade/node_modules/commander/Readme.md new file mode 100644 index 0000000..22909fa --- /dev/null +++ b/node_modules/jade/node_modules/commander/Readme.md @@ -0,0 +1,260 @@ + +# Commander.js + + The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/visionmedia/commander). + +## Installation + + $ npm install commander + +## Option parsing + + Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options. + +```js +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('commander'); + +program + .version('0.0.1') + .option('-p, --peppers', 'Add peppers') + .option('-P, --pineapple', 'Add pineapple') + .option('-b, --bbq', 'Add bbq sauce') + .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble') + .parse(process.argv); + +console.log('you ordered a pizza with:'); +if (program.peppers) console.log(' - peppers'); +if (program.pineapple) console.log(' - pineappe'); +if (program.bbq) console.log(' - bbq'); +console.log(' - %s cheese', program.cheese); +``` + + Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc. + +## Automated --help + + The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free: + +``` + $ ./examples/pizza --help + + Usage: pizza [options] + + Options: + + -v, --version output the version number + -p, --peppers Add peppers + -P, --pineapple Add pineappe + -b, --bbq Add bbq sauce + -c, --cheese Add the specified type of cheese [marble] + -h, --help output usage information + +``` + +## Coercion + +```js +function range(val) { + return val.split('..').map(Number); +} + +function list(val) { + return val.split(','); +} + +program + .version('0.0.1') + .option('-i, --integer ', 'An integer argument', parseInt) + .option('-f, --float ', 'A float argument', parseFloat) + .option('-r, --range ..', 'A range', range) + .option('-l, --list ', 'A list', list) + .option('-o, --optional [value]', 'An optional value') + .parse(process.argv); + +console.log(' int: %j', program.integer); +console.log(' float: %j', program.float); +console.log(' optional: %j', program.optional); +program.range = program.range || []; +console.log(' range: %j..%j', program.range[0], program.range[1]); +console.log(' list: %j', program.list); +console.log(' args: %j', program.args); +``` + +## Custom help + + You can display arbitrary `-h, --help` information + by listening for "--help". Commander will automatically + exit once you are done so that the remainder of your program + does not execute causing undesired behaviours, for example + in the following executable "stuff" will not output when + `--help` is used. + +```js +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +function list(val) { + return val.split(',').map(Number); +} + +program + .version('0.0.1') + .option('-f, --foo', 'enable some foo') + .option('-b, --bar', 'enable some bar') + .option('-B, --baz', 'enable some baz'); + +// must be before .parse() since +// node's emit() is immediate + +program.on('--help', function(){ + console.log(' Examples:'); + console.log(''); + console.log(' $ custom-help --help'); + console.log(' $ custom-help -h'); + console.log(''); +}); + +program.parse(process.argv); + +console.log('stuff'); +``` + +yielding the following help output: + +``` + +Usage: custom-help [options] + +Options: + + -h, --help output usage information + -v, --version output the version number + -f, --foo enable some foo + -b, --bar enable some bar + -B, --baz enable some baz + +Examples: + + $ custom-help --help + $ custom-help -h + +``` + +## .prompt(msg, fn) + + Single-line prompt: + +```js +program.prompt('name: ', function(name){ + console.log('hi %s', name); +}); +``` + + Multi-line prompt: + +```js +program.prompt('description:', function(name){ + console.log('hi %s', name); +}); +``` + + Coercion: + +```js +program.prompt('Age: ', Number, function(age){ + console.log('age: %j', age); +}); +``` + +```js +program.prompt('Birthdate: ', Date, function(date){ + console.log('date: %s', date); +}); +``` + +## .password(msg[, mask], fn) + +Prompt for password without echoing: + +```js +program.password('Password: ', function(pass){ + console.log('got "%s"', pass); + process.stdin.destroy(); +}); +``` + +Prompt for password with mask char "*": + +```js +program.password('Password: ', '*', function(pass){ + console.log('got "%s"', pass); + process.stdin.destroy(); +}); +``` + +## .confirm(msg, fn) + + Confirm with the given `msg`: + +```js +program.confirm('continue? ', function(ok){ + console.log(' got %j', ok); +}); +``` + +## .choose(list, fn) + + Let the user choose from a `list`: + +```js +var list = ['tobi', 'loki', 'jane', 'manny', 'luna']; + +console.log('Choose the coolest pet:'); +program.choose(list, function(i){ + console.log('you chose %d "%s"', i, list[i]); +}); +``` + +## Links + + - [API documentation](http://visionmedia.github.com/commander.js/) + - [ascii tables](https://github.com/LearnBoost/cli-table) + - [progress bars](https://github.com/visionmedia/node-progress) + - [more progress bars](https://github.com/substack/node-multimeter) + - [examples](https://github.com/visionmedia/commander.js/tree/master/examples) + +## License + +(The MIT License) + +Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/examples/choice b/node_modules/jade/node_modules/commander/examples/choice new file mode 100755 index 0000000..7cc3638 --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/choice @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +var list = ['tobi', 'loki', 'jane', 'manny', 'luna']; + +console.log('Choose the coolest pet:'); +program.choose(list, function(i){ + console.log('you chose %d "%s"', i, list[i]); + process.stdin.destroy(); +}); \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/examples/coercion b/node_modules/jade/node_modules/commander/examples/coercion new file mode 100755 index 0000000..6013eca --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/coercion @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +function range(val) { + return val.split('..').map(Number); +} + +function list(val) { + return val.split(','); +} + +program + .version('0.0.1') + .option('-i, --integer ', 'An integer argument', parseInt) + .option('-f, --float ', 'A float argument', parseFloat) + .option('-r, --range ..', 'A range', range) + .option('-l, --list ', 'A list', list) + .option('-o, --optional [value]', 'An optional value') + .parse(process.argv); + +console.log(' int: %j', program.integer); +console.log(' float: %j', program.float); +console.log(' optional: %j', program.optional); +program.range = program.range || []; +console.log(' range: %j..%j', program.range[0], program.range[1]); +console.log(' list: %j', program.list); +console.log(' args: %j', program.args); \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/examples/confirm b/node_modules/jade/node_modules/commander/examples/confirm new file mode 100755 index 0000000..784e27f --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/confirm @@ -0,0 +1,12 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +program.confirm('continue? ', function(ok){ + console.log(' got %j', ok); + process.stdin.destroy(); +}); \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/examples/custom-help b/node_modules/jade/node_modules/commander/examples/custom-help new file mode 100755 index 0000000..72286e6 --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/custom-help @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +function list(val) { + return val.split(',').map(Number); +} + +program + .version('0.0.1') + .option('-f, --foo', 'enable some foo') + .option('-b, --bar', 'enable some bar') + .option('-B, --baz', 'enable some baz'); + +// must be before .parse() since +// node's emit() is immediate + +program.on('--help', function(){ + console.log(' Examples:'); + console.log(''); + console.log(' $ custom-help --help'); + console.log(' $ custom-help -h'); + console.log(''); +}); + +program.parse(process.argv); + +console.log('stuff'); \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/examples/defaults b/node_modules/jade/node_modules/commander/examples/defaults new file mode 100755 index 0000000..c417d88 --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/defaults @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +function list(val) { + return val.split(',').map(Number); +} + +program + .version('0.0.1') + .option('-t, --template-engine [engine]', 'Add template [engine] support', 'jade') + .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble') + .option('-l, --list [items]', 'Specify list items defaulting to 1,2,3', list, [1,2,3]) + .parse(process.argv); + +console.log(' - %s template engine', program.templateEngine); +console.log(' - %s cheese', program.cheese); +console.log(' - %j', program.list); diff --git a/node_modules/jade/node_modules/commander/examples/deploy b/node_modules/jade/node_modules/commander/examples/deploy new file mode 100755 index 0000000..cc0332d --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/deploy @@ -0,0 +1,38 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +program + .version('0.0.1') + .option('-C, --chdir ', 'change the working directory') + .option('-c, --config ', 'set config path. defaults to ./deploy.conf') + .option('-T, --no-tests', 'ignore test hook') + +program + .command('setup [env]') + .description('run setup commands for all envs') + .action(function(env){ + env = env || 'all'; + console.log('setup for %s env(s)', env); + }); + +program + .command('exec ') + .description('execute the given remote cmd') + .action(function(cmd){ + console.log('exec "%s"', cmd); + }); + +program + .command('*') + .action(function(env){ + console.log('deploying "%s"', env); + }); + +program.parse(process.argv); + + diff --git a/node_modules/jade/node_modules/commander/examples/express b/node_modules/jade/node_modules/commander/examples/express new file mode 100755 index 0000000..6bb7e26 --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/express @@ -0,0 +1,18 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +program + .version('0.0.1') + .option('-s, --sessions', 'add session support') + .option('-t, --template ', 'specify template engine (jade|ejs) [jade]', 'jade') + .option('-c, --css ', 'specify stylesheet engine (stylus|sass|less) [css]', 'css') + .parse(process.argv); + +console.log(' - sessions %j', program.sessions); +console.log(' - template %j', program.template); +console.log(' - css %j', program.css); \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/examples/input b/node_modules/jade/node_modules/commander/examples/input new file mode 100755 index 0000000..baff826 --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/input @@ -0,0 +1,24 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +program.prompt('Username: ', function(name){ + console.log('hi %s', name); + + program.prompt('Description:', function(desc){ + console.log('description was "%s"', desc.trim()); + + program.prompt('Age: ', Number, function(age){ + console.log('age: %j', age); + + program.prompt('Birthdate: ', Date, function(date){ + console.log('date: %s', date); + process.stdin.destroy(); + }); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/examples/password b/node_modules/jade/node_modules/commander/examples/password new file mode 100755 index 0000000..f02a2ea --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/password @@ -0,0 +1,18 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +program.password('Password: ', function(pass){ + console.log('got "%s"', pass); + program.password('Password: ', '*', function(pass){ + console.log('got "%s"', pass); + program.password('Password: ', '-', function(pass){ + console.log('got "%s"', pass); + process.stdin.destroy(); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/examples/pizza b/node_modules/jade/node_modules/commander/examples/pizza new file mode 100755 index 0000000..ec0c9e9 --- /dev/null +++ b/node_modules/jade/node_modules/commander/examples/pizza @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var program = require('../'); + +program + .version('0.0.1') + .option('-p, --peppers', 'Add peppers') + .option('-P, --pineapple', 'Add pineapple') + .option('-b, --bbq', 'Add bbq sauce') + .option('-c, --cheese ', 'Add the specified type of cheese [marble]') + .option('-C, --no-cheese', 'You do not want any cheese') + .parse(process.argv); + +console.log('you ordered a pizza with:'); +if (program.peppers) console.log(' - peppers'); +if (program.pineappe) console.log(' - pineapple'); +if (program.bbq) console.log(' - bbq'); + +var cheese = true === program.cheese + ? 'marble' + : program.cheese || 'no'; + +console.log(' - %s cheese', cheese); +console.log(program.args); diff --git a/node_modules/jade/node_modules/commander/index.js b/node_modules/jade/node_modules/commander/index.js new file mode 100644 index 0000000..06ec1e4 --- /dev/null +++ b/node_modules/jade/node_modules/commander/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/commander'); \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/lib/commander.js b/node_modules/jade/node_modules/commander/lib/commander.js new file mode 100644 index 0000000..2d40479 --- /dev/null +++ b/node_modules/jade/node_modules/commander/lib/commander.js @@ -0,0 +1,906 @@ +/*! + * commander + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , path = require('path') + , tty = require('tty') + , basename = path.basename; + +/** + * Expose the root command. + */ + +exports = module.exports = new Command; + +/** + * Expose `Command`. + */ + +exports.Command = Command; + +/** + * Expose `Option`. + */ + +exports.Option = Option; + +/** + * Initialize a new `Option` with the given `flags` and `description`. + * + * @param {String} flags + * @param {String} description + * @api public + */ + +function Option(flags, description) { + this.flags = flags; + this.required = ~flags.indexOf('<'); + this.optional = ~flags.indexOf('['); + this.bool = !~flags.indexOf('-no-'); + flags = flags.split(/[ ,|]+/) + this.short = flags.shift(); + this.long = flags.shift(); + this.description = description; +} + +/** + * Return option name. + * + * @return {String} + * @api private + */ + +Option.prototype.name = function(){ + return this.long + .replace('--', '') + .replace('no-', ''); +}; + +/** + * Check if `arg` matches the short or long flag. + * + * @param {String} arg + * @return {Boolean} + * @api private + */ + +Option.prototype.is = function(arg){ + return arg == this.short + || arg == this.long; +}; + +/** + * Initialize a new `Command`. + * + * @param {String} name + * @api public + */ + +function Command(name) { + this.commands = []; + this.options = []; + this.args = []; + this.name = name; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +Command.prototype.__proto__ = EventEmitter.prototype; + +/** + * Add command `name`. + * + * The `.action()` callback is invoked when the + * command `name` is specified via __ARGV__, + * and the remaining arguments are applied to the + * function for access. + * + * When the `name` is "*" an un-matched command + * will be passed as the first arg, followed by + * the rest of __ARGV__ remaining. + * + * Examples: + * + * program + * .version('0.0.1') + * .option('-C, --chdir ', 'change the working directory') + * .option('-c, --config ', 'set config path. defaults to ./deploy.conf') + * .option('-T, --no-tests', 'ignore test hook') + * + * program + * .command('setup') + * .description('run remote setup commands') + * .action(function(){ + * console.log('setup'); + * }); + * + * program + * .command('exec ') + * .description('run the given remote command') + * .action(function(cmd){ + * console.log('exec "%s"', cmd); + * }); + * + * program + * .command('*') + * .description('deploy the given env') + * .action(function(env){ + * console.log('deploying "%s"', env); + * }); + * + * program.parse(process.argv); + * + * @param {String} name + * @return {Command} the new command + * @api public + */ + +Command.prototype.command = function(name){ + var args = name.split(/ +/); + var cmd = new Command(args.shift()); + this.commands.push(cmd); + cmd.parseExpectedArgs(args); + cmd.parent = this; + return cmd; +}; + +/** + * Parse expected `args`. + * + * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`. + * + * @param {Array} args + * @return {Command} for chaining + * @api public + */ + +Command.prototype.parseExpectedArgs = function(args){ + if (!args.length) return; + var self = this; + args.forEach(function(arg){ + switch (arg[0]) { + case '<': + self.args.push({ required: true, name: arg.slice(1, -1) }); + break; + case '[': + self.args.push({ required: false, name: arg.slice(1, -1) }); + break; + } + }); + return this; +}; + +/** + * Register callback `fn` for the command. + * + * Examples: + * + * program + * .command('help') + * .description('display verbose help') + * .action(function(){ + * // output help here + * }); + * + * @param {Function} fn + * @return {Command} for chaining + * @api public + */ + +Command.prototype.action = function(fn){ + var self = this; + this.parent.on(this.name, function(args){ + self.args.forEach(function(arg, i){ + if (arg.required && null == args[i]) { + self.missingArgument(arg.name); + } + }); + fn.apply(this, args); + }); + return this; +}; + +/** + * Define option with `flags`, `description` and optional + * coercion `fn`. + * + * The `flags` string should contain both the short and long flags, + * separated by comma, a pipe or space. The following are all valid + * all will output this way when `--help` is used. + * + * "-p, --pepper" + * "-p|--pepper" + * "-p --pepper" + * + * Examples: + * + * // simple boolean defaulting to false + * program.option('-p, --pepper', 'add pepper'); + * + * --pepper + * program.pepper + * // => Boolean + * + * // simple boolean defaulting to false + * program.option('-C, --no-cheese', 'remove cheese'); + * + * program.cheese + * // => true + * + * --no-cheese + * program.cheese + * // => true + * + * // required argument + * program.option('-C, --chdir ', 'change the working directory'); + * + * --chdir /tmp + * program.chdir + * // => "/tmp" + * + * // optional argument + * program.option('-c, --cheese [type]', 'add cheese [marble]'); + * + * @param {String} flags + * @param {String} description + * @param {Function|Mixed} fn or default + * @param {Mixed} defaultValue + * @return {Command} for chaining + * @api public + */ + +Command.prototype.option = function(flags, description, fn, defaultValue){ + var self = this + , option = new Option(flags, description) + , oname = option.name() + , name = camelcase(oname); + + // default as 3rd arg + if ('function' != typeof fn) defaultValue = fn, fn = null; + + // preassign default value only for --no-*, [optional], or + if (false == option.bool || option.optional || option.required) { + // when --no-* we make sure default is true + if (false == option.bool) defaultValue = true; + // preassign only if we have a default + if (undefined !== defaultValue) self[name] = defaultValue; + } + + // register the option + this.options.push(option); + + // when it's passed assign the value + // and conditionally invoke the callback + this.on(oname, function(val){ + // coercion + if (null != val && fn) val = fn(val); + + // unassigned or bool + if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) { + // if no value, bool true, and we have a default, then use it! + if (null == val) { + self[name] = option.bool + ? defaultValue || true + : false; + } else { + self[name] = val; + } + } else if (null !== val) { + // reassign + self[name] = val; + } + }); + + return this; +}; + +/** + * Parse `argv`, settings options and invoking commands when defined. + * + * @param {Array} argv + * @return {Command} for chaining + * @api public + */ + +Command.prototype.parse = function(argv){ + // store raw args + this.rawArgs = argv; + + // guess name + if (!this.name) this.name = basename(argv[1]); + + // process argv + this.args = this.parseOptions(this.normalize(argv)); + return this.parseArgs(this.args); +}; + +/** + * Normalize `args`, splitting joined short flags. For example + * the arg "-abc" is equivalent to "-a -b -c". + * + * @param {Array} args + * @return {Array} + * @api private + */ + +Command.prototype.normalize = function(args){ + var ret = [] + , arg; + + for (var i = 0, len = args.length; i < len; ++i) { + arg = args[i]; + if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) { + arg.slice(1).split('').forEach(function(c){ + ret.push('-' + c); + }); + } else { + ret.push(arg); + } + } + + return ret; +}; + +/** + * Parse command `args`. + * + * When listener(s) are available those + * callbacks are invoked, otherwise the "*" + * event is emitted and those actions are invoked. + * + * @param {Array} args + * @return {Command} for chaining + * @api private + */ + +Command.prototype.parseArgs = function(args){ + var cmds = this.commands + , len = cmds.length + , name; + + if (args.length) { + name = args[0]; + if (this.listeners(name).length) { + this.emit(args.shift(), args); + } else { + this.emit('*', args); + } + } + + return this; +}; + +/** + * Return an option matching `arg` if any. + * + * @param {String} arg + * @return {Option} + * @api private + */ + +Command.prototype.optionFor = function(arg){ + for (var i = 0, len = this.options.length; i < len; ++i) { + if (this.options[i].is(arg)) { + return this.options[i]; + } + } +}; + +/** + * Parse options from `argv` returning `argv` + * void of these options. + * + * @param {Array} argv + * @return {Array} + * @api public + */ + +Command.prototype.parseOptions = function(argv){ + var args = [] + , argv = argv.slice(2) + , len = argv.length + , option + , arg; + + // parse options + for (var i = 0; i < len; ++i) { + arg = argv[i]; + option = this.optionFor(arg); + + // option is defined + if (option) { + // requires arg + if (option.required) { + arg = argv[++i]; + if (null == arg) return this.optionMissingArgument(option); + if ('-' == arg[0]) return this.optionMissingArgument(option, arg); + this.emit(option.name(), arg); + // optional arg + } else if (option.optional) { + arg = argv[i+1]; + if (null == arg || '-' == arg[0]) { + arg = null; + } else { + ++i; + } + this.emit(option.name(), arg); + // bool + } else { + this.emit(option.name()); + } + continue; + } + + // looks like an option + if (arg.length > 1 && '-' == arg[0]) { + this.unknownOption(arg); + } + + // arg + args.push(arg); + } + + return args; +}; + +/** + * Argument `name` is missing. + * + * @param {String} name + * @api private + */ + +Command.prototype.missingArgument = function(name){ + console.error(); + console.error(" error: missing required argument `%s'", name); + console.error(); + process.exit(1); +}; + +/** + * `Option` is missing an argument, but received `flag` or nothing. + * + * @param {String} option + * @param {String} flag + * @api private + */ + +Command.prototype.optionMissingArgument = function(option, flag){ + console.error(); + if (flag) { + console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag); + } else { + console.error(" error: option `%s' argument missing", option.flags); + } + console.error(); + process.exit(1); +}; + +/** + * Unknown option `flag`. + * + * @param {String} flag + * @api private + */ + +Command.prototype.unknownOption = function(flag){ + console.error(); + console.error(" error: unknown option `%s'", flag); + console.error(); + process.exit(1); +}; + +/** + * Set the program version to `str`. + * + * This method auto-registers the "-v, --version" flag + * which will print the version number when passed. + * + * @param {String} str + * @return {Command} for chaining + * @api public + */ + +Command.prototype.version = function(str){ + if (0 == arguments.length) return this._version; + this._version = str; + this.option('-v, --version', 'output the version number'); + this.on('version', function(){ + console.log(str); + process.exit(0); + }); + return this; +}; + +/** + * Set the description `str`. + * + * @param {String} str + * @return {String|Command} + * @api public + */ + +Command.prototype.description = function(str){ + if (0 == arguments.length) return this._description; + this._description = str; + return this; +}; + +/** + * Set / get the command usage `str`. + * + * @param {String} str + * @return {String|Command} + * @api public + */ + +Command.prototype.usage = function(str){ + var usage = '[options' + + (this.commands.length ? '] [command' : '') + + ']'; + if (0 == arguments.length) return this._usage || usage; + this._usage = str; + return this; +}; + +/** + * Return the largest option length. + * + * @return {Number} + * @api private + */ + +Command.prototype.largestOptionLength = function(){ + return this.options.reduce(function(max, option){ + return Math.max(max, option.flags.length); + }, 0); +}; + +/** + * Return help for options. + * + * @return {String} + * @api private + */ + +Command.prototype.optionHelp = function(){ + var width = this.largestOptionLength(); + return this.options.map(function(option){ + return pad(option.flags, width) + + ' ' + option.description; + }).join('\n'); +}; + +/** + * Return command help documentation. + * + * @return {String} + * @api private + */ + +Command.prototype.commandHelp = function(){ + if (!this.commands.length) return ''; + return [ + '' + , ' Commands:' + , '' + , this.commands.map(function(cmd){ + var args = cmd.args.map(function(arg){ + return arg.required + ? '<' + arg.name + '>' + : '[' + arg.name + ']'; + }).join(' '); + return cmd.name + ' ' + args + + (cmd.description() + ? '\n' + cmd.description() + : ''); + }).join('\n\n').replace(/^/gm, ' ') + , '' + ].join('\n'); +}; + +/** + * Return program help documentation. + * + * @return {String} + * @api private + */ + +Command.prototype.helpInformation = function(){ + return [ + '' + , ' Usage: ' + this.name + ' ' + this.usage() + , '' + this.commandHelp() + , ' Options:' + , '' + , '' + this.optionHelp().replace(/^/gm, ' ') + , '' + , '' + ].join('\n'); +}; + +/** + * Prompt for a `Number`. + * + * @param {String} str + * @param {Function} fn + * @api private + */ + +Command.prototype.promptForNumber = function(str, fn){ + this.promptSingleLine(str, function(val){ + val = Number(val); + if (isNaN(val)) return program.promptForNumber(str + '(must be a number) ', fn); + fn(val); + }); +}; + +/** + * Prompt for a `Date`. + * + * @param {String} str + * @param {Function} fn + * @api private + */ + +Command.prototype.promptForDate = function(str, fn){ + this.promptSingleLine(str, function(val){ + val = new Date(val); + if (isNaN(val.getTime())) return program.promptForDate(str + '(must be a date) ', fn); + fn(val); + }); +}; + +/** + * Single-line prompt. + * + * @param {String} str + * @param {Function} fn + * @api private + */ + +Command.prototype.promptSingleLine = function(str, fn){ + if ('function' == typeof arguments[2]) { + return this['promptFor' + (fn.name || fn)](str, arguments[2]); + } + + process.stdout.write(str); + process.stdin.setEncoding('utf8'); + process.stdin.once('data', function(val){ + fn(val); + }).resume(); +}; + +/** + * Multi-line prompt. + * + * @param {String} str + * @param {Function} fn + * @api private + */ + +Command.prototype.promptMultiLine = function(str, fn){ + var buf = ''; + console.log(str); + process.stdin.setEncoding('utf8'); + process.stdin.on('data', function(val){ + if ('\n' == val) { + process.stdin.removeAllListeners('data'); + fn(buf); + } else { + buf += val; + } + }).resume(); +}; + +/** + * Prompt `str` and callback `fn(val)` + * + * Commander supports single-line and multi-line prompts. + * To issue a single-line prompt simply add white-space + * to the end of `str`, something like "name: ", whereas + * for a multi-line prompt omit this "description:". + * + * + * Examples: + * + * program.prompt('Username: ', function(name){ + * console.log('hi %s', name); + * }); + * + * program.prompt('Description:', function(desc){ + * console.log('description was "%s"', desc.trim()); + * }); + * + * @param {String} str + * @param {Function} fn + * @api public + */ + +Command.prototype.prompt = function(str, fn){ + if (/ $/.test(str)) return this.promptSingleLine.apply(this, arguments); + this.promptMultiLine(str, fn); +}; + +/** + * Prompt for password with `str`, `mask` char and callback `fn(val)`. + * + * The mask string defaults to '', aka no output is + * written while typing, you may want to use "*" etc. + * + * Examples: + * + * program.password('Password: ', function(pass){ + * console.log('got "%s"', pass); + * process.stdin.destroy(); + * }); + * + * program.password('Password: ', '*', function(pass){ + * console.log('got "%s"', pass); + * process.stdin.destroy(); + * }); + * + * @param {String} str + * @param {String} mask + * @param {Function} fn + * @api public + */ + +Command.prototype.password = function(str, mask, fn){ + var self = this + , buf = ''; + + // default mask + if ('function' == typeof mask) { + fn = mask; + mask = ''; + } + + tty.setRawMode(true); + process.stdout.write(str); + + // keypress + process.stdin.on('keypress', function(c, key){ + if (key && 'enter' == key.name) { + console.log(); + process.stdin.removeAllListeners('keypress'); + tty.setRawMode(false); + if (!buf.trim().length) return self.password(str, mask, fn); + fn(buf); + return; + } + + if (key && key.ctrl && 'c' == key.name) { + console.log('%s', buf); + process.exit(); + } + + process.stdout.write(mask); + buf += c; + }).resume(); +}; + +/** + * Confirmation prompt with `str` and callback `fn(bool)` + * + * Examples: + * + * program.confirm('continue? ', function(ok){ + * console.log(' got %j', ok); + * process.stdin.destroy(); + * }); + * + * @param {String} str + * @param {Function} fn + * @api public + */ + + +Command.prototype.confirm = function(str, fn){ + var self = this; + this.prompt(str, function(ok){ + if (!ok.trim()) { + return self.confirm(str, fn); + } + fn(parseBool(ok)); + }); +}; + +/** + * Choice prompt with `list` of items and callback `fn(index, item)` + * + * Examples: + * + * var list = ['tobi', 'loki', 'jane', 'manny', 'luna']; + * + * console.log('Choose the coolest pet:'); + * program.choose(list, function(i){ + * console.log('you chose %d "%s"', i, list[i]); + * process.stdin.destroy(); + * }); + * + * @param {Array} list + * @param {Function} fn + * @api public + */ + +Command.prototype.choose = function(list, fn){ + var self = this; + + list.forEach(function(item, i){ + console.log(' %d) %s', i + 1, item); + }); + + function again() { + self.prompt(' : ', function(val){ + val = parseInt(val, 10) - 1; + if (null == list[val]) { + again(); + } else { + fn(val, list[val]); + } + }); + } + + again(); +}; + +/** + * Camel-case the given `flag` + * + * @param {String} flag + * @return {String} + * @api private + */ + +function camelcase(flag) { + return flag.split('-').reduce(function(str, word){ + return str + word[0].toUpperCase() + word.slice(1); + }); +} + +/** + * Parse a boolean `str`. + * + * @param {String} str + * @return {Boolean} + * @api private + */ + +function parseBool(str) { + return /^y|yes|ok|true$/i.test(str); +} + +/** + * Pad `str` to `width`. + * + * @param {String} str + * @param {Number} width + * @return {String} + * @api private + */ + +function pad(str, width) { + var len = Math.max(0, width - str.length); + return str + Array(len + 1).join(' '); +} + +/** + * Default -h, --help option. + */ + +exports.option('-h, --help', 'output usage information'); +exports.on('help', function(){ + process.stdout.write(this.helpInformation()); + exports.emit('--help'); + process.exit(0); +}); diff --git a/node_modules/jade/node_modules/commander/package.json b/node_modules/jade/node_modules/commander/package.json new file mode 100644 index 0000000..cc72d7b --- /dev/null +++ b/node_modules/jade/node_modules/commander/package.json @@ -0,0 +1,12 @@ +{ + "name": "commander" + , "version": "0.2.1" + , "description": "the complete solution for node.js command-line programs" + , "keywords": ["command", "option", "parser", "prompt", "stdin"] + , "author": "TJ Holowaychuk " + , "repository": { "type": "git", "url": "https://github.com/visionmedia/commander.js.git" } + , "dependencies": {} + , "devDependencies": { "should": ">= 0.0.1" } + , "main": "index" + , "engines": { "node": ">= 0.4.x < 0.7.0" } +} \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/test/run b/node_modules/jade/node_modules/commander/test/run new file mode 100755 index 0000000..37893b5 --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/run @@ -0,0 +1,16 @@ +#!/bin/sh + +export NODE_ENV=test + +echo +for file in $@; do + printf "\033[90m ${file#test/}\033[0m " + node $file 2> /tmp/stderr && echo "\033[36m✓\033[0m" + code=$? + if test $code -ne 0; then + echo "\033[31m✖\033[0m" + cat /tmp/stderr >&2 + exit $code + fi +done +echo \ No newline at end of file diff --git a/node_modules/jade/node_modules/commander/test/test.options.args.optional.given.js b/node_modules/jade/node_modules/commander/test/test.options.args.optional.given.js new file mode 100644 index 0000000..86e9d14 --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.args.optional.given.js @@ -0,0 +1,13 @@ +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +program + .version('0.0.1') + .option('-c, --cheese [type]', 'optionally specify the type of cheese'); + +program.parse(['node', 'test', '--cheese', 'feta']); +program.cheese.should.equal('feta'); diff --git a/node_modules/jade/node_modules/commander/test/test.options.args.optional.js b/node_modules/jade/node_modules/commander/test/test.options.args.optional.js new file mode 100644 index 0000000..aacaccc --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.args.optional.js @@ -0,0 +1,13 @@ +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +program + .version('0.0.1') + .option('-c, --cheese [type]', 'optionally specify the type of cheese'); + +program.parse(['node', 'test', '--cheese']); +program.cheese.should.be.true; diff --git a/node_modules/jade/node_modules/commander/test/test.options.bool.js b/node_modules/jade/node_modules/commander/test/test.options.bool.js new file mode 100644 index 0000000..4fc3206 --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.bool.js @@ -0,0 +1,16 @@ + +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +program + .version('0.0.1') + .option('-p, --pepper', 'add pepper') + .option('-c, --no-cheese', 'remove cheese'); + +program.parse(['node', 'test', '--pepper']); +program.pepper.should.be.true; +program.cheese.should.be.true; diff --git a/node_modules/jade/node_modules/commander/test/test.options.bool.no.js b/node_modules/jade/node_modules/commander/test/test.options.bool.no.js new file mode 100644 index 0000000..bb7b899 --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.bool.no.js @@ -0,0 +1,15 @@ +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +program + .version('0.0.1') + .option('-p, --pepper', 'add pepper') + .option('-c|--no-cheese', 'remove cheese'); + +program.parse(['node', 'test', '--no-cheese']); +should.equal(undefined, program.pepper); +program.cheese.should.be.false; diff --git a/node_modules/jade/node_modules/commander/test/test.options.bool.small.combined.js b/node_modules/jade/node_modules/commander/test/test.options.bool.small.combined.js new file mode 100644 index 0000000..0b1a3bf --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.bool.small.combined.js @@ -0,0 +1,15 @@ +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +program + .version('0.0.1') + .option('-p, --pepper', 'add pepper') + .option('-c, --no-cheese', 'remove cheese'); + +program.parse(['node', 'test', '-pc']); +program.pepper.should.be.true; +program.cheese.should.be.false; diff --git a/node_modules/jade/node_modules/commander/test/test.options.bool.small.js b/node_modules/jade/node_modules/commander/test/test.options.bool.small.js new file mode 100644 index 0000000..2818bfd --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.bool.small.js @@ -0,0 +1,15 @@ +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +program + .version('0.0.1') + .option('-p, --pepper', 'add pepper') + .option('-c, --no-cheese', 'remove cheese'); + +program.parse(['node', 'test', '-p', '-c']); +program.pepper.should.be.true; +program.cheese.should.be.false; diff --git a/node_modules/jade/node_modules/commander/test/test.options.camelcase.js b/node_modules/jade/node_modules/commander/test/test.options.camelcase.js new file mode 100644 index 0000000..b36e762 --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.camelcase.js @@ -0,0 +1,27 @@ +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +function parseRange(str) { + return str.split('..').map(Number); +} + +program + .version('0.0.1') + .option('-i, --my-int ', 'pass an int', parseInt) + .option('-n, --my-num ', 'pass a number', Number) + .option('-f, --my-fLOAT ', 'pass a float', parseFloat) + .option('-m, --my-very-long-float ', 'pass a float', parseFloat) + .option('-u, --my-URL-count ', 'pass a float', parseFloat) + .option('-r, --my-long-range ', 'pass a range', parseRange); + +program.parse('node test -i 5.5 -f 5.5 -m 6.5 -u 7.5 -n 15.99 -r 1..5'.split(' ')); +program.myInt.should.equal(5); +program.myNum.should.equal(15.99); +program.myFLOAT.should.equal(5.5); +program.myVeryLongFloat.should.equal(6.5); +program.myURLCount.should.equal(7.5); +program.myLongRange.should.eql([1,5]); diff --git a/node_modules/jade/node_modules/commander/test/test.options.coercion.js b/node_modules/jade/node_modules/commander/test/test.options.coercion.js new file mode 100644 index 0000000..296d967 --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.coercion.js @@ -0,0 +1,23 @@ +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +function parseRange(str) { + return str.split('..').map(Number); +} + +program + .version('0.0.1') + .option('-i, --int ', 'pass an int', parseInt) + .option('-n, --num ', 'pass a number', Number) + .option('-f, --float ', 'pass a float', parseFloat) + .option('-r, --range ', 'pass a range', parseRange); + +program.parse('node test -i 5.5 -f 5.5 -n 15.99 -r 1..5'.split(' ')); +program.int.should.equal(5); +program.num.should.equal(15.99); +program.float.should.equal(5.5); +program.range.should.eql([1,5]); diff --git a/node_modules/jade/node_modules/commander/test/test.options.defaults.given.js b/node_modules/jade/node_modules/commander/test/test.options.defaults.given.js new file mode 100644 index 0000000..d0846d7 --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.defaults.given.js @@ -0,0 +1,23 @@ +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +program + .version('0.0.1') + .option('-a, --anchovies', 'Add anchovies?') + .option('-o, --onions', 'Add onions?', true) + .option('-v, --olives', 'Add olives? Sorry we only have black.', 'black') + .option('-s, --no-sauce', 'Uh… okay') + .option('-r, --crust ', 'What kind of crust would you like?', 'hand-tossed') + .option('-c, --cheese [type]', 'optionally specify the type of cheese', 'mozzarella'); + +program.parse(['node', 'test', '--anchovies', '--onions', '--olives', '--no-sauce', '--crust', 'thin', '--cheese', 'wensleydale']); +program.should.have.property('anchovies', true); +program.should.have.property('onions', true); +program.should.have.property('olives', 'black'); +program.should.have.property('sauce', false); +program.should.have.property('crust', 'thin'); +program.should.have.property('cheese', 'wensleydale'); diff --git a/node_modules/jade/node_modules/commander/test/test.options.defaults.js b/node_modules/jade/node_modules/commander/test/test.options.defaults.js new file mode 100644 index 0000000..67800a4 --- /dev/null +++ b/node_modules/jade/node_modules/commander/test/test.options.defaults.js @@ -0,0 +1,23 @@ +/** + * Module dependencies. + */ + +var program = require('../') + , should = require('should'); + +program + .version('0.0.1') + .option('-a, --anchovies', 'Add anchovies?') + .option('-o, --onions', 'Add onions?', true) + .option('-v, --olives', 'Add olives? Sorry we only have black.', 'black') + .option('-s, --no-sauce', 'Uh… okay') + .option('-r, --crust ', 'What kind of crust would you like?', 'hand-tossed') + .option('-c, --cheese [type]', 'optionally specify the type of cheese', 'mozzarella'); + +program.parse(['node', 'test']); +program.should.not.have.property('anchovies'); +program.should.not.have.property('onions'); +program.should.not.have.property('olives'); +program.should.have.property('sauce', true); +program.should.have.property('crust', 'hand-tossed'); +program.should.have.property('cheese', 'mozzarella'); diff --git a/node_modules/jade/node_modules/mkdirp/.gitignore b/node_modules/jade/node_modules/mkdirp/.gitignore new file mode 100644 index 0000000..9303c34 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +npm-debug.log \ No newline at end of file diff --git a/node_modules/jade/node_modules/mkdirp/.gitignore.orig b/node_modules/jade/node_modules/mkdirp/.gitignore.orig new file mode 100644 index 0000000..9303c34 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/.gitignore.orig @@ -0,0 +1,2 @@ +node_modules/ +npm-debug.log \ No newline at end of file diff --git a/node_modules/jade/node_modules/mkdirp/.gitignore.rej b/node_modules/jade/node_modules/mkdirp/.gitignore.rej new file mode 100644 index 0000000..69244ff --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/.gitignore.rej @@ -0,0 +1,5 @@ +--- /dev/null ++++ .gitignore +@@ -0,0 +1,2 @@ ++node_modules/ ++npm-debug.log \ No newline at end of file diff --git a/node_modules/jade/node_modules/mkdirp/LICENSE b/node_modules/jade/node_modules/mkdirp/LICENSE new file mode 100644 index 0000000..432d1ae --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/LICENSE @@ -0,0 +1,21 @@ +Copyright 2010 James Halliday (mail@substack.net) + +This project is free software released under the MIT/X11 license: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/jade/node_modules/mkdirp/README.markdown b/node_modules/jade/node_modules/mkdirp/README.markdown new file mode 100644 index 0000000..c7cec72 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/README.markdown @@ -0,0 +1,50 @@ +mkdirp +====== + +Like `mkdir -p`, but in node.js! + +example +======= + +pow.js +------ + var mkdirp = require('mkdirp'); + + mkdirp('/tmp/foo/bar/baz', 0755, function (err) { + if (err) console.error(err) + else console.log('pow!') + }); + +Output + pow! + +And now /tmp/foo/bar/baz exists, huzzah! + +methods +======= + +var mkdirp = require('mkdirp'); + +mkdirp(dir, mode, cb) +--------------------- + +Create a new directory and any necessary subdirectories at `dir` with octal +permission string `mode`. + +mkdirp.sync(dir, mode) +---------------------- + +Synchronously create a new directory and any necessary subdirectories at `dir` +with octal permission string `mode`. + +install +======= + +With [npm](http://npmjs.org) do: + + npm install mkdirp + +license +======= + +MIT/X11 diff --git a/node_modules/jade/node_modules/mkdirp/examples/pow.js b/node_modules/jade/node_modules/mkdirp/examples/pow.js new file mode 100644 index 0000000..7741462 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/examples/pow.js @@ -0,0 +1,6 @@ +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', 0755, function (err) { + if (err) console.error(err) + else console.log('pow!') +}); diff --git a/node_modules/jade/node_modules/mkdirp/examples/pow.js.orig b/node_modules/jade/node_modules/mkdirp/examples/pow.js.orig new file mode 100644 index 0000000..7741462 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/examples/pow.js.orig @@ -0,0 +1,6 @@ +var mkdirp = require('mkdirp'); + +mkdirp('/tmp/foo/bar/baz', 0755, function (err) { + if (err) console.error(err) + else console.log('pow!') +}); diff --git a/node_modules/jade/node_modules/mkdirp/examples/pow.js.rej b/node_modules/jade/node_modules/mkdirp/examples/pow.js.rej new file mode 100644 index 0000000..81e7f43 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/examples/pow.js.rej @@ -0,0 +1,19 @@ +--- examples/pow.js ++++ examples/pow.js +@@ -1,6 +1,15 @@ +-var mkdirp = require('mkdirp').mkdirp; ++var mkdirp = require('../').mkdirp, ++ mkdirpSync = require('../').mkdirpSync; + + mkdirp('/tmp/foo/bar/baz', 0755, function (err) { + if (err) console.error(err) + else console.log('pow!') + }); ++ ++try { ++ mkdirpSync('/tmp/bar/foo/baz', 0755); ++ console.log('double pow!'); ++} ++catch (ex) { ++ console.log(ex); ++} \ No newline at end of file diff --git a/node_modules/jade/node_modules/mkdirp/index.js b/node_modules/jade/node_modules/mkdirp/index.js new file mode 100644 index 0000000..f6e4f2b --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/index.js @@ -0,0 +1,85 @@ +var path = require('path'); +var fs = require('fs'); + +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; + +function mkdirP (p, mode, f) { + if (mode === undefined) throw new Error('mode not specified'); + + var cb = f || function () {}; + if (typeof mode === 'string') mode = parseInt(mode, 8); + p = path.resolve(p); + + fs.mkdir(p, mode, function (er) { + if (!er) return cb(); + switch (er.code) { + case 'ENOENT': + mkdirP(path.dirname(p), mode, function (er) { + if (er) cb(er); + else mkdirP(p, mode, cb); + }); + break; + + case 'EEXIST': + fs.stat(p, function (er2, stat) { + // if the stat fails, then that's super weird. + // let the original EEXIST be the failure reason. + if (er2 || !stat.isDirectory()) cb(er) + else if ((stat.mode & 0777) !== mode) fs.chmod(p, mode, cb); + else cb(); + }); + break; + + default: + cb(er); + break; + } + }); +} + +mkdirP.sync = function sync (p, mode) { + if (mode === undefined) throw new Error('mode not specified'); + + if (typeof mode === 'string') mode = parseInt(mode, 8); + p = path.resolve(p); + + try { + fs.mkdirSync(p, mode) + } + catch (err0) { + switch (err0.code) { + case 'ENOENT' : + var err1 = sync(path.dirname(p), mode) + if (err1) throw err1; + else return sync(p, mode); + break; + + case 'EEXIST' : + var stat; + try { + stat = fs.statSync(p); + } + catch (err1) { + throw err0 + } + if (!stat.isDirectory()) throw err0; + else if ((stat.mode & 0777) !== mode) { + try { + fs.chmodSync(p, mode); + } + catch (err) { + if (err && err.code === 'EPERM') return null; + else throw err; + } + return null; + } + else return null; + break; + default : + throw err0 + break; + } + } + + return null; +}; diff --git a/node_modules/jade/node_modules/mkdirp/package.json b/node_modules/jade/node_modules/mkdirp/package.json new file mode 100644 index 0000000..a783046 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/package.json @@ -0,0 +1,23 @@ +{ + "name" : "mkdirp", + "description" : "Recursively mkdir, like `mkdir -p`", + "version" : "0.2.1", + "author" : "James Halliday (http://substack.net)", + "main" : "./index", + "keywords" : [ + "mkdir", + "directory" + ], + "repository" : { + "type" : "git", + "url" : "http://github.com/substack/node-mkdirp.git" + }, + "scripts" : { + "test" : "tap test/*.js" + }, + "devDependencies" : { + "tap" : "0.0.x" + }, + "license" : "MIT/X11", + "engines": { "node": "*" } +} diff --git a/node_modules/jade/node_modules/mkdirp/test/chmod.js b/node_modules/jade/node_modules/mkdirp/test/chmod.js new file mode 100644 index 0000000..0609694 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/test/chmod.js @@ -0,0 +1,39 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +var ps = [ '', 'tmp' ]; + +for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); +} + +var file = ps.join('/'); + +test('chmod-pre', function (t) { + var mode = 0744 + mkdirp(file, mode, function (er) { + t.ifError(er, 'should not error'); + fs.stat(file, function (er, stat) { + t.ifError(er, 'should exist'); + t.ok(stat && stat.isDirectory(), 'should be directory'); + t.equal(stat && stat.mode & 0777, mode, 'should be 0744'); + t.end(); + }); + }); +}); + +test('chmod', function (t) { + var mode = 0755 + mkdirp(file, mode, function (er) { + t.ifError(er, 'should not error'); + fs.stat(file, function (er, stat) { + t.ifError(er, 'should exist'); + t.ok(stat && stat.isDirectory(), 'should be directory'); + t.equal(stat && stat.mode & 0777, mode, 'should be 0755'); + t.end(); + }); + }); +}); diff --git a/node_modules/jade/node_modules/mkdirp/test/clobber.js b/node_modules/jade/node_modules/mkdirp/test/clobber.js new file mode 100644 index 0000000..0eb7099 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/test/clobber.js @@ -0,0 +1,37 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +var ps = [ '', 'tmp' ]; + +for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); +} + +var file = ps.join('/'); + +// a file in the way +var itw = ps.slice(0, 3).join('/'); + + +test('clobber-pre', function (t) { + console.error("about to write to "+itw) + fs.writeFileSync(itw, 'I AM IN THE WAY, THE TRUTH, AND THE LIGHT.'); + + fs.stat(itw, function (er, stat) { + t.ifError(er) + t.ok(stat && stat.isFile(), 'should be file') + t.end() + }) +}) + +test('clobber', function (t) { + t.plan(2); + mkdirp(file, 0755, function (err) { + t.ok(err); + t.equal(err.code, 'ENOTDIR'); + t.end(); + }); +}); diff --git a/node_modules/jade/node_modules/mkdirp/test/mkdirp.js b/node_modules/jade/node_modules/mkdirp/test/mkdirp.js new file mode 100644 index 0000000..b07cd70 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/test/mkdirp.js @@ -0,0 +1,28 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('woo', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); diff --git a/node_modules/jade/node_modules/mkdirp/test/perm.js b/node_modules/jade/node_modules/mkdirp/test/perm.js new file mode 100644 index 0000000..23a7abb --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/test/perm.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('async perm', function (t) { + t.plan(2); + var file = '/tmp/' + (Math.random() * (1<<30)).toString(16); + + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); + +test('async root perm', function (t) { + mkdirp('/tmp', 0755, function (err) { + if (err) t.fail(err); + t.end(); + }); + t.end(); +}); diff --git a/node_modules/jade/node_modules/mkdirp/test/perm_sync.js b/node_modules/jade/node_modules/mkdirp/test/perm_sync.js new file mode 100644 index 0000000..f685f60 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/test/perm_sync.js @@ -0,0 +1,39 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('sync perm', function (t) { + t.plan(2); + var file = '/tmp/' + (Math.random() * (1<<30)).toString(16) + '.json'; + + mkdirp.sync(file, 0755); + path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }); +}); + +test('sync root perm', function (t) { + t.plan(1); + + var file = '/tmp'; + mkdirp.sync(file, 0755); + path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }); +}); diff --git a/node_modules/jade/node_modules/mkdirp/test/race.js b/node_modules/jade/node_modules/mkdirp/test/race.js new file mode 100644 index 0000000..96a0447 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/test/race.js @@ -0,0 +1,41 @@ +var mkdirp = require('../').mkdirp; +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('race', function (t) { + t.plan(4); + var ps = [ '', 'tmp' ]; + + for (var i = 0; i < 25; i++) { + var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + ps.push(dir); + } + var file = ps.join('/'); + + var res = 2; + mk(file, function () { + if (--res === 0) t.end(); + }); + + mk(file, function () { + if (--res === 0) t.end(); + }); + + function mk (file, cb) { + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + if (cb) cb(); + } + }) + }) + }); + } +}); diff --git a/node_modules/jade/node_modules/mkdirp/test/rel.js b/node_modules/jade/node_modules/mkdirp/test/rel.js new file mode 100644 index 0000000..7985824 --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/test/rel.js @@ -0,0 +1,32 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('rel', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var cwd = process.cwd(); + process.chdir('/tmp'); + + var file = [x,y,z].join('/'); + + mkdirp(file, 0755, function (err) { + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + process.chdir(cwd); + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) + }); +}); diff --git a/node_modules/jade/node_modules/mkdirp/test/sync.js b/node_modules/jade/node_modules/mkdirp/test/sync.js new file mode 100644 index 0000000..e0e389d --- /dev/null +++ b/node_modules/jade/node_modules/mkdirp/test/sync.js @@ -0,0 +1,27 @@ +var mkdirp = require('../'); +var path = require('path'); +var fs = require('fs'); +var test = require('tap').test; + +test('sync', function (t) { + t.plan(2); + var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16); + + var file = '/tmp/' + [x,y,z].join('/'); + + var err = mkdirp.sync(file, 0755); + if (err) t.fail(err); + else path.exists(file, function (ex) { + if (!ex) t.fail('file not created') + else fs.stat(file, function (err, stat) { + if (err) t.fail(err) + else { + t.equal(stat.mode & 0777, 0755); + t.ok(stat.isDirectory(), 'target not a directory'); + t.end(); + } + }) + }) +}); diff --git a/node_modules/jade/package.json b/node_modules/jade/package.json new file mode 100644 index 0000000..2fce87a --- /dev/null +++ b/node_modules/jade/package.json @@ -0,0 +1,23 @@ +{ + "name": "jade", + "description": "Jade template engine", + "version": "0.20.0", + "author": "TJ Holowaychuk ", + "repository": "git://github.com/visionmedia/jade", + "main": "./index.js", + "bin": { "jade": "./bin/jade" }, + "dependencies": { + "commander": "0.2.x", + "mkdirp": ">= 0.0.7" + }, + "devDependencies": { + "mocha": "*", + "coffee-script": ">= 0.0.1", + "markdown": ">= 0.0.1", + "stylus": ">= 0.0.1", + "uubench": "0.0.1", + "uglify-js": ">= 1.0.7" + }, + "scripts" : { "prepublish" : "npm prune" }, + "engines": { "node": ">= 0.1.98" } +} diff --git a/node_modules/jade/runtime.js b/node_modules/jade/runtime.js new file mode 100644 index 0000000..39f8a28 --- /dev/null +++ b/node_modules/jade/runtime.js @@ -0,0 +1,123 @@ + +var jade = (function(exports){ +/*! + * Jade - runtime + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Lame Array.isArray() polyfill for now. + */ + +if (!Array.isArray) { + Array.isArray = function(arr){ + return '[object Array]' == Object.prototype.toString.call(arr); + }; +} + +/** + * Lame Object.keys() polyfill for now. + */ + +if (!Object.keys) { + Object.keys = function(obj){ + var arr = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + arr.push(key); + } + } + return arr; + } +} + +/** + * Render the given attributes object. + * + * @param {Object} obj + * @return {String} + * @api private + */ + +exports.attrs = function attrs(obj){ + var buf = [] + , terse = obj.terse; + delete obj.terse; + var keys = Object.keys(obj) + , len = keys.length; + if (len) { + buf.push(''); + for (var i = 0; i < len; ++i) { + var key = keys[i] + , val = obj[key]; + if ('boolean' == typeof val || null == val) { + if (val) { + terse + ? buf.push(key) + : buf.push(key + '="' + key + '"'); + } + } else if ('class' == key && Array.isArray(val)) { + buf.push(key + '="' + exports.escape(val.join(' ')) + '"'); + } else { + buf.push(key + '="' + exports.escape(val) + '"'); + } + } + } + return buf.join(' '); +}; + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function escape(html){ + return String(html) + .replace(/&(?!\w+;)/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +}; + +/** + * Re-throw the given `err` in context to the + * the jade in `filename` at the given `lineno`. + * + * @param {Error} err + * @param {String} filename + * @param {String} lineno + * @api private + */ + +exports.rethrow = function rethrow(err, filename, lineno){ + if (!filename) throw err; + + var context = 3 + , str = require('fs').readFileSync(filename, 'utf8') + , lines = str.split('\n') + , start = Math.max(lineno - context, 0) + , end = Math.min(lines.length, lineno + context); + + // Error context + var context = lines.slice(start, end).map(function(line, i){ + var curr = i + start + 1; + return (curr == lineno ? ' > ' : ' ') + + curr + + '| ' + + line; + }).join('\n'); + + // Alter exception message + err.path = filename; + err.message = (filename || 'Jade') + ':' + lineno + + '\n' + context + '\n\n' + err.message; + throw err; +}; + + return exports; + +})({}); \ No newline at end of file diff --git a/node_modules/jade/runtime.min.js b/node_modules/jade/runtime.min.js new file mode 100644 index 0000000..8c19a98 --- /dev/null +++ b/node_modules/jade/runtime.min.js @@ -0,0 +1 @@ +var jade=function(exports){return Array.isArray||(Array.isArray=function(arr){return"[object Array]"==Object.prototype.toString.call(arr)}),Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(key);return arr}),exports.attrs=function(obj){var buf=[],terse=obj.terse;delete obj.terse;var keys=Object.keys(obj),len=keys.length;if(len){buf.push("");for(var i=0;i/g,">").replace(/"/g,""")},exports.rethrow=function(err,filename,lineno){if(!filename)throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message,err},exports}({}) \ No newline at end of file diff --git a/node_modules/jade/testing/head.jade b/node_modules/jade/testing/head.jade new file mode 100644 index 0000000..8515406 --- /dev/null +++ b/node_modules/jade/testing/head.jade @@ -0,0 +1,5 @@ +head + script(src='/jquery.js') + yield + if false + script(src='/jquery.ui.js') diff --git a/node_modules/jade/testing/index.jade b/node_modules/jade/testing/index.jade new file mode 100644 index 0000000..0f7f511 --- /dev/null +++ b/node_modules/jade/testing/index.jade @@ -0,0 +1,5 @@ +html + body + include head + script(src='/caustic.js') + script(src='/app.js') diff --git a/node_modules/jade/testing/index.js b/node_modules/jade/testing/index.js new file mode 100644 index 0000000..776fa0f --- /dev/null +++ b/node_modules/jade/testing/index.js @@ -0,0 +1,11 @@ + +/** + * Module dependencies. + */ + +var jade = require('../'); + +jade.renderFile('testing/index.jade', { pretty: true }, function(err, str){ + if (err) throw err; + console.log(str); +}); \ No newline at end of file diff --git a/node_modules/jade/testing/layout.jade b/node_modules/jade/testing/layout.jade new file mode 100644 index 0000000..6923cf1 --- /dev/null +++ b/node_modules/jade/testing/layout.jade @@ -0,0 +1,6 @@ +html + include head + script(src='/caustic.js') + script(src='/app.js') + body + block content \ No newline at end of file diff --git a/node_modules/jade/testing/user.jade b/node_modules/jade/testing/user.jade new file mode 100644 index 0000000..7907ea1 --- /dev/null +++ b/node_modules/jade/testing/user.jade @@ -0,0 +1,3 @@ + +h1= user.name +p= user.occupation \ No newline at end of file diff --git a/node_modules/mongoose/.gitignore b/node_modules/mongoose/.gitignore new file mode 100644 index 0000000..fe1a335 --- /dev/null +++ b/node_modules/mongoose/.gitignore @@ -0,0 +1,6 @@ +lib-cov +**.swp +*.swo +*.swn +node_modules/ +*.orig diff --git a/node_modules/mongoose/History.md b/node_modules/mongoose/History.md new file mode 100644 index 0000000..24ce4ee --- /dev/null +++ b/node_modules/mongoose/History.md @@ -0,0 +1,716 @@ + +2.4.8 / 2011-12-22 +=================== + + * updated; bump -native to 0.9.7.2-5 + * fixed; compatibility with date.js (#646) [chrisleishman] + * changed; undocumented schema "lax" option to "strict" + * fixed; default value population for strict schemas + * updated; the nextTick helper for small performance gain. 1bee2a2 + +2.4.7 / 2011-12-16 +=================== + + * fixed; bug in 2.4.6 with path setting + * updated; bump -native to 0.9.7.2-1 + * added; strict schema option [nw] + +2.4.6 / 2011-12-16 +=================== + + * fixed; conflicting mods on update bug [sirlantis] + * improved; doc.id getter performance + +2.4.5 / 2011-12-14 +=================== + + * fixed; bad MongooseArray behavior in 2.4.2 - 2.4.4 + +2.4.4 / 2011-12-14 +=================== + + * fixed; MongooseArray#doAtomics throwing after sliced + +2.4.3 / 2011-12-14 +=================== + + * updated; system.profile schema for MongoDB 2x + +2.4.2 / 2011-12-12 +=================== + + * fixed; partially populating multiple children of subdocs (#639) [kenpratt] + * fixed; allow Update of numbers to null (#640) [jerem] + +2.4.1 / 2011-12-02 +=================== + + * added; options support for populate() queries + * updated; -native driver to 0.9.7-1.4 + +2.4.0 / 2011-11-29 +=================== + + * added; QueryStreams (#614) + * added; debug print mode for development + * added; $within support to Array queries (#586) [ggoodale] + * added; $centerSphere query support + * fixed; $within support + * added; $unset is now used when setting a path to undefined (#519) + * added; query#batchSize support + * updated; docs + * updated; -native driver to 0.9.7-1.3 (provides Windows support) + +2.3.13 / 2011-11-15 +=================== + + * fixed; required validation for Refs (#612) [ded] + * added; $nearSphere support for Arrays (#610) + +2.3.12 / 2011-11-09 +=================== + + * fixed; regression, objects passed to Model.update should not be changed (#605) + * fixed; regression, empty Model.update should not be executed + +2.3.11 / 2011-11-08 +=================== + + * fixed; using $elemMatch on arrays of Mixed types (#591) + * fixed; allow using $regex when querying Arrays (#599) + * fixed; calling Model.update with no atomic keys (#602) + +2.3.10 / 2011-11-05 +=================== + + * fixed; model.update casting for nested paths works (#542) + +2.3.9 / 2011-11-04 +================== + + * fixed; deepEquals check for MongooseArray returned false + * fixed; reset modified flags of embedded docs after save [gitfy] + * fixed; setting embedded doc with identical values no longer marks modified [gitfy] + * updated; -native driver to 0.9.6.23 [mlazarov] + * fixed; Model.update casting (#542, #545, #479) + * fixed; populated refs no longer fail required validators (#577) + * fixed; populating refs of objects with custom ids works + * fixed; $pop & $unset work with Model.update (#574) + * added; more helpful debugging message for Schema#add (#578) + * fixed; accessing .id when no _id exists now returns null (#590) + +2.3.8 / 2011-10-26 +================== + + * added; callback to query#findOne is now optional (#581) + +2.3.7 / 2011-10-24 +================== + + * fixed; wrapped save/remove callbacks in nextTick to mitigate -native swallowing thrown errors + +2.3.6 / 2011-10-21 +================== + + * fixed; exclusion of embedded doc _id from query results (#541) + +2.3.5 / 2011-10-19 +================== + + * fixed; calling queries without passing a callback works (#569) + * fixed; populate() works with String and Number _ids too (#568) + +2.3.4 / 2011-10-18 +================== + + * added; Model.create now accepts an array as a first arg + * fixed; calling toObject on a DocumentArray with nulls no longer throws + * fixed; calling inspect on a DocumentArray with nulls no longer throws + * added; MongooseArray#unshift support + * fixed; save hooks now fire on embedded documents [gitfy] (#456) + * updated; -native driver to 0.9.6-22 + * fixed; correctly pass $addToSet op instead of $push + * fixed; $addToSet properly detects dates + * fixed; $addToSet with multiple items works + * updated; better node 0.6 Buffer support + +2.3.3 / 2011-10-12 +================== + + * fixed; population conditions in multi-query settings [vedmalex] (#563) + * fixed; now compatible with Node v0.5.x + +2.3.2 / 2011-10-11 +================== + + * fixed; population of null subdoc properties no longer hangs (#561) + +2.3.1 / 2011-10-10 +================== + + * added; support for Query filters to populate() [eneko] + * fixed; querying with number no longer crashes mongodb (#555) [jlbyrey] + * updated; version of -native driver to 0.9.6-21 + * fixed; prevent query callbacks that throw errors from corrupting -native connection state + +2.3.0 / 2011-10-04 +================== + + * fixed; nulls as default values for Boolean now works as expected + * updated; version of -native driver to 0.9.6-20 + +2.2.4 / 2011-10-03 +================== + + * fixed; populate() works when returned array contains undefined/nulls + +2.2.3 / 2011-09-29 +================== + + * updated; version of -native driver to 0.9.6-19 + +2.2.2 / 2011-09-28 +================== + + * added; $regex support to String [davidandrewcope] + * added; support for other contexts like repl etc (#535) + * fixed; clear modified state properly after saving + * added; $addToSet support to Array + +2.2.1 / 2011-09-22 +================== + + * more descript error when casting undefined to string + * updated; version of -native driver to 0.9.6-18 + +2.2.0 / 2011-09-22 +================== + + * fixed; maxListeners warning on schemas with many arrays (#530) + * changed; return / apply defaults based on fields selected in query (#423) + * fixed; correctly detect Mixed types within schema arrays (#532) + +2.1.4 / 2011-09-20 +================== + + * fixed; new private methods that stomped on users code + * changed; finished removing old "compat" support which did nothing + +2.1.3 / 2011-09-16 +================== + + * updated; version of -native driver to 0.9.6-15 + * added; emit `error` on connection when open fails [edwardhotchkiss] + * added; index support to Buffers (thanks justmoon for helping track this down) + * fixed; passing collection name via schema in conn.model() now works (thanks vedmalex for reporting) + +2.1.2 / 2011-09-07 +================== + + * fixed; Query#find with no args no longer throws + +2.1.1 / 2011-09-07 +================== + + * added; support Model.count(fn) + * fixed; compatibility with node >=0.4.0 < 0.4.3 + * added; pass model.options.safe through with .save() so w:2, wtimeout:5000 options work [andrewjstone] + * added; support for $type queries + * added; support for Query#or + * added; more tests + * optimized populate queries + +2.1.0 / 2011-09-01 +================== + + * changed; document#validate is a public method + * fixed; setting number to same value no longer marks modified (#476) [gitfy] + * fixed; Buffers shouldn't have default vals + * added; allow specifying collection name in schema (#470) [ixti] + * fixed; reset modified paths and atomics after saved (#459) + * fixed; set isNew on embedded docs to false after save + * fixed; use self to ensure proper scope of options in doOpenSet (#483) [andrewjstone] + +2.0.4 / 2011-08-29 +================== + + * Fixed; Only send the depopulated ObjectId instead of the entire doc on save (DBRefs) + * Fixed; Properly cast nested array values in Model.update (the data was stored in Mongo incorrectly but recast on document fetch was "fixing" it) + +2.0.3 / 2011-08-28 +================== + + * Fixed; manipulating a populated array no longer causes infinite loop in BSON serializer during save (#477) + * Fixed; populating an empty array no longer hangs foreeeeeeeever (#481) + +2.0.2 / 2011-08-25 +================== + + * Fixed; Maintain query option key order (fixes 'bad hint' error from compound query hints) + +2.0.1 / 2011-08-25 +================== + + * Fixed; do not over-write the doc when no valide props exist in Model.update (#473) + +2.0.0 / 2011-08-24 +=================== + + * Added; support for Buffers [justmoon] + * Changed; improved error handling [maelstrom] + * Removed: unused utils.erase + * Fixed; support for passing other context object into Schemas (#234) [Sija] + * Fixed; getters are no longer circular refs to themselves (#366) + * Removed; unused compat.js + * Fixed; getter/setter scopes are set properly + * Changed; made several private properties more obvious by prefixing _ + * Added; DBRef support [guille] + * Changed; removed support for multiple collection names per model + * Fixed; no longer applying setters when document returned from db + * Changed; default auto_reconnect to true + * Changed; Query#bind no longer clones the query + * Fixed; Model.update now accepts $pull, $inc and friends (#404) + * Added; virtual type option support [nw] + +1.8.4 / 2011-08-21 +=================== + + * Fixed; validation bug when instantiated with non-schema properties (#464) [jmreidy] + +1.8.3 / 2011-08-19 +=================== + + * Fixed; regression in connection#open [jshaw86] + +1.8.2 / 2011-08-17 +=================== + + * fixed; reset connection.readyState after failure [tomseago] + * fixed; can now query positionally for non-embedded docs (arrays of numbers/strings etc) + * fixed; embedded document query casting + * added; support for passing options to node-mongo-native db, server, and replsetserver [tomseago] + +1.8.1 / 2011-08-10 +=================== + + * fixed; ObjectIds were always marked modified + * fixed; can now query using document instances + * fixed; can now query/update using documents with subdocs + +1.8.0 / 2011-08-04 +=================== + + * fixed; can now use $all with String and Number + * fixed; can query subdoc array with $ne: null + * fixed; instance.subdocs#id now works with custom _ids + * fixed; do not apply setters when doc returned from db (change in bad behavior) + +1.7.4 / 2011-07-25 +=================== + + * fixed; sparse now a valid seperate schema option + * fixed; now catching cast errors in queries + * fixed; calling new Schema with object created in vm.runInNewContext now works (#384) [Sija] + * fixed; String enum was disallowing null + * fixed; Find by nested document _id now works (#389) + +1.7.3 / 2011-07-16 +=================== + + * fixed; MongooseArray#indexOf now works with ObjectIds + * fixed; validation scope now set properly (#418) + * fixed; added missing colors dependency (#398) + +1.7.2 / 2011-07-13 +=================== + + * changed; node-mongodb-native driver to v0.9.6.7 + +1.7.1 / 2011-07-12 +=================== + + * changed; roll back node-mongodb-native driver to v0.9.6.4 + +1.7.0 / 2011-07-12 +=================== + + * fixed; collection name misspelling [mathrawka] + * fixed; 2nd param is required for ReplSetServers [kevinmarvin] + * fixed; MongooseArray behaves properly with Object.keys + * changed; node-mongodb-native driver to v0.9.6.6 + * fixed/changed; Mongodb segfault when passed invalid ObjectId (#407) + - This means invalid data passed to the ObjectId constructor will now error + +1.6.0 / 2011-07-07 +=================== + + * changed; .save() errors are now emitted on the instances db instead of the instance 9782463fc + * fixed; errors occurring when creating indexes now properly emit on db + * added; $maxDistance support to MongooseArrays + * fixed; RegExps now work with $all + * changed; node-mongodb-native driver to v0.9.6.4 + * fixed; model names are now accessible via .modelName + * added; Query#slaveOk support + +1.5.0 / 2011-06-27 +=================== + + * changed; saving without a callback no longer ignores the error (@bnoguchi) + * changed; hook-js version bump to 0.1.9 + * changed; node-mongodb-native version bumped to 0.9.6.1 - When .remove() doesn't + return an error, null is no longer passed. + * fixed; two memory leaks (@justmoon) + * added; sparse index support + * added; more ObjectId conditionals (gt, lt, gte, lte) (@phillyqueso) + * added; options are now passed in model#remote (@JerryLuke) + +1.4.0 / 2011-06-10 +=================== + + * bumped hooks-js dependency (fixes issue passing null as first arg to next()) + * fixed; document#inspect now works properly with nested docs + * fixed; 'set' now works as a schema attribute (GH-365) + * fixed; _id is now set properly within pre-init hooks (GH-289) + * added; Query#distinct / Model#distinct support (GH-155) + * fixed; embedded docs now can use instance methods (GH-249) + * fixed; can now overwrite strings conflicting with schema type + +1.3.7 / 2011-06-03 +=================== + + * added MongooseArray#splice support + * fixed; 'path' is now a valid Schema pathname + * improved hooks (utilizing https://github.com/bnoguchi/hooks-js) + * fixed; MongooseArray#$shift now works (never did) + * fixed; Document.modified no longer throws + * fixed; modifying subdoc property sets modified paths for subdoc and parent doc + * fixed; marking subdoc path as modified properly persists the value to the db + * fixed; RexExps can again be saved ( #357 ) + +1.3.6 / 2011-05-18 +=================== + + * fixed; corrected casting for queries against array types + * added; Document#set now accepts Document instances + +1.3.5 / 2011-05-17 +=================== + + * fixed; $ne queries work properly with single vals + * added; #inspect() methods to improve console.log output + +1.3.4 / 2011-05-17 +=================== + + * fixed; find by Date works as expected (#336) + * added; geospatial 2d index support + * added; support for $near (#309) + * updated; node-mongodb-native driver + * fixed; updating numbers work (#342) + * added; better error msg when try to remove an embedded doc without an _id (#307) + * added; support for 'on-the-fly' schemas (#227) + * changed; virtual id getters can now be skipped + * fixed; .index() called on subdoc schema now works as expected + * fixed; db.setProfile() now buffers until the db is open (#340) + +1.3.3 / 2011-04-27 +=================== + + * fixed; corrected query casting on nested mixed types + +1.3.2 / 2011-04-27 +=================== + + * fixed; query hints now retain key order + +1.3.1 / 2011-04-27 +=================== + + * fixed; setting a property on an embedded array no longer overwrites entire array (GH-310) + * fixed; setting nested properties works when sibling prop is named "type" + * fixed; isModified is now much finer grained when .set() is used (GH-323) + * fixed; mongoose.model() and connection.model() now return the Model (GH-308, GH-305) + * fixed; can now use $gt, $lt, $gte, $lte with String schema types (GH-317) + * fixed; .lowercase() -> .toLowerCase() in pluralize() + * fixed; updating an embedded document by index works (GH-334) + * changed; .save() now passes the instance to the callback (GH-294, GH-264) + * added; can now query system.profile and system.indexes collections + * added; db.model('system.profile') is now included as a default Schema + * added; db.setProfiling(level, ms, callback) + * added; Query#hint() support + * added; more tests + * updated node-mongodb-native to 0.9.3 + +1.3.0 / 2011-04-19 +=================== + + * changed; save() callbacks now fire only once on failed validation + * changed; Errors returned from save() callbacks now instances of ValidationError + * fixed; MongooseArray#indexOf now works properly + +1.2.0 / 2011-04-11 +=================== + + * changed; MongooseNumber now casts empty string to null + +1.1.25 / 2011-04-08 +=================== + + * fixed; post init now fires at proper time + +1.1.24 / 2011-04-03 +=================== + + * fixed; pushing an array onto an Array works on existing docs + +1.1.23 / 2011-04-01 +=================== + + * Added Model#model + +1.1.22 / 2011-03-31 +=================== + + * Fixed; $in queries on mixed types now work + +1.1.21 / 2011-03-31 +=================== + + * Fixed; setting object root to null/undefined works + +1.1.20 / 2011-03-31 +=================== + + * Fixed; setting multiple props on null field works + +1.1.19 / 2011-03-31 +=================== + + * Fixed; no longer using $set on paths to an unexisting fields + +1.1.18 / 2011-03-30 +=================== + + * Fixed; non-mixed type object setters work after initd from null + +1.1.17 / 2011-03-30 +=================== + + * Fixed; nested object property access works when root initd with null value + +1.1.16 / 2011-03-28 +=================== + + * Fixed; empty arrays are now saved + +1.1.15 / 2011-03-28 +=================== + + * Fixed; `null` and `undefined` are set atomically. + +1.1.14 / 2011-03-28 +=================== + + * Changed; more forgiving date casting, accepting '' as null. + +1.1.13 / 2011-03-26 +=================== + + * Fixed setting values as `undefined`. + +1.1.12 / 2011-03-26 +=================== + + * Fixed; nested objects now convert to JSON properly + * Fixed; setting nested objects directly now works + * Update node-mongodb-native + +1.1.11 / 2011-03-25 +=================== + + * Fixed for use of `type` as a key. + +1.1.10 / 2011-03-23 +=================== + + * Changed; Make sure to only ensure indexes while connected + +1.1.9 / 2011-03-2 +================== + + * Fixed; Mixed can now default to empty arrays + * Fixed; keys by the name 'type' are now valid + * Fixed; null values retrieved from the database are hydrated as null values. + * Fixed repeated atomic operations when saving a same document twice. + +1.1.8 / 2011-03-23 +================== + + * Fixed 'id' overriding. [bnoguchi] + +1.1.7 / 2011-03-22 +================== + + * Fixed RegExp query casting when querying against an Array of Strings [bnoguchi] + * Fixed getters/setters for nested virtualsl. [bnoguchi] + +1.1.6 / 2011-03-22 +================== + + * Only doValidate when path exists in Schema [aheckmann] + * Allow function defaults for Array types [aheckmann] + * Fix validation hang [aheckmann] + * Fix setting of isRequired of SchemaType [aheckmann] + * Fix SchemaType#required(false) filter [aheckmann] + * More backwards compatibility [aheckmann] + * More tests [aheckmann] + +1.1.5 / 2011-03-14 +================== + + * Added support for `uri, db, fn` and `uri, fn` signatures for replica sets. + * Improved/extended replica set tests. + +1.1.4 / 2011-03-09 +================== + + * Fixed; running an empty Query doesn't throw. [aheckmann] + * Changed; Promise#addBack returns promise. [aheckmann] + * Added streaming cursor support. [aheckmann] + * Changed; Query#update defaults to use$SetOnSave now. [brian] + * Added more docs. + +1.1.3 / 2011-03-04 +================== + + * Added Promise#resolve [aheckmann] + * Fixed backward compatibility with nulls [aheckmann] + * Changed; Query#{run,exec} return promises [aheckmann] + +1.1.2 / 2011-03-03 +================== + + * Restored Query#exec and added notion of default operation [brian] + * Fixed ValidatorError messages [brian] + +1.1.1 / 2011-03-01 +================== + + * Added SchemaType String `lowercase`, `uppercase`, `trim`. + * Public exports (`Model`, `Document`) and tests. + * Added ObjectId casting support for `Document`s. + +1.1.0 / 2011-02-25 +================== + + * Added support for replica sets. + +1.0.16 / 2011-02-18 +=================== + + * Added $nin as another whitelisted $conditional for SchemaArray [brian] + * Changed #with to #where [brian] + * Added ability to use $in conditional with Array types [brian] + +1.0.15 / 2011-02-18 +=================== + + * Added `id` virtual getter for documents to easily access the hexString of + the `_id`. + +1.0.14 / 2011-02-17 +=================== + + * Fix for arrays within subdocuments [brian] + +1.0.13 / 2011-02-16 +=================== + + * Fixed embedded documents saving. + +1.0.12 / 2011-02-14 +=================== + + * Minor refactorings [brian] + +1.0.11 / 2011-02-14 +=================== + + * Query refactor and $ne, $slice, $or, $size, $elemMatch, $nin, $exists support [brian] + * Named scopes sugar [brian] + +1.0.10 / 2011-02-11 +=================== + + * Updated node-mongodb-native driver [thanks John Allen] + +1.0.9 / 2011-02-09 +================== + + * Fixed single member arrays as defaults [brian] + +1.0.8 / 2011-02-09 +================== + + * Fixed for collection-level buffering of commands [gitfy] + * Fixed `Document#toJSON` [dalejefferson] + * Fixed `Connection` authentication [robrighter] + * Fixed clash of accessors in getters/setters [eirikurn] + * Improved `Model#save` promise handling + +1.0.7 / 2011-02-05 +================== + + * Fixed memory leak warnings for test suite on 0.3 + * Fixed querying documents that have an array that contain at least one + specified member. [brian] + * Fixed default value for Array types (fixes GH-210). [brian] + * Fixed example code. + +1.0.6 / 2011-02-03 +================== + + * Fixed `post` middleware + * Fixed; it's now possible to instantiate a model even when one of the paths maps + to an undefined value [brian] + +1.0.5 / 2011-02-02 +================== + + * Fixed; combo $push and $pushAll auto-converts into a $pushAll [brian] + * Fixed; combo $pull and $pullAll auto-converts to a single $pullAll [brian] + * Fixed; $pullAll now removes said members from array before save (so it acts just + like pushAll) [brian] + * Fixed; multiple $pulls and $pushes become a single $pullAll and $pushAll. + Moreover, $pull now modifies the array before save to reflect the immediate + change [brian] + * Added tests for nested shortcut getters [brian] + * Added tests that show that Schemas with nested Arrays don't apply defaults + [brian] + +1.0.4 / 2011-02-02 +================== + + * Added MongooseNumber#toString + * Added MongooseNumber unit tests + +1.0.3 / 2011-02-02 +================== + + * Make sure safe mode works with Model#save + * Changed Schema options: safe mode is now the default + * Updated node-mongodb-native to HEAD + +1.0.2 / 2011-02-02 +================== + + * Added a Model.create shortcut for creating documents. [brian] + * Fixed; we can now instantiate models with hashes that map to at least one + null value. [brian] + * Fixed Schema with more than 2 nested levels. [brian] + +1.0.1 / 2011-02-02 +================== + + * Improved `MongooseNumber`, works almost like the native except for `typeof` + not being `'number'`. diff --git a/node_modules/mongoose/Makefile b/node_modules/mongoose/Makefile new file mode 100644 index 0000000..cdbcc19 --- /dev/null +++ b/node_modules/mongoose/Makefile @@ -0,0 +1,25 @@ + +TESTS = $(shell find test/ -name '*.test.js') + +test: + @NODE_ENV=test ./support/expresso/bin/expresso \ + $(TESTFLAGS) \ + $(TESTS) + @node test/dropdb.js + +test-cov: + @TESTFLAGS=--cov $(MAKE) test + +docs: docs/api.html + +docs/api.html: lib/mongoose/*.js + dox \ + --private \ + --title Mongooose \ + --desc "Expressive MongoDB for Node.JS" \ + $(shell find lib/mongoose/* -type f) > $@ + +docclean: + rm -f docs/*.{1,html} + +.PHONY: test test-cov docs docclean diff --git a/node_modules/mongoose/README.md b/node_modules/mongoose/README.md new file mode 100644 index 0000000..6d75d3c --- /dev/null +++ b/node_modules/mongoose/README.md @@ -0,0 +1,417 @@ +Mongoose 2.0 +============ + +## What's Mongoose? + +Mongoose is a [MongoDB](http://www.mongodb.org/) object modeling tool designed to work in an asynchronous +environment. + +Defining a model is as easy as: + +```javascript +var Comments = new Schema({ + title : String + , body : String + , date : Date +}); + +var BlogPost = new Schema({ + author : ObjectId + , title : String + , body : String + , buf : Buffer + , date : Date + , comments : [Comments] + , meta : { + votes : Number + , favs : Number + } +}); + +var Post = mongoose.model('BlogPost', BlogPost); +``` + +## Installation + +The recommended way is through the excellent [NPM](http://www.npmjs.org/): + +```bash +$ npm install mongoose +``` + +Otherwise, you can check it in your repository and then expose it: + +```bash +$ git clone git://github.com/LearnBoost/mongoose.git node_modules/mongoose/ +``` +And install dependency modules written on `package.json`. + +Then you can `require` it: + +```javascript +require('mongoose') +``` + +## Connecting to MongoDB + +First, we need to define a connection. If your app uses only one database, you +should use `mongose.connect`. If you need to create additional connections, use +`mongoose.createConnection`. + +Both `connect` and `createConnection` take a `mongodb://` URI, or the parameters +`host, database, port, options`. + +```javascript +var mongoose = require('mongoose'); + +mongoose.connect('mongodb://localhost/my_database'); +``` + +Once connected, the `open` event is fired on the `Connection` instance. If +you're using `mongoose.connect`, the `Connection` is `mongoose.connection`. +Otherwise, `mongoose.createConnection` return value is a `Connection`. + +**Important!** Mongoose buffers all the commands until it's connected to the +database. This means that you don't have to wait until it connects to MongoDB +in order to define models, run queries, etc. + +## Defining a Model + +Models are defined through the `Schema` interface. + +```javascript +var Schema = mongoose.Schema + , ObjectId = Schema.ObjectId; + +var BlogPost = new Schema({ + author : ObjectId + , title : String + , body : String + , date : Date +}); +``` + +Aside from defining the structure of your documents and the types of data you're +storing, a Schema handles the definition of: + +* [Validators](http://mongoosejs.com/docs/validation.html) (async and sync) +* [Defaults](http://mongoosejs.com/docs/schematypes.html) +* [Getters](http://mongoosejs.com/docs/getters-setters.html) +* [Setters](http://mongoosejs.com/docs/getters-setters.html) +* [Indexes](http://mongoosejs.com/docs/indexes.html) +* [Middleware](http://mongoosejs.com/docs/middleware.html) +* [Methods](http://mongoosejs.com/docs/methods-statics.html) definition +* [Statics](http://mongoosejs.com/docs/methods-statics.html) definition +* [Plugins](http://mongoosejs.com/docs/plugins.html) +* [DBRefs](http://mongoosejs.com/docs/dbrefs.html) + +The following example shows some of these features: + +```javascript +var Comment = new Schema({ + name : { type: String, default: 'hahaha' } + , age : { type: Number, min: 18, index: true } + , bio : { type: String, match: /[a-z]/ } + , date : { type: Date, default: Date.now } + , buff : Buffer +}); + +// a setter +Comment.path('name').set(function (v) { + return capitalize(v); +}); + +// middleware +Comment.pre('save', function (next) { + notify(this.get('email')); + next(); +}); +``` + +Take a look at the example in `examples/schema.js` for an end-to-end example of +a typical setup. + +## Accessing a Model + +Once we define a model through `mongoose.model('ModelName', mySchema)`, we can +access it through the same function + +```javascript +var myModel = mongoose.model('ModelName'); +``` + +Or just do it all at once + +```javascript +var MyModel = mongoose.model('ModelName', mySchema); +``` + +We can then instantiate it, and save it: + +```javascript +var instance = new MyModel(); +instance.my.key = 'hello'; +instance.save(function (err) { + // +}); +``` + +Or we can find documents from the same collection + +```javascript +MyModel.find({}, function (err, docs) { + // docs.forEach +}); +``` + +You can also `findOne`, `findById`, `update`, etc. For more details check out +[this link](http://mongoosejs.com/docs/finding-documents.html). + +## Embedded Documents + +In the first example snippet, we defined a key in the Schema that looks like: + +``` +comments: [Comments] +``` + +Where `Comments` is a `Schema` we created. This means that creating embedded +documents is as simple as: + +```javascript +// retrieve my model +var BlogPost = mongoose.model('BlogPost'); + +// create a blog post +var post = new BlogPost(); + +// create a comment +post.comments.push({ title: 'My comment' }); + +post.save(function (err) { + if (!err) console.log('Success!'); +}); +``` + +The same goes for removing them: + +```javascript +BlogPost.findById(myId, function (err, post) { + if (!err) { + post.comments[0].remove(); + post.save(function (err) { + // do something + }); + } +}); +``` + +Embedded documents enjoy all the same features as your models. Defaults, +validators, middleware. Whenever an error occurs, it's bubbled to the `save()` +error callback, so error handling is a snap! + +Mongoose interacts with your embedded documents in arrays _atomically_, out of +the box. + +## Middleware + +Middleware is one of the most exciting features about Mongoose. Middleware +takes away all the pain of nested callbacks. + +Middleware are defined at the Schema level and are applied for the methods +`init` (when a document is initialized with data from MongoDB), `save` (when +a document or embedded document is saved). + +There's two types of middleware: + +- Serial + Serial middleware are defined like: + +```javascript +.pre(method, function (next, methodArg1, methodArg2, ...) { + // ... +}) +``` + + They're executed one after the other, when each middleware calls `next`. + + You can also intercept the `method`'s incoming arguments via your middleware -- + notice `methodArg1`, `methodArg2`, etc in the `pre` definition above. See + section "Intercepting and mutating method arguments" below. + + +- Parallel + Parallel middleware offer more fine-grained flow control, and are defined + like: + +```javascript +.pre(method, true, function (next, done, methodArg1, methodArg2) { + // ... +}) +``` + + Parallel middleware can `next()` immediately, but the final argument will be + called when all the parallel middleware have called `done()`. + +### Error handling + +If any middleware calls `next` or `done` with an `Error` instance, the flow is +interrupted, and the error is passed to the function passed as an argument. + +For example: + +```javascript +schema.pre('save', function (next) { + // something goes wrong + next(new Error('something went wrong')); +}); + +// later... + +myModel.save(function (err) { + // err can come from a middleware +}); +``` + +### Intercepting and mutating method arguments + +You can intercept method arguments via middleware. + +For example, this would allow you to broadcast changes about your Documents +every time someone `set`s a path in your Document to a new value: + +```javascript +schema.pre('set', function (next, path, val, typel) { + // `this` is the current Document + this.emit('set', path, val); + + // Pass control to the next pre + next(); +}); +``` + +Moreover, you can mutate the incoming `method` arguments so that subsequent +middleware see different values for those arguments. To do so, just pass the +new values to `next`: + +```javascript +.pre(method, function firstPre (next, methodArg1, methodArg2) { + // Mutate methodArg1 + next("altered-" + methodArg1.toString(), methodArg2); +}) + +// pre declaration is chainable +.pre(method, function secondPre (next, methodArg1, methodArg2) { + console.log(methodArg1); + // => 'altered-originalValOfMethodArg1' + + console.log(methodArg2); + // => 'originalValOfMethodArg2' + + // Passing no arguments to `next` automatically passes along the current argument values + // i.e., the following `next()` is equivalent to `next(methodArg1, methodArg2)` + // and also equivalent to, with the example method arg + // values, `next('altered-originalValOfMethodArg1', 'originalValOfMethodArg2')` + next(); +}) +``` + +### Schema gotcha + +`type`, when used in a schema has special meaning within Mongoose. If your +schema requires using `type` as a nested property you must use object notation: + +``` javascript +new Schema({ + broken: { type: Boolean } + , asset : { + name: String + , type: String // uh oh, it broke. asset will be interpreted as String + } +}); + +new Schema({ + works: { type: Boolean } + , asset : { + name: String + , type: { type: String } // works. asset is an object with a type property + } +}); +``` + +## API docs + +You can find the [Dox](http://github.com/visionmedia/dox) generated API docs +[here](http://mongoosejs.com/docs/api.html). + +## Getting support + +Please subscribe to the Google Groups [mailing +list](http://groups.google.com/group/mongoose-orm). + +Join #mongoosejs on freenode. + +## Driver access + +The driver being used defaults to [node-mongodb-native](https://github.com/christkv/node-mongodb-native) and is directly accessible through `YourModel.collection`. **Note**: using the driver directly bypasses all Mongoose power-tools like validation, getters, setters, hooks, etc. + +## Mongoose Plugins + +The following plugins are currently available for use with mongoose: + +- [mongoose-types](https://github.com/bnoguchi/mongoose-types) - Adds + several additional types (e.g., Email) that you can use in your + Schema declarations +- [mongoose-auth](https://github.com/bnoguchi/mongoose-auth) - A drop in + solution for your auth needs. Currently supports Password, Facebook, + Twitter, Github, and more. +- [mongoose-joins](https://github.com/goulash1971/mongoose-joins) - Adds simple join support +- [mongoose-dbref](https://github.com/goulash1971/mongoose-dbref) - An alternative DBRef option +- [mongoose-flatmatcher](https://github.com/marksweiss/mongoose-flatmatcher) - A query pre-processor that maps flat name/value pairs to schemas + +## Contributing to Mongoose + +### Cloning the repository + +Make a fork of `mongoose`, then clone it in your computer. The `v2.x` branch +contains the current stable release, and the `master` branch the next upcoming +major release. + +### Guidelines + +- Please write inline documentation for new methods or class members. +- Please write tests and make sure your tests pass. +- Before starting to write code, look for existing tickets or create one for + your specific issue (unless you're addressing something that's clearly broken). + That way you avoid working on something that might not be of interest or that + has been addressed already in a different branch. + +## Credits + +- Guillermo Rauch - guillermo@learnboost.com - [Guille](http://github.com/guille) +- Nathan White - [nw](http://github.com/nw/) +- Brian Noguchi - [bnoguchi](https://github.com/bnoguchi) +- Aaron Heckmann - [aheckmann](https://github.com/aheckmann) + +## License + +Copyright (c) 2010-2011 LearnBoost <dev@learnboost.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/mongoose/benchmarks/clone.js b/node_modules/mongoose/benchmarks/clone.js new file mode 100644 index 0000000..45aa6a7 --- /dev/null +++ b/node_modules/mongoose/benchmarks/clone.js @@ -0,0 +1,60 @@ + +var mongoose = require('../') + , utils = require('../lib/utils') + , clone = utils.clone + , Schema = mongoose.Schema + +var DocSchema = new Schema({ + title: String +}); + +var AllSchema = new Schema({ + string: { type: String, required: true } + , number: { type: Number, min: 10 } + , date : Date + , bool : Boolean + , buffer: Buffer + , objectid: Schema.ObjectId + , array : Array + , strings: [String] + , numbers: [Number] + , dates : [Date] + , bools : [Boolean] + , buffers: [Buffer] + , objectids: [Schema.ObjectId] + , docs : { type: [DocSchema], validate: function () { return true }} + , s: { nest: String } +}); + +var A = mongoose.model('A', AllSchema); +var a = new A({ + string: "hello world" + , number: 444848484 + , date: new Date + , bool: true + , buffer: new Buffer(0) + , objectid: new mongoose.Types.ObjectId() + , array: [4,{},[],"asdfa"] + , strings: ["one","two","three","four"] + , numbers:[72,6493,83984643,348282.55] + , dates:[new Date, new Date, new Date] + , bools:[true, false, false, true, true] + , buffers: [new Buffer([33]), new Buffer([12])] + , objectids: [new mongoose.Types.ObjectId] + , docs: [ {title: "yo"}, {title:"nowafasdi0fas asjkdfla fa" }] + , s: { nest: 'hello there everyone!' } +}); + +var start = new Date; +var total = 100000; +var i = total; + +for (var i = 0, len = total; i < len; ++i) { + a.toObject({ depopulate: true }); +} + +var time= (new Date - start)/1000; +console.error('took %d seconds for %d docs (%d dps)', time, total, total/time); +var used = process.memoryUsage(); + +// --trace-opt --trace-deopt --trace-bailout diff --git a/node_modules/mongoose/benchmarks/index.js b/node_modules/mongoose/benchmarks/index.js new file mode 100644 index 0000000..4695113 --- /dev/null +++ b/node_modules/mongoose/benchmarks/index.js @@ -0,0 +1,128 @@ +Error.stackTraceLimit = Infinity; +var mongoose = require('../') + , Schema = mongoose.Schema; + +var DocSchema = new Schema({ + title: String +}); + +var AllSchema = new Schema({ + string: String + , number: Number + , date : Date + , bool : Boolean + , buffer: Buffer + , objectid: Schema.ObjectId + , array : Array + , strings: [String] + , numbers: [Number] + , dates : [Date] + , bools : [Boolean] + , buffers: [Buffer] + , objectids: [Schema.ObjectId] + , docs : [DocSchema] +}); + +var A = mongoose.model('A', AllSchema); + +// bench the normal way +// the try building the doc into the document prototype +// and using inheritance and bench that +// +// also, bench using listeners for each subdoc vs one +// listener that knows about all subdocs and notifies +// them. + +function run (label, fn) { + console.error('running %s', label); + var started = process.memoryUsage(); + var start = new Date; + var total = 10000; + var i = total; + while (i--) { + a = fn(); + if (i%2) + a.toObject({ depopulate: true }); + else + a._delta(); + } + var time = (new Date - start)/1000; + console.error(label + ' took %d seconds for %d docs (%d dps)', time, total, total/time); + var used = process.memoryUsage(); + console.error(((used.vsize - started.vsize) / 1048576)+' MB'); +} + +run('string', function () { + return new A({ + string: "hello world" + }); +}) +run('number', function () { + return new A({ + number: 444848484 + }); +}) +run('date', function () { + return new A({ + date: new Date + }); +}) +run('bool', function () { + return new A({ + bool: true + }); +}) +run('buffer', function () { + return new A({ + buffer: new Buffer(0) + }); +}) +run('objectid', function () { + return new A({ + objectid: new mongoose.Types.ObjectId() + }); +}) +run('array of mixed', function () { + return new A({ + array: [4,{},[],"asdfa"] + }); +}) +run('array of strings', function () { + return new A({ + strings: ["one","two","three","four"] + }); +}) +run('array of numbers', function () { + return new A({ + numbers:[72,6493,83984643,348282.55] + }); +}) +run('array of dates', function () { + return new A({ + dates:[new Date, new Date, new Date] + }); +}) +run('array of bools', function () { + return new A({ + bools:[true, false, false, true, true] + }); +}) +run('array of buffers', function () { + return new A({ + buffers: [new Buffer([33]), new Buffer([12])] + }); +}) +run('array of objectids', function () { + return new A({ + objectids: [new mongoose.Types.ObjectId] + }); +}) +run('array of docs', function () { + return new A({ + docs: [ {title: "yo"}, {title:"nowafasdi0fas asjkdfla fa" }] + }); +}) + +//console.error(a.toObject({depopulate:true})); + +// --trace-opt --trace-deopt --trace-bailout diff --git a/node_modules/mongoose/benchmarks/mem.js b/node_modules/mongoose/benchmarks/mem.js new file mode 100644 index 0000000..8ce0cfe --- /dev/null +++ b/node_modules/mongoose/benchmarks/mem.js @@ -0,0 +1,149 @@ + +var mongoose = require('../') + , Schema = mongoose.Schema; + +var db = mongoose.connect('localhost', 'testing_bench'); + +var DocSchema = new Schema({ + title: String +}); + +var AllSchema = new Schema({ + string: { type: String, required: true } + , number: { type: Number, min: 10 } + , date : Date + , bool : Boolean + , buffer: Buffer + , objectid: Schema.ObjectId + , array : Array + , strings: [String] + , numbers: [Number] + , dates : [Date] + , bools : [Boolean] + , buffers: [Buffer] + , objectids: [Schema.ObjectId] + , docs : { type: [DocSchema], validate: function () { return true }} + , s: { nest: String } +}); + +var A = mongoose.model('A', AllSchema); + +var methods = []; +methods.push(function (a, cb) { + A.findOne({ _id: a._id }, cb); +}); // 2 MB +methods.push(function (a, cb) { + A.find({ _id: a._id, bool: a.bool }, cb); +}); // 3.8 MB +methods.push(function (a, cb) { + A.findById(a._id, cb); +}); // 4.6 MB +methods.push(function (a, cb) { + A.where('number', a.number).sort('_id', -1).limit(10).run(cb) +}); // 4.8 MB +methods.push(function (a, cb) { + A.where('date', a.date).select('string').limit(10).run(cb) +}); // 3.5 mb +methods.push(function (a, cb) { + A.where('date', a.date).select('string', 'bool').asc('date').limit(10).run(cb) +}); // 3.5 MB +methods.push(function (a, cb) { + A.find('date', a.date).where('array').$in(3).limit(10).run(cb) +}); // 1.82 MB +methods.push(function (a, cb) { + A.update({ _id: a._id }, { $addToset: { array: "heeeeello" }}, cb); +}); // 3.32 MB +methods.push(function (a, cb) { + A.remove({ _id: a._id }, cb); +}); // 3.32 MB +methods.push(function (a, cb) { + A.find().where('objectids').exists().only('dates').limit(10).exec(cb); +}); // 3.32 MB +methods.push(function (a, cb) { + A.count({ strings: a.strings[2], number: a.number }, cb); +}); // 3.32 MB +methods.push(function (a, cb) { + a.string= "asdfaf"; + a.number = 38383838; + a.date= new Date; + a.bool = false; + a.array.push(3); + a.dates.push(new Date); + a.bools.$pushAll([true, false]); + a.docs.$addToSet({ title: 'woot' }); + a.strings.remove("three"); + a.numbers.$pull(72); + a.objectids.$pop(); + a.docs.$pullAll(a.docs); + a.s.nest = "aooooooga"; + + if (i%2) + a.toObject({ depopulate: true }); + else + a._delta(); + + cb(); +}); + +var started = process.memoryUsage(); +var start = new Date; +var total = 500; +var i = total; + +mongoose.connection.on('open', function () { + mongoose.connection.db.dropDatabase(function () { + + ;(function cycle () { + if (0 === i--) return done(); + + var a = new A({ + string: "hello world" + , number: 444848484 + , date: new Date + , bool: true + , buffer: new Buffer(0) + , objectid: new mongoose.Types.ObjectId() + , array: [4,{},[],"asdfa"] + , strings: ["one","two","three","four"] + , numbers:[72,6493,83984643,348282.55] + , dates:[new Date, new Date, new Date] + , bools:[true, false, false, true, true] + , buffers: [new Buffer([33]), new Buffer([12])] + , objectids: [new mongoose.Types.ObjectId] + , docs: [ {title: "yo"}, {title:"nowafasdi0fas asjkdfla fa" }] + }); + + a.save(function (err) { + methods[Math.random()*methods.length|0](a, function () { + process.nextTick(cycle); + }) + }); + + //if (i%2) + //a.toObject({ depopulate: true }); + //else + //a._delta(); + + if (!(i%50)) { + var u = process.memoryUsage(); + console.error('rss: %d, vsize: %d, heapTotal: %d, heapUsed: %d', + u.rss, u.vsize, u.heapTotal, u.heapUsed); + } + })() + + function done () { + var time= (new Date - start)/1000; + console.error('took %d seconds for %d docs (%d dps)', time, total, total/time); + var used = process.memoryUsage(); + console.error(((used.vsize - started.vsize) / 1048576)+' MB'); + + //console.error(a.toObject({depopulate:true})); + + mongoose.connection.db.dropDatabase(function () { + mongoose.connection.close(); + }); + } + + // --trace-opt --trace-deopt --trace-bailout + }) +}) diff --git a/node_modules/mongoose/docs/defaults.md b/node_modules/mongoose/docs/defaults.md new file mode 100644 index 0000000..0497787 --- /dev/null +++ b/node_modules/mongoose/docs/defaults.md @@ -0,0 +1,29 @@ + +Defaults +======== + +Each `SchemaType` that you define \(you can read more about them in the [model +definition chapter](/docs/model-definition.html) \) can have a default value. + +Default values are applied when the document skeleton is constructed. This +means that if you create a new document (`new MyModel`) or if you find an +existing document (`MyModel.findById`), both will have defaults +provided that a certain key is missing. + +## Definition + +You can define a default with a function: + + new Schema({ + date: { type: Date, default: Date.now } + }) + +or a value: + + new Schema({ + date: { type: Date, default: '12/10/1990' } + }) + +Notice that defaults are automatically casted. In both cases, the defaults will +become actual `Date` objects, but we're passing a timestamp first, and a string +date second. diff --git a/node_modules/mongoose/docs/embedded-documents.md b/node_modules/mongoose/docs/embedded-documents.md new file mode 100644 index 0000000..10b6aae --- /dev/null +++ b/node_modules/mongoose/docs/embedded-documents.md @@ -0,0 +1,79 @@ + +Embedded Documents +================== + +Embedded documents are documents with schemas of their own that are part of +other documents (as items within an array). + +Embedded documents enjoy all the same features as your models. Defaults, +validators, middleware. Whenever an error occurs, it's bubbled to the `save()` +error callback, so error handling is a snap! + +Mongoose interacts with your embedded documents in arrays _atomically_, out of +the box. + +## Definition and initialization + +When you define a Schema like this: + + var Comments = new Schema({ + title : String + , body : String + , date : Date + }); + + var BlogPost = new Schema({ + author : ObjectId + , title : String + , body : String + , date : Date + , comments : [Comments] + , meta : { + votes : Number + , favs : Number + } + }); + + mongoose.model('BlogPost', BlogPost); + +The `comments` key of your `BlogPost` documents will then be an instance of +`DocumentArray`. This is a special subclassed `Array` that can deal with +casting, and has special methods to work with embedded documents. + +## Adding an embedded document to an array + + // retrieve my model + var BlogPost = mongoose.model('BlogPost'); + + // create a blog post + var post = new BlogPost(); + + // create a comment + post.comments.push({ title: 'My comment' }); + + post.save(function (err) { + if (!err) console.log('Success!'); + }); + +## Removing an embedded document + + BlogPost.findById(myId, function (err, post) { + if (!err) { + post.comments[0].remove(); + post.save(function (err) { + // do something + }); + } + }); + +## Finding an embedded document by id + +`DocumentArray`s have an special method `id` that filters your embedded +documents by their `_id` property (each embedded document gets one): + +Consider the following snippet: + + post.comments.id(my_id).remove(); + post.save(function (err) { + // embedded comment with id `my_id` removed! + }); diff --git a/node_modules/mongoose/docs/errors.md b/node_modules/mongoose/docs/errors.md new file mode 100644 index 0000000..a1852ef --- /dev/null +++ b/node_modules/mongoose/docs/errors.md @@ -0,0 +1,54 @@ + +Error handling +============== + +Errors returned after failed validation contain an `errors` object +holding the actual ValidatorErrors. Each ValidatorError has a `type` and `path` property +providing us with a little more error handling flexibility. + + var ToySchema = new Schema({ + color: String + , name: String + }); + + var Toy = db.model('Toy', ToySchema); + + Toy.schema.path('name').validate(function (value) { + return /blue|green|white|red|orange|periwinkel/i.test(value); + }, 'Invalid color'); + + var toy = new Toy({ color: 'grease'}); + + toy.save(function (err) { + // previous behavior (v1x): + + console.log(err.errors.color) + // prints 'Validator "Invalid color" failed for path color' + + // new v2x behavior - err.errors.color is a ValidatorError object + + console.log(err.errors.color.message) + // prints 'Validator "Invalid color" failed for path color' + + // you can get v1 behavior back by casting error.color toString + + console.log(String(err.errors.color)) + // prints 'Validator "Invalid color" failed for path color' + + console.log(err.errors.color.type); + // prints "Invalid color" + + console.log(err.errors.color.path) + // prints "color" + + console.log(err.name) + // prints "ValidationError" + + console.log(err.message) + // prints "Validation failed" + }); + +BTW, the `err.errors` object is also available on the model instance. + + toy.errors.color.message === err.errors.color.message + diff --git a/node_modules/mongoose/docs/finding-documents.md b/node_modules/mongoose/docs/finding-documents.md new file mode 100644 index 0000000..db6e25d --- /dev/null +++ b/node_modules/mongoose/docs/finding-documents.md @@ -0,0 +1,124 @@ + +Querying +================= + +Documents can be retrieved through `find`, `findOne` and `findById`. These +methods are executed on your `Model`s. + +## Model.find + + Model.find(query, fields, options, callback) + + // fields and options can be omitted + +### Simple query: + + Model.find({ 'some.value': 5 }, function (err, docs) { + // docs is an array + }); + +### Retrieving only certain fields + + Model.find({}, ['first', 'last'], function (err, docs) { + // docs is an array of partially-`init`d documents + // defaults are still applied and will be "populated" + }) + +## Model.findOne + +Same as `Model#find`, but only receives a single document as second parameter: + + Model.findOne({ age: 5}, function (err, doc){ + // doc is a Document + }); + +## Model.findById + +Same as `findOne`, but receives a value to search a document by their `_id` +key. This value is subject to casting, so it can be a hex string or a proper +ObjectId. + + Model.findById(obj._id, function (err, doc){ + // doc is a Document + }); + +## Model.count + +Counts the number of documents matching `conditions`. + + Model.count(conditions, callback); + +## Model.remove + +Removes documents matching `conditions`. + + Model.remove(conditions, callback); + +## Model.distinct + +Finds distinct values of `field` for documents matching `conditions`. + + Model.distinct(field, conditions, callback); + +## Model.where + +Creates a Query for this model. +Handy when expressing complex directives. + + Model + .where('age').gte(25) + .where('tags').in(['movie', 'music', 'art']) + .select('name', 'age', 'tags') + .skip(20) + .limit(10) + .asc('age') + .slaveOk() + .hint({ age: 1, name: 1 }) + .run(callback); + +## Model.$where + +Sometimes you need to query for things in mongodb using a JavaScript +expression. You can do so via find({$where: javascript}), or you can +use the mongoose shortcut method $where via a Query chain or from +your mongoose Model. + + Model.$where('this.firstname === this.lastname').exec(callback) + +## Model.update + +Updates all documents matching `conditions` using the `update` clause. All +`update` values are casted to their appropriate types before being sent. + + var conditions = { name: 'borne' } + , update = { $inc: { visits: 1 }} + , options = { multi: true }; + + Model.update(conditions, update, options, callback) + +Note: for backwards compatibility, all top-level `update` keys that are +not $atomic operation names are treated as `$set` operations. Example: + + var query = { name: 'borne' }; + Model.update(query, { name: 'jason borne' }, options, callback) + + // is sent as + + Model.update(query, { $set: { name: 'jason borne' }}, options, callback) + +## Query + +Each of these methods returns a [Query](https://github.com/LearnBoost/mongoose/blob/master/lib/query.js). +If you don't pass a callback to these methods, the Query can be continued to be +modified (such as adding options, fields, etc), before it's `exec`d. + + var query = Model.find({}); + + query.where('field', 5); + query.limit(5); + query.skip(100); + + query.exec(function (err, docs) { + // called when the `query.complete` or `query.error` are called + // internally + }); diff --git a/node_modules/mongoose/docs/getters-setters.md b/node_modules/mongoose/docs/getters-setters.md new file mode 100644 index 0000000..7cf7b94 --- /dev/null +++ b/node_modules/mongoose/docs/getters-setters.md @@ -0,0 +1,52 @@ +Getters and Setters +==================== + +Getters and setters help you change how you get and set the attributes defined by the keys and values in the underlying raw document. + +## Setters + +Setters allow you to transform the mongoose document's data before it gets to the raw mongodb document and is set as a value on an actual key. + +Suppose you are implementing user registration for a website. User provide an email and password, which gets saved to mongodb. The email is a string that you will want to normalize to lower case, in order to avoid one email having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM. + +You can set up email lower case normalization easily via a Mongoose setter. Note in the following snippet that setters (and also getters) are defined in the `Schema`: + + function toLower (v) { + return v.toLowerCase(); + } + + var UserSchema = new Schema({ + email: { type: String, set: toLower } + }); + + var User = mongoose.model('User', UserSchema); + var user = new User({email: 'AVENUE@Q.COM'}); + + console.log(user.email); // 'avenue@q.com' + + +As you can see above, setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key. + +## Getters + +Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see. + +Suppose you are storing credit card numbers and you want to hide everything except the last 4 digits to the mongoose user. You can do so by defining a getter in the following way (again, notice that getters are defined in the `Schema`): + + function obfuscate (cc) { + return '****-****-****-' + cc.slice(cc.length-4, cc.length); + } + + var AccountSchema = new Schema({ + creditCardNumber: { type: String, get: obfuscate } + }); + + var Account = mongoose.model('Account', AccountSchema); + + Account.findById( someId, function (err, found) { + console.log(found.creditCardNumber); // '****-****-****-1234' + }); + +## Summary + +Setters are intended to modify the underlying raw data. Getters are intended to transform (but not modify at the raw data level) the underlying raw data into something that the user expects to see. They are both defined in the `Schema` definition. diff --git a/node_modules/mongoose/docs/indexes.md b/node_modules/mongoose/docs/indexes.md new file mode 100644 index 0000000..f294318 --- /dev/null +++ b/node_modules/mongoose/docs/indexes.md @@ -0,0 +1,43 @@ + +Indexes +======= + +Indexes are defined through `ensureIndex` every time a model is compiled for a +certain connection / database. This means that indexes will only be ensured +once during the lifetime of your app. + +## Definition + +Regular indexes: + + var User = new Schema({ + name: { type: String, index: true } + }) + +[Sparse](http://www.mongodb.org/display/DOCS/Indexes#Indexes-SparseIndexes) indexes: + + var User = new Schema({ + name: { type: String, sparse: true } + }) + +Unique indexes: + + var User = new Schema({ + name: { type: String, unique: true } + }) + + // or + + var User = new Schema({ + name: { type: String, index: { unique: true } } + }) + +Unique sparse indexes: + + var User = new Schema({ + name: { type: String, unique: true, sparse: true } + }) + +Compound indexes are defined on the `Schema` itself. + + User.index({ first: 1, last: -1 }, { unique: true }) diff --git a/node_modules/mongoose/docs/methods-statics.md b/node_modules/mongoose/docs/methods-statics.md new file mode 100644 index 0000000..27b5902 --- /dev/null +++ b/node_modules/mongoose/docs/methods-statics.md @@ -0,0 +1,55 @@ +Methods and Statics +==================== + +Each `Schema` can define instance and static methods for its model. + +## Methods + +Methods are easy to define: + + var AnimalSchema = new Schema({ + name: String + , type: String + }); + + AnimalSchema.methods.findSimilarType = function findSimilarType (cb) { + return this.find({ type: this.type }, cb); + }; + +Now when we have an instance of `Animal` we can call our `findSimilarType` method and +find all animals with a matching `type`. + + var Animal = mongoose.model('Animal', AnimalSchema); + var dog = new Animal({ name: 'Rover', type: 'dog' }); + + dog.findSimilarType(function (err, dogs) { + if (err) return ... + dogs.forEach(..); + }) + +Note that we return what `.find()` returns in our method. The advantages are two-fold. +First, by passing `cb` into `find` we are making it optional b/c `find` called +without a callback will not run the query. Secondly, `this.find`, `this.where`, +and other Model methods return instances of [Query](/docs/finding-documents.html) +which allow us to further utilize its expressive capabilities. + + dog + .findSimilarType() + .where('name': /rover/i) + .limit(20) + .run(function (err, rovers) { + if (err) ... + }) + +## Statics + +Statics are pretty much the same as methods but allow for defining functions that +exist directly on your Model. + + AnimalSchema.statics.search = function search (name, cb) { + return this.where('name', new RegExp(name, 'i')).run(cb); + } + + Animal.search('Rover', function (err) { + if (err) ... + }) diff --git a/node_modules/mongoose/docs/middleware.md b/node_modules/mongoose/docs/middleware.md new file mode 100644 index 0000000..947006d --- /dev/null +++ b/node_modules/mongoose/docs/middleware.md @@ -0,0 +1,61 @@ + +Middleware +========== + +Middleware are defined at the Schema level and are applied when the methods +`init` (when a document is initialized with data from MongoDB), `save`, and +`remove` are called on a document instance. + +There are two types of middleware, serial and parallel. + +Serial middleware are defined like: + + schema.pre('save', function (next) { + // ... + }) + +They're executed one after the other, when each middleware calls `next`. + +Parallel middleware offer more fine-grained flow control, and are defined +like + + schema.pre('remove', true, function (next, done) { + // ... + }) + +Parallel middleware can `next()` immediately, but the final argument will be +called when all the parallel middleware have called `done()`. + +## Use cases + +Middleware are useful for: + +- Complex validation +- Removing dependent documents when a certain document is removed (eg: +removing a user removes all his blogposts) +- Asynchronous defaults +- Asynchronous tasks that a certain action triggers. For example: + - Triggering custom events + - Creating notifications + - Emails + +and many other things. They're specially useful for atomizing model logic +and avoiding nested blocks of async code. + +## Error handling + +If any middleware calls `next` or `done` with an `Error` instance, the flow is +interrupted, and the error is passed to the callback. + +For example: + + schema.pre('save', function (next) { + // something goes wrong + next(new Error('something went wrong')); + }); + + // later... + + myModel.save(function (err) { + // err can come from a middleware + }); diff --git a/node_modules/mongoose/docs/migration-guide.md b/node_modules/mongoose/docs/migration-guide.md new file mode 100644 index 0000000..214bd4e --- /dev/null +++ b/node_modules/mongoose/docs/migration-guide.md @@ -0,0 +1,148 @@ + +Migrating from v1.x to 2.x +========================== + +Migrating from __v1.x__ to __2.x__ brings with it a few changes to be aware of. + +## Auto-reconnect + +Previously the `auto_reconnect` option of the node-mongodb-driver +defaulted to false. It now defaults to true so if your connection drops +while your app is running the driver will continue retrying until it +can connect again. + +## Private props + +Several internal instance props have had name changes so its more obvious that +they are not intended for public use. Namely `instance.doc` has changed +to `instance._doc` since it contains the structure Mongoose relies on +to operate properly and should only be manipulated with caution. + +Here are the relavent changes: + + var thing = new Thing; + + thing.doc -> thing._doc + thing.activePaths -> thing._activePaths + thing.saveError -> thing._saveError + thing.validationError -> thing._validationError + +## Circular refs in getters + +Previously Mongoose exibited very odd behavior with getters: + + toy.color.color.color.color ... // actually worked! + +Obviously this was wrong and has now been fixed. + + toy.color.color // undefined + +## Getter / Setter scope + +Nested getter/setter scopes were set incorrectly since version 1.7 or so. +This has been fixed. In your getter/setter, `this` now properly refers +to the instance. + + var SongSchema = new Schema({ + title: String + , detail: { + format: String + } + }); + + SongSchema.path('detail.format').get(function () { + console.log(this !== this.detail) // true, used to be false + }); + +You may not have noticed this bug since the circular getters previously +masked (_mostly_) this bad behavior. + +## Setters application + +Setters are no longer applied when the doc returns from the db (bug). It +caused problems for folks trying to use setters for passwords / salts +resulting in doubly hashed passwords after queries. + + UserSchema.path('password').set(function (val) { + // now only runs when you change `user.password` + // not when the doc returns from the db + }); + +## Query#bind + +If you were using the `Query` object directly and calling its `bind` +method, the v1.x behavior cloned the query and returned the +new one. This is no longer the case. The query is now simply +bound and returns itself. + +## Multiple collection support removed + +In 1.x Mongoose had support for multiple collection names per model. This +was an edge case and support for it has been removed. + +## Compat.js removed + +Backward compatibility with verions 0.x has been removed. + + require('mongoose').compat = true // no longer does anything + +## Utils.erase removed + +We removed utils.erase since it was unused in the project. If you were +using it you'll need to copy it from the 1.x branch into your own. + +## Error handling + +Previously, the error returned after failed validation contained an `errors` +object which was a hash of path keys to error message values. +Now the Error returned is more helpful. Instead of the `errors` +object containing string values it holds the actual +ValidatorError. Each ValidatorError has a `type` and `path` property +providing us with a little more error handling flexibility. + + var ToySchema = new Schema({ + color: String + , name: String + }); + + var Toy = db.model('Toy', ToySchema); + + Toy.schema.path('name').validate(function (value) { + return /blue|green|white|red|orange|periwinkel/i.test(value); + }, 'Invalid color'); + + var toy = new Toy({ color: 'grease'}); + + toy.save(function (err) { + // previous behavior (v1x): + + console.log(err.errors.color) + // prints 'Validator "Invalid color" failed for path color' + + // new v2x behavior - err.errors.color is a ValidatorError object + + console.log(err.errors.color.message) + // prints 'Validator "Invalid color" failed for path color' + + // you can get v1 behavior back by casting error.color toString + + console.log(String(err.errors.color)) + // prints 'Validator "Invalid color" failed for path color' + + console.log(err.errors.color.type); + // prints "Invalid color" + + console.log(err.errors.color.path) + // prints "color" + + console.log(err.name) + // prints "ValidationError" + + console.log(err.message) + // prints "Validation failed" + }); + +BTW, the `err.errors` object is also available on the model instance. + + toy.errors.color.message === err.errors.color.message + diff --git a/node_modules/mongoose/docs/model-definition.md b/node_modules/mongoose/docs/model-definition.md new file mode 100644 index 0000000..69700ff --- /dev/null +++ b/node_modules/mongoose/docs/model-definition.md @@ -0,0 +1,142 @@ +Defining a model +================ + +Models are defined by passing a `Schema` instance to `mongoose.model`. + + mongoose.model('MyModel', mySchema); + // mySchema is + +You can easily access the `Schema` constructor from the `mongoose` singleton: + + var mongoose = require('mongoose') + , Schema = mongoose.Schema; + + var mySchema = new Schema({ + // my props + }); + +Models are then accessed from `mongoose` if you want to use a single +connection: + + // connect the `mongoose` instance + mongoose.connect('mongodb://host/db'); + + var BlogPost = mongoose.model('BlogPost'); + +Or from a `Connection` instance if you want to use multiple +databases/connections: + + var db = mongoose.createConnection('mongodb://host/db') + , BlogPost = db.model('BlogPost'); + +**Important**: the actual interaction with the data happens with the `Model` +that you obtain through `mongoose.model` or `db.model`. That's the object that +you can instantiate or that you can call `.find()`, `.findOne()`, etc upon. +Don't confuse schemas and actual models! + +## Defining your keys + +The `Schema` constructor receives an object representation of your schemas as +its first parameter. If you want to add more keys later, `Schema#add` provides +the same functionality. + +Your schema is constructed by passing all the +JavaScript natives that you know (String, Number, Date, Buffer) as well +as others exclusive to MongoDb (for example `Schema.ObjectId`). For details on all +SchemaTypes see the [Schema Type chapter](/docs/schematypes.html). + + var ObjectId = Schema.ObjectId; + + var PostSchema = new Schema({ + owner : ObjectId + , title : String + , date : Date + }); + +### Defining documents within documents + +To define an array of documents that follows a certain schema, make the value +an array with the schema constructor inside. + +For example, let's assume we want to have a collection of comments within a +blogpost, and we want them to be subject to casting, validation, and other +functionality provided by models: + + var Comment = new Schema({ + body : String + , date : Date + }); + + var Post = new Schema({ + title : String + , comments : [Comment] + }); + +This will allow you to interact very easily with subdocuments later on. For +more information, refer to the chapter on +[embedded documents](/docs/embedded-documents.html). + +### Defining custom options for keys + +Each key that you define is internally mapped to a `SchemaType`. Bear in mind, a +Schema is not something that you interact directly with, but it's a way to +describe to Mongoose what your want your data to look like, and how you want +it to behave. + +`SchemaType`s take care of validation, casting, defaults, and other general +options. Some functionality is exclusive to certain types of `SchemaType`s, for +example only numbers support `min` and `max` values. + +In order to customize some of these options directly from the definition of +your model, set your key to an object with the format `{ type: Type, ... }`. + + var Person = new Schema({ + title : { type: String, required: true } + , age : { type: Number, min: 5, max: 20 } + , meta : { + likes : [String] + , birth : { type: Date, default: Date.now } + } + }); + +Those options are functions that are called on each SchemaType. +If you want to define options later on, you could access a certain key through +the `path` function: + + Person.path('age').max(400); + + Person.path('meta.birth').set(function (v) { + // this is a setter + }); + + Person.path('title').validate(function (v) { + return v.length > 50; + }); + +Some of the options are versatile. `default` takes a `Function` or a value. +`validate` takes a `Function` or a `RegExp`. More information on these can be +found in the [Schema Type chapter](/docs/schematypes.html). + +## Beyond keys: Middleware + +Middleware are special user-defined functions that are called transparently +when certain native methods are called on `Document` instances (`init`, `save` +and `remove`). + +Let's say that you want to email a certain user when his document changes. +You'd then define a hook on the User schema like this: + + User.pre('save', function (next) { + email(this.email, 'Your record has changed'); + next(); + }); + +More information about the specifics of middleware can be found [here](/docs/middleware.html). + +## Instance Methods and Static methods + +For details about defining your own custom static and instance methods read [this](/docs/methods-statics.html). + +## Plugins + +Schemas also support plugins. Read more about it on the [Plugins](/docs/plugins.html) page. diff --git a/node_modules/mongoose/docs/plugins.md b/node_modules/mongoose/docs/plugins.md new file mode 100644 index 0000000..2d22a55 --- /dev/null +++ b/node_modules/mongoose/docs/plugins.md @@ -0,0 +1,38 @@ +Plugins +==================== + +`Schema`s are pluggable, that is, they allow for applying pre-packaged +capabilities to extend their functionality. + +Suppose that we have several collections in our database and want +to add last-modified functionality to each one. With plugins this +is easy. Just create a plugin once and apply it to each `Schema`: + + // lastMod.js + module.exports = exports = function lastModifiedPlugin (schema, options) { + schema.add({ lastMod: Date }) + + schema.pre('save', function (next) { + this.lastMod = new Date + next() + }) + + if (options && options.index) { + schema.path('lastMod').index(options.index) + } + } + + // in your schema files + var lastMod = require('./lastMod'); + + var Game = new Schema({ ... }); + + Game.plugin(lastMod); + + var Player = new Schema({ ... }); + + Player.plugin(lastMod, { index: true }); + +In the example above we added last-modified functionality to both the Game +and Player schemas. We also took advantage of options passing supported by +the `plugin()` method to dynamically define an index on the Player. diff --git a/node_modules/mongoose/docs/populate.md b/node_modules/mongoose/docs/populate.md new file mode 100644 index 0000000..6e6aa0c --- /dev/null +++ b/node_modules/mongoose/docs/populate.md @@ -0,0 +1,119 @@ +Populate - DBRef-like behavior (experimental) +============================================= + +`ObjectIds` can now refer to another document in a +collection within our database and be `populate()`d when +querying. An example is helpful: + + var mongoose = require('mongoose') + , Schema = mongoose.Schema + + var PersonSchema = new Schema({ + name : String + , age : Number + , stories : [{ type: Schema.ObjectId, ref: 'Story' }] + }); + + var StorySchema = new Schema({ + _creator : { type: Schema.ObjectId, ref: 'Person' } + , title : String + , fans : [{ type: Schema.ObjectId, ref: 'Person' }] + }); + + var Story = mongoose.model('Story', StorySchema); + var Person = mongoose.model('Person', PersonSchema); + +So far we've created two models. Our `Person` model has it's `stories` field +set to an array of `ObjectId`s. The `ref` option is what tells Mongoose in which +model to look, in our case the `Story` model. All `_id`s we +store here must be document `_id`s from the `Story` model. We also added +a `_creator` `ObjectId` to our `Story` schema which refers to a single `Person`. + +## Populating the refs + + var aaron = new Person({ name: 'Aaron', age: 100 }); + + aaron.save(function (err) { + if (err) ... + + var story1 = new Story({ + title: "A man who cooked Nintendo" + , _creator: aaron._id + }); + + story1.save(function (err) { + if (err) ... + }); + }) + +So far we haven't done anything special. We've merely created a `Person` and +a `Story`. Now let's take a look at populating our story's `_creator`: + + Story + .findOne({ title: /Nintendo/i }) + .populate('_creator') // <-- + .run(function (err, story) { + if (err) .. + console.log('The creator is %s', story._creator.name); + // prints "The creator is Aaron" + }) + +Yup that's it. We've just queried for a `Story` with the term Nintendo in it's +title and also queried the `Person` collection for the story's creator. Nice! + +Arrays of `ObjectId` refs work the same way. Just call the `populate` method on the query and +an array of documents will be returned in place of the `ObjectId`s. + +What if we only want a few specific fields returned for the query? This can +be accomplished by passing an array of field names to the `populate` method: + + Story + .findOne({ title: /Nintendo/i }) + .populate('_creator', ['name']) // <-- only return the Persons name + .run(function (err, story) { + if (err) .. + + console.log('The creator is %s', story._creator.name); + // prints "The creator is Aaron" + + console.log('The creators age is %s', story._creator.age) + // prints "The creators age is null' + }) + +Now this is much better. The only property of the creator we are using +is the `name` so we only returned that field from the db. Efficiency FTW! + +## Updating + +Now that we have a story we realized that the `_creator` was incorrect. We can +update `ObjectId` refs the same as any other property through the magic of Mongooses +internal casting: + + var guille = new Person({ name: 'Guillermo' }); + guille.save(function (err) { + if (err) .. + + story._creator = guille; // or guille._id + + story.save(function (err) { + if (err) .. + + Story + .findOne({ title: /Nintendo/i }) + .populate('_creator', ['name']) + .run(function (err, story) { + if (err) .. + + console.log('The creator is %s', story._creator.name) + // prints "The creator is Guillermo" + }) + + }) + }) + +### Note: + +The documents returned from calling `populate` become fully functional, +`remove`able, `save`able documents. Do not confuse them with embedded +docs. Take caution when calling its `remove` method because +you'll be removing it from the database, not just the array. diff --git a/node_modules/mongoose/docs/query.md b/node_modules/mongoose/docs/query.md new file mode 100644 index 0000000..d12892e --- /dev/null +++ b/node_modules/mongoose/docs/query.md @@ -0,0 +1,362 @@ + +Queries +================= + +A `Query` is what is returned when calling many `Model` +[methods](/docs/finding-documents.html). These `Query` +objects provide a chaining api to specify search terms, +cursor options, hints, and other behavior. + +## Query#where + +Lets you specify query terms with sugar. + + query.where(path [, val]) + +`path` is a valid document path. `val` is optional. Its useful to omit +`val` when used in conjunction with other query methods. For example: + + query + .where('name', 'Space Ghost') + .where('age').gte(21).lte(65) + .run(callback) + +In this case, `gte()` and `lte()` operate on the previous path if not +explicitly passed. The above query results in the following query expression: + + { name: 'Space Ghost', age: { $gte: 21, $lte: 65 }} + +## Query#$where + +Specifies the javascript function to run on MongoDB on each document scanned. +See the [MongoDB docs](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-JavascriptExpressionsand%7B%7B%24where%7D%7D) for details. + + Model.find().$where(fn) + +`fn` can be either a function or a string. + +## Query#$or, Query#or + +Specifies the [$or](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24or) operator. + + query.$or(array); + +`array` is an array of expressions. + + query.or([{ color: 'blue' }, { color: 'red' }]); + +## Query#gt, Query#$gt + +Specifies a [greater than](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%3C%2C%3C%3D%2C%3E%2C%3E%3D) expression. + + query.$gt(path, val); + +`$gt` is also one of the methods with extra chaining sugar: when only one +argument is passed, it uses the path used the last call to `where()`. Example: + + Model.where('age').$gt(21) + +Results in: + + { age: { $gt: 21 }} + +## Query#gte, Query#$gte + +Specifies a [greater than or equal to](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%3C%2C%3C%3D%2C%3E%2C%3E%3D) expression. + + query.$gte(path, val); + +`$gte` is also one of the methods with extra chaining sugar: when only one +argument is passed, it uses the path used the last call to `where()`. Example: + + Model.where('age').$gte(21) + +Results in: + + { age: { $gte: 21 }} + +## Query#lt,Query#$lt + +Specifies a [less than](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%3C%2C%3C%3D%2C%3E%2C%3E%3D) expression. + + query.$lt(path, val); + +`$lt` is also one of the methods with extra chaining sugar: when only one +argument is passed, it uses the path used the last call to `where()`. Example: + + Model.where('age').$lt(21) + +Results in: + + { age: { $lt: 21 }} + +## Query#lte, Query#$lte + +Specifies a [less than or equal to](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%3C%2C%3C%3D%2C%3E%2C%3E%3D) expression. + + query.$lte(path, val); + +`$lte` is also one of the methods with extra chaining sugar: when only one +argument is passed, it uses the path used the last call to `where()`. Example: + + Model.where('age').$lte(21) + +Results in: + + { age: { $lte: 21 }} + +## Query#ne, Query#$ne, Query#notEqualTo + +Specifies the [$ne](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24ne) operator. + + query.$ne(path, val); + +These methods have extra sugar in that +when only one argument is passed, the path in the last call +to `where()` is used. Example: + + query.where('_id').ne('4ecf92f31993a52c58e07f6a') + +and + + query.notEqualTo('_id', '4ecf92f31993a52c58e07f6a') + +both result in + + { _id: { $ne: ObjectId('4ecf92f31993a52c58e07f6a') }} + +## Query#in, Query#$in + +Specifies the [$in](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24in) operator. + + query.$in(path, array) + +These methods have extra sugar in that +when only one argument is passed, the path in the last call +to `where()` is used. + + query.where('tags').in(['game', 'fun', 'holiday']) + +results in + + { tags: { $in: ['game', 'fun', 'holiday'] }} + +## Query#nin, Query#$nin + +Specifies the [$nin](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24nin) operator. + + query.$nin(path, array) + +These methods have extra sugar in that +when only one argument is passed, the path in the last call +to `where()` is used. + + query.where('games').nin(['boring', 'lame']) + +results in + + { games: { $nin: ['boring', 'lame'] }} + +## Query#all, Query#$all + +Specifies the [$all](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24all) operator. + + query.$all(path, array) + +These methods have extra sugar in that +when only one argument is passed, the path in the last call +to `where()` is used. + + query.where('games').all(['fun', 'exhausting']) + +results in + + { games: { $all: ['fun', 'exhausting'] }} + +## Query#regex, Query#$regex + +Specifies the [$regex](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-RegularExpressions) operator. + + query.regex('name.first', /^a/i) + +These methods have extra sugar in that +when only one argument is passed, the path in the last call +to `where()` is used. + + query.where('name.first').$regex(/^a/i) + +results in + + { 'name.first': { $regex: /^a/i }} + +## Query#size, Query#$size + +Specifies the [$size](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24size) operator. + + query.$size(path, integer) + +These methods have extra sugar in that +when only one argument is passed, the path in the last call +to `where()` is used. + + query.size('comments', 2) + query.where('comments').size(2) + +both result in + + { comments: { $size: 2 }} + +## Query#mod, Query#$mod + +Specifies the [$mod](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24mod) operator. + + var array = [10, 1] + query.mod(path, array) + + query.mod(path, 10, 1) + + query.where(path).$mod(10, 1) + +all result in + + { path: { $mod: [10, 1] }} + +## Query#exists, Query#$exists + +Specifies the [$exists](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24exists) operator. + + query.exists(path, Boolean) + +These methods have extra sugar in that +when only one argument is passed, the path from the last call +to `where()` is used. + +Given the following query, + + var query = Model.find(); + +the following queries + + query.exists('occupation'); + query.exists('occupation', true); + query.where('occupation').exists(); + +all result in + + { occupation: { $exists: true }} + +Another example: + + query.exists('occupation', false) + query.where('occupation').exists(false); + +result in + + { occupation: { $exists: false }} + +## Query#elemMatch, Query#$elemMatch + +Specifies the [$elemMatch](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24elemMatch) operator. + + query.elemMatch(path, criteria) + query.where(path).elemMatch(criteria) + +Functions can also be passed so you can further use query sugar +to declare the expression: + + query.where(path).elemMatch(function) + query.elemMatch(path, function) + +In this case a `query` is passed as the only argument into the function. + + query.where('comments').elemMatch(function (elem) { + elem.where('author', 'bnoguchi') + elem.where('votes').gte(5); + }); + +Results in + + { comments: { $elemMatch: { author: 'bnoguchi', votes: { $gte: 5 }}}} + +## Query#within, Query#$within + +## Query#box, Query#$box + +## Query#center, Query#$center + +## Query#centerSphere, Query#$centerSphere + +## Query#near, Query#$near + +Specifies the [$near](http://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-Querying) operator. + + var array = [10, 1] + query.near(path, array) + + query.near(path, 10, 1) + + query.where(path).$near(10, 1) + +all result in + + { path: { $near: [10, 1] }} + +## Query#maxDistance, Query#$maxDistance + +Specifies the [$maxDistance](http://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-Querying) operator. + + query.where('checkin').near([40, -72]).maxDistance(1); + +results in + + { checkin: { $near: [40, -72], $maxDistance: 1 }} + +## Query#select +## Query#fields + +## Query#only +## Query#exclude + +## Query#slice +## Query#$slice + +## Query#populate + +// sorting +## Query#sort +## Query#asc +## Query#desc + +// options +## Query#limit +## Query#skip +## Query#maxscan +## Query#snapshot +## Query#batchSize +## Query#slaveOk +## Query#hint + +// executing +## Query#find + + query.find(criteria [, callback]) + +## Query#findOne +## Query#count +## Query#distinct +## Query#update +## Query#remove + +## Query#run +## Query#exec + + query.run([callback]) + +## Query#each + +## Query#stream + +Returns a [QueryStream](/docs/querystream.html) for the query. + + Model.find({}).stream().pipe(writeStream) + diff --git a/node_modules/mongoose/docs/querystream.md b/node_modules/mongoose/docs/querystream.md new file mode 100644 index 0000000..a2a7300 --- /dev/null +++ b/node_modules/mongoose/docs/querystream.md @@ -0,0 +1,6 @@ + +Querystreams +================= + +Docs coming soon + diff --git a/node_modules/mongoose/docs/schematypes.md b/node_modules/mongoose/docs/schematypes.md new file mode 100644 index 0000000..f1e2787 --- /dev/null +++ b/node_modules/mongoose/docs/schematypes.md @@ -0,0 +1,197 @@ + +Schema Types +============ + +`SchemaType`s take care of validation, casting, defaults, and other general +options in our models. We can specify our types one of two ways: + + // directly without options + var Person = new Schema({ + title : String + }); + + // or with options + var Person = new Schema({ + title : { type: String, lowercase: true } + }); + +In the example above we specified the `lowercase` option for strings which +will lowercase the string whenever it is set. Options are functions that are +called on each SchemaType. Each `SchemaType` has its own set of custom options. + +## Available Schema Types + +### String + + - `lowercase`: {Boolean} + + Creates a setter which calls `.toLowerCase()` on the value + + - `uppercase`: {Boolean} + + Creates a setter which calls `.toUpperCase()` on the value + + - `trim`: {Boolean} + + Creates a setter which calls `.trim()` on the value + + - `match`: {RegExp} + + Creates a RegExp based [validator](/docs/validation.html). The value being set is `.test()`ed + against the RegExp. If it does not pass, validation will fail. + + - `enum`: {Array} + + Creates an enum validator. If the value being set is not in this + array, validation will fail. + +### Number + + - `min`: {Number} + + Creates a validator which checks that the value being set is not less + than the value specified. + + - `max`: {Number} + + Creates a validator which checks that the value being set is not greater + than the value specified. + +### Date + + - no custom options + +### Boolean + + - no custom options + +### Buffer (v2.x only) + + - no custom options + +### ObjectId + + To specify a type of `ObjectId`, use `Schema.ObjectId` in your declaration. + + var mongoose = require('mongoose'); + var Schema = mongoose.Schema; + var Car = new Schema({ driver: Schema.ObjectId }) + + - no custom options + +### Mixed + + An "anything goes" `SchemaType`, its flexibility comes at a trade-off of it being + harder to maintain. `Mixed` is available either through `Schema.Types.Mixed` or + by passing an empty object literal. The following are equivalent: + + var Any = new Schema({ any: {} }); + var Any = new Schema({ any: Schema.Types.Mixed }); + + Since it is a schema-less type, you can change the value to anything else + you like, but Mongoose loses the ability to auto detect/save those changes. + To "tell" Mongoose that the value of a `Mixed` type has changed, call + the `.markModified(path)` method of the document passing the path to + the `Mixed` type you just changed. + + person.anything = { x: [3, 4, { y: "changed" }] }; + person.markModified('anything'); + person.save(); // anything will now get saved + + - no custom options + +### Array + + Creates an array of `SchemaTypes` or [Embedded Documents](/docs/embedded-documents.html). + + var ToySchema = new Schema({ name: String }); + var ToyBox = new Schema({ + toys: [ToySchema] + , buffers: [Buffer] + , string: [String] + , numbers: [Number] + ... etc + }); + + Note: specifying an empty array is equivalent to `[Mixed]`. The following all + create arrays of `Mixed`: + + var Empty1 = new Schema({ any: [] }); + var Empty2 = new Schema({ any: Array }); + var Empty3 = new Schema({ any: [Schema.Types.Mixed] }); + var Empty4 = new Schema({ any: [{}] }); + + - no custom options + +## Additional options + +Besides the options listed above, all SchemaTypes share the following additional +options. + + - `default`: {Function|value} - Determines the default value for the path. All values are casted. If using a function, the value it returns will be casted as the default value. + + - `required`: {Boolean} - If true, creates a validation rule requiring this path be set before saving occurs. + + - `get`: {Function} - Adds a getter for this path. See the [getters / setters](/docs/getters-setters.html) docs for more detail. + + - `set`: {Function} - Adds a setter for this path. See the [getters / setters](/docs/getters-setters.html) docs for more detail. + + - `index`: {Boolean|Object} - Tells Mongoose to ensure an index is created for this path. An object can be passed as well. + + var Person = new Schema({ name: String, index: true }) + var Person = new Schema({ name: String, index: { unique: true }}) + + Note: indexes cannot be created for `Buffer` `SchemaTypes`.
    + Note: if the index already exists on the db, it will _not_ be replaced. + + - `unique`: {Boolean} - Tells Mongoose to ensure a unique index is created for this path. The following are equivalent: + + var Person = new Schema({ name: String, unique: true }) + var Person = new Schema({ name: String, index: { unique: true }}) + + Note: indexes cannot be created for `Buffer` `SchemaTypes`.
    + Note: if the index already exists on the db, it will _not_ be replaced. + + - `sparse`: {Boolean} - Tells Mongoose to ensure a sparse index is created for this path. The following are equivalent: + + var Person = new Schema({ name: String, sparse: true }) + var Person = new Schema({ name: String, index: { sparse: true }}) + + Note: indexes cannot be created for `Buffer` `SchemaTypes`.
    + Note: if the index already exists on the db, it will _not_ be replaced. + + - `validate`: {Function|RegExp|Array} - Creates a [validator](/docs/validation.html) for this path. + + // passing a function + function hasNumber (v) { + return v.length && /\d/.test(v); + } + var Person = new Schema({ street: String, validate: hasNumber }); + + // passing a RegExp + var Person = new Schema({ street: String, validate: /\d/ }); + + // passing an array + var Person = new Schema({ street: String, validate: [hasNumber, 'street number required'] }); + + // or + var Person = new Schema({ street: String, validate: [/\d/, 'street number required'] }); + + For more detail about validation including async validation, see the [validation](/docs/validation.html) page. + +## Alternate options definition + +Instead of defining options when instanciating your `Schema` we can also +access keys through the `path` function and add options there: + + Person.path('age').max(400); + + Person.path('meta.birth').set(function (v) { + // this is a setter + }); + + Person.path('title').validate(function (v) { + return v.length > 50; + }); + + diff --git a/node_modules/mongoose/docs/validation.md b/node_modules/mongoose/docs/validation.md new file mode 100644 index 0000000..7748e8b --- /dev/null +++ b/node_modules/mongoose/docs/validation.md @@ -0,0 +1,92 @@ + +Validation in models +==================== + +Before we get into the specifics of validation syntax, please keep the +following rules in mind: + +- Validation is defined in the `Schema` + +- Validation occurs when a document attempts to be saved, after defaults have +been applied. + +- Mongoose doesn't care about complex error message construction. Errors have +type identifiers. For example, `"min"` is the identifier for the error +triggered when a number doesn't meet the minimum value. The path and value +that triggered the error can be accessed in the `ValidationError` object. + +- Validation is an internal piece of middleware + +- Validation is asynchronously recursive: when you call `Model#save`, embedded + documents validation is executed. If an error happens, your `Model#save` + callback receives it. + +## Simple validation + +Simple validation is declared by passing a function to `validate` and an error +type to your `SchemaType` \(please read the chapter on [model definition](/docs/model-definition.html) to learn +more about schemas). + + function validator (v) { + return v.length > 5; + }; + + new Schema({ + name: { type: String, validate: [validator, 'my error type'] } + }) + +If you find this syntax too clumsy, you can also define the type + + var schema = new Schema({ + name: String + }) + +and then your validator + + schema.path('name').validate(function (v) { + return v.length > 5; + }, 'my error type'); + +## Regular expressions + +If you want to test a certain value against a regular expression: + + var schema = new Schema({ + name: { type: String, validate: /[a-z]/ } + }); + +## Asynchronous validation + +If you define a validator function with two parameters, like: + + schema.path('name').validate(function (v, fn) { + // my logic + }, 'my error type'); + +Then the function `fn` has to be called with `true` or `false`, depending on +whether the validator passed. This allows for calling other models and querying +data asynchronously from your validator. + +## Built in validators + +Strings: + + - `enum`: takes a list of allowed values. Example: + + var Post = new Schema({ + type: { type: String, enum: ['page', 'post', 'link'] } + }) + +Numbers: + + - `min`: minimum value + + var Person = new Schema({ + age: { type: Number, min: 5 } + }) + + - `max`: maxmimum value + + var Person = new Schema({ + age: { type: Number, max: 100 } + }) diff --git a/node_modules/mongoose/docs/virtuals.md b/node_modules/mongoose/docs/virtuals.md new file mode 100644 index 0000000..65bc2bf --- /dev/null +++ b/node_modules/mongoose/docs/virtuals.md @@ -0,0 +1,84 @@ +Virtual attributes +==================== + +Mongoose supports virtual attributes. Virtual attributes are attributes +that are convenient to have around but that do not get persisted to mongodb. + +An example is helpful. + +## Example +Take the following schema: + + var PersonSchema = new Schema({ + name: { + first: String + , last: String + } + }); + + var Person = mongoose.model('Person', PersonSchema); + + var theSituation = new Person({name: { first: 'Michael', last: 'Sorrentino' }}); + +Suppose you want to write `theSituation`'s full name. You could do so via: + + console.log(theSituation.name.first + ' ' + theSituation.name.last); + +It is more convenient to define a virtual attribute, `name.full`, so you can instead write: + + console.log(theSituation.name.full); + +To do so, you can declare a virtual attribute on the Schema, `Person`: + + PersonSchema + .virtual('name.full') + .get(function () { + return this.name.first + ' ' + this.name.last; + }); + +So when you get `name.full`, via + + theSituation.name.full; + +the implementation ends up invoking the getter function + + function () { + return this.name.first + ' ' + this.name.last; + } + +and returning it. + +It would also be nice to be able to set `this.name.first` and `this.name.last` by setting `this.name.full`. For example, it would be nice if we wanted to change theSituation's `name.first` and `name.last` to 'The' and 'Situation' respectively just by invoking: + + theSituation.set('name.full', 'The Situation'); + +Mongoose allows you to do this, too, via virtual attribute setters. You can define a virtual attribute setter thusly: + + PersonSchema + .virtual('name.full') + .get(function () { + return this.name.first + ' ' + this.name.last; + }) + .set(function (setFullNameTo) { + var split = setFullNameTo.split(' ') + , firstName = split[0] + , lastName = split[1]; + + this.set('name.first', firstName); + this.set('name.last', lastName); + }); + +Then, when you invoke: + + theSituation.set('name.full', 'The Situation'); + +and you save the document, then `name.first` and `name.last` will be changed in monbodb, but the mongodb document will not have persisted a `name.full` key or value to the database: + + theSituation.save(function (err) { + Person.findById(theSituation._id, function (err, found) { + console.log(found.name.first); // 'The' + console.log(found.name.last); // 'Situation' + }); + }); + +If you want attributes that you can get and set but that are not themselves persisted to mongodb, virtual attributes is the Mongoose feature for you. diff --git a/node_modules/mongoose/examples/schema.js b/node_modules/mongoose/examples/schema.js new file mode 100644 index 0000000..d108d05 --- /dev/null +++ b/node_modules/mongoose/examples/schema.js @@ -0,0 +1,102 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('mongoose') + , Schema = mongoose.Schema; + +/** + * Schema definition + */ + +// recursive embedded-document schema + +var Comment = new Schema(); + +Comment.add({ + title : { type: String, index: true } + , date : Date + , body : String + , comments : [Comment] +}); + +var BlogPost = new Schema({ + title : { type: String, index: true } + , slug : { type: String, lowercase: true, trim: true } + , date : Date + , buf : Buffer + , comments : [Comment] + , creator : Schema.ObjectId +}); + +var Person = new Schema({ + name: { + first: String + , last : String + } + , email: { type: String, required: true, index: { unique: true, sparse: true } } + , alive: Boolean +}); + +/** + * Accessing a specific schema type by key + */ + +BlogPost.path('date') +.default(function(){ + return new Date() + }) +.set(function(v){ + return v == 'now' ? new Date() : v; + }); + +/** + * Pre hook. + */ + +BlogPost.pre('save', function(next, done){ + emailAuthor(done); // some async function + next(); +}); + +/** + * Methods + */ + +BlogPost.methods.findCreator = function (callback) { + return this.db.model('Person').findById(this.creator, callback); +} + +BlogPost.statics.findByTitle = function (title, callback) { + return this.find({ title: title }, callback); +} + +BlogPost.methods.expressiveQuery = function (creator, date, callback) { + return this.find('creator', creator).where('date').gte(date).run(callback); +} + +/** + * Plugins + */ + +function slugGenerator (options){ + options = options || {}; + var key = options.key || 'title'; + + return function slugGenerator(schema){ + schema.path(key).set(function(v){ + this.slug = v.toLowerCase().replace(/[^a-z0-9]/g, '').replace(/-+/g, ''); + return v; + }); + }; +}; + +BlogPost.plugin(slugGenerator()); + +/** + * Define model. + */ + +mongoose.model('BlogPost', BlogPost); +mongoose.model('Person', Person); diff --git a/node_modules/mongoose/index.js b/node_modules/mongoose/index.js new file mode 100644 index 0000000..e7e6278 --- /dev/null +++ b/node_modules/mongoose/index.js @@ -0,0 +1,7 @@ + +/** + * Export lib/mongoose + * + */ + +module.exports = require('./lib/'); diff --git a/node_modules/mongoose/lib/collection.js b/node_modules/mongoose/lib/collection.js new file mode 100644 index 0000000..c4d5e79 --- /dev/null +++ b/node_modules/mongoose/lib/collection.js @@ -0,0 +1,167 @@ + +/** + * Collection constructor + * + * @param {String} collection name + * @param {Collection} connection object + * @api public + */ + +function Collection (name, conn) { + this.name = name; + this.conn = conn; + this.buffer = true; + this.queue = []; + if (this.conn.readyState == 1) this.onOpen(); +}; + +/** + * The collection name + * + * @api public + */ + +Collection.prototype.name; + +/** + * The Connection instance + * + * @api public + */ + +Collection.prototype.conn; + +/** + * Called when the database connects + * + * @api private + */ + +Collection.prototype.onOpen = function () { + var self = this; + this.buffer = false; + self.doQueue(); +}; + +/** + * Called when the database disconnects + * + * @api private + */ + +Collection.prototype.onClose = function () { + this.buffer = true; +}; + +/** + * Adds a callback to the queue + * + * @param {String} method name + * @param {Array} arguments + * @api private + */ + +Collection.prototype.addQueue = function (name, args) { + this.queue.push([name, args]); + return this; +}; + +/** + * Executes the current queue + * + * @api private + */ + +Collection.prototype.doQueue = function () { + for (var i = 0, l = this.queue.length; i < l; i++){ + this[this.queue[i][0]].apply(this, this.queue[i][1]); + } + this.queue = []; + return this; +}; + +/** + * Ensure index function + * + * @api private + */ + +Collection.prototype.ensureIndex = function(){ + throw new Error('Collection#ensureIndex unimplemented by driver'); +}; + +/** + * FindAndModify command + * + * @api private + */ + +Collection.prototype.findAndModify = function(){ + throw new Error('Collection#findAndModify unimplemented by driver'); +}; + +/** + * FindOne command + * + * @api private + */ + +Collection.prototype.findOne = function(){ + throw new Error('Collection#findOne unimplemented by driver'); +}; + +/** + * Find command + * + * @api private + */ + +Collection.prototype.find = function(){ + throw new Error('Collection#find unimplemented by driver'); +}; + +/** + * Insert command + * + * @api private + */ + +Collection.prototype.insert = function(){ + throw new Error('Collection#insert unimplemented by driver'); +}; + +/** + * Update command + * + * @api private + */ + +Collection.prototype.save = function(){ + throw new Error('Collection#save unimplemented by driver'); +}; + +/** + * Insert command + * + * @api private + */ + +Collection.prototype.update = function(){ + throw new Error('Collection#update unimplemented by driver'); +}; + +/** + * getIndexes command + * + * @api private + */ + +Collection.prototype.getIndexes = function(){ + throw new Error('Collection#getIndexes unimplemented by driver'); +}; + +/** + * Module exports. + */ + +module.exports = Collection; diff --git a/node_modules/mongoose/lib/connection.js b/node_modules/mongoose/lib/connection.js new file mode 100644 index 0000000..d201e8f --- /dev/null +++ b/node_modules/mongoose/lib/connection.js @@ -0,0 +1,500 @@ +/** + * Module dependencies. + */ + +var url = require('url') + , utils = require('./utils') + , EventEmitter = utils.EventEmitter + , driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native' + , Model = require('./model') + , Schema = require('./schema') + , Collection = require(driver + '/collection'); + +/** + * Connection constructor. For practical reasons, a Connection equals a Db + * + * @param {Mongoose} mongoose base + * @api public + */ + +function Connection (base) { + this.base = base; + this.collections = {}; + this.models = {}; +}; + +/** + * Inherit from EventEmitter. + * + */ + +Connection.prototype.__proto__ = EventEmitter.prototype; + +/** + * Connection ready state: + * 0 = Disconnected + * 1 = Connected + * 2 = Connecting + * 3 = Disconnecting + * + * @api public + */ + +Connection.prototype.readyState = 0; + +/** + * A hash of the collections associated with this connection + */ + +Connection.prototype.collections; + +/** + * The mongodb.Db instance, set when the connection is opened + * + * @api public + */ + +Connection.prototype.db; + +/** + * Establishes the connection + * + * `options` is a hash with the following optional properties: + * + * options.db - passed to the connection db instance + * options.server - passed to the connection server instance(s) + * options.replset - passed to the connection ReplSetServer instance + * + * Notes: + * + * Mongoose forces the db option `forceServerObjectId` false and cannot + * be overridden. + * + * Mongoose defaults the server `auto_reconnect` options to true which + * can be overridden. + * + * See the node-mongodb-native driver instance for options that it + * understands. + * + * @param {String} mongodb://uri + * @return {Connection} self + * @see https://github.com/christkv/node-mongodb-native + * @api public + */ + +Connection.prototype.open = function (host, database, port, options, callback) { + var self = this + , uri; + + if ('string' === typeof database) { + switch (arguments.length) { + case 2: + port = 27017; + case 3: + switch (typeof port) { + case 'function': + callback = port, port = 27017; + break; + case 'object': + options = port, port = 27017; + break; + } + break; + case 4: + if ('function' === typeof options) + callback = options, options = {}; + } + } else { + switch (typeof database) { + case 'function': + callback = database, database = undefined; + break; + case 'object': + options = database; + database = undefined; + callback = port; + break; + } + + uri = url.parse(host); + host = uri.hostname; + port = uri.port || 27017; + database = uri.pathname && uri.pathname.replace(/\//g, ''); + } + + callback = callback || noop; + this.options = this.defaultOptions(options); + + // make sure we can open + if (0 !== this.readyState) { + var err = new Error('Trying to open unclosed connection.'); + err.state = this.readyState; + callback(err); + return this; + } + + if (!host) { + callback(new Error('Missing connection hostname.')); + return this; + } + + if (!database) { + callback(new Error('Missing connection database.')); + return this; + } + + // handle authentication + if (uri && uri.auth) { + var auth = uri.auth.split(':'); + this.user = auth[0]; + this.pass = auth[1]; + } else { + this.user = this.pass = undefined; + } + + this.name = database; + this.host = host; + this.port = port; + + // signal connecting + this.readyState = 2; + this.emit('opening'); + + // open connection + this.doOpen(function (err) { + if (err) { + if (self._events && self._events.error && self._events.error.length) { + self.emit("error", err); + } + self.readyState = 0; + } else { + self.onOpen(); + } + + callback(err || null); + }); + + return this; +}; + +/** + * Connects to a replica set. + * + * Supply a comma-separted list of mongodb:// URIs. You only need to specify + * the database name and/or auth to one of them. + * + * The options parameter is passed to the low level connection. See the + * node-mongodb-native driver instance for detail. + * + * @param {String} comma-separated mongodb:// URIs + * @param {String} optional database name + * @param {Object} optional options + * @param {Function} optional callback + */ + +Connection.prototype.openSet = function (uris, database, options, callback) { + var uris = uris.split(',') + , self = this; + + switch (arguments.length) { + case 3: + this.name = database; + if ('function' === typeof options) callback = options, options = {}; + break; + case 2: + switch (typeof database) { + case 'string': + this.name = database; + case 'function': + callback = database, database = null; + break; + case 'object': + options = database, database = null; + break; + } + } + + this.options = options = this.defaultOptions(options); + callback = callback || noop; + + if (uris.length < 2) { + callback(new Error('Please provide comma-separated URIs')); + return this; + } + + this.host = []; + this.port = []; + + uris.forEach(function (uri) { + var uri = url.parse(uri); + + self.host.push(uri.hostname); + self.port.push(uri.port || 27017); + + if (!self.name && uri.pathname.replace(/\//g, '')) + self.name = uri.pathname.replace(/\//g, ''); + + if (!self.user && uri.auth) { + var auth = uri.auth.split(':'); + self.user = auth[0]; + self.pass = auth[1]; + } + }); + + if (!this.name) { + callback(new Error('No database name provided for replica set')); + return this; + } + + this.readyState = 2; + this.emit('opening'); + + // open connection + this.doOpenSet(function (err) { + if (err) { + if (self._events && self._events.error && self._events.error.length) { + self.emit("error", err); + } + self.readyState = 0; + } else { + self.onOpen(); + } + + callback(err || null); + }); +}; + +/** + * Called when the connection is opened + * + * @api private + */ + +Connection.prototype.onOpen = function () { + var self = this; + + function open () { + self.readyState = 1; + + // avoid having the collection subscribe to our event emitter + // to prevent 0.3 warning + for (var i in self.collections) + self.collections[i].onOpen(); + + self.emit('open'); + }; + + // re-authenticate + if (self.user && self.pass) + self.db.authenticate(self.user, self.pass, open); + else + open(); +}; + +/** + * Closes the connection + * + * @param {Function} optional callback + * @return {Connection} self + * @api public + */ + +Connection.prototype.close = function (callback) { + var self = this + , callback = callback || function(){}; + + switch (this.readyState){ + case 0: // disconnected + callback(null); + break; + + case 1: // connected + this.readyState = 3; + this.doClose(function(err){ + if (err){ + callback(err); + } else { + self.onClose(); + callback(null); + } + }); + break; + + case 2: // connecting + this.once('open', function(){ + self.close(callback); + }); + break; + + case 3: // disconnecting + this.once('close', function () { + callback(null); + }); + break; + } + + return this; +}; + +/** + * Called when the connection closes + * + * @api private + */ + +Connection.prototype.onClose = function () { + this.readyState = 0; + + // avoid having the collection subscribe to our event emitter + // to prevent 0.3 warning + for (var i in this.collections) + this.collections[i].onClose(); + + this.emit('close'); +}; + +/** + * Retrieves a collection, creating it if not cached. + * + * @param {String} collection name + * @return {Collection} collection instance + * @api public + */ + +Connection.prototype.collection = function (name) { + if (!(name in this.collections)) + this.collections[name] = new Collection(name, this); + return this.collections[name]; +}; + +/** + * Defines a model or retrieves it + * + * @param {String} model name + * @param {Schema} schema object + * @param {String} collection name (optional, induced from model name) + * @api public + */ + +Connection.prototype.model = function (name, schema, collection) { + if (!this.models[name]) { + var model = this.base.model(name, schema, collection, true) + , Model + + if (this != model.prototype.connection) { + // subclass model using this connection and collection name + Model = function Model () { + model.apply(this, arguments); + }; + + Model.__proto__ = model; + Model.prototype.__proto__ = model.prototype; + Model.prototype.db = this; + + // collection name discovery + if ('string' === typeof schema) { + collection = schema; + } + + if (!collection) { + collection = model.prototype.schema.set('collection') || utils.toCollectionName(name); + } + + Model.prototype.collection = this.collection(collection); + Model.init(); + } + + this.models[name] = Model || model; + } + + return this.models[name]; +}; + +/** + * Set profiling level. + * + * @param {Int|String} level - Either off (0), slow (1), or all (2) + * @param {Int} [ms] If profiling `level` is set to 1, this determines + * the threshold in milliseconds above which queries + * will be logged. Defaults to 100. (optional) + * @param {Function} callback + * @api public + */ + +Connection.prototype.setProfiling = function (level, ms, callback) { + if (1 !== this.readyState) { + return this.on('open', this.setProfiling.bind(this, level, ms, callback)); + } + + if (!callback) callback = ms, ms = 100; + + var cmd = {}; + + switch (level) { + case 0: + case 'off': + cmd.profile = 0; + break; + case 1: + case 'slow': + cmd.profile = 1; + if ('number' !== typeof ms) { + ms = parseInt(ms, 10); + if (isNaN(ms)) ms = 100; + } + cmd.slowms = ms; + break; + case 2: + case 'all': + cmd.profile = 2; + break; + default: + return callback(new Error('Invalid profiling level: '+ level)); + } + + this.db.executeDbCommand(cmd, function (err, resp) { + if (err) return callback(err); + + var doc = resp.documents[0]; + + err = 1 === doc.ok + ? null + : new Error('Could not set profiling level to: '+ level) + + callback(err, doc); + }); +}; + +/** + * Prepares default connection options. + * + * @param {Object} options + * @api private + */ + +Connection.prototype.defaultOptions = function (options) { + var o = options || {}; + + o.server = o.server || {}; + + if (!('auto_reconnect' in o.server)) { + o.server.auto_reconnect = true; + } + + o.db = o.db || {}; + o.db.forceServerObjectId = false; + + return o; +} + +/** + * Noop. + */ + +function noop () {} + +/** + * Module exports. + */ + +module.exports = Connection; diff --git a/node_modules/mongoose/lib/document.js b/node_modules/mongoose/lib/document.js new file mode 100644 index 0000000..612631a --- /dev/null +++ b/node_modules/mongoose/lib/document.js @@ -0,0 +1,1025 @@ +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , MongooseError = require('./error') + , MixedSchema = require('./schema/mixed') + , Schema = require('./schema') + , ValidatorError = require('./schematype').ValidatorError + , utils = require('./utils') + , clone = utils.clone + , inspect = require('util').inspect + , StateMachine = require('./statemachine') + , ActiveRoster = StateMachine.ctor('require', 'modify', 'init') + , deepEqual = utils.deepEqual + , hooks = require('hooks') + , DocumentArray + +/** + * Document constructor. + * + * @param {Object} values to set + * @api private + */ + +function Document (obj, fields) { + // node <0.4.3 bug + if (!this._events) this._events = {}; + this.setMaxListeners(0); + + this._strictMode = this.options && this.options.strict; + if ('boolean' === typeof fields) this._strictMode = fields; + + this._doc = this.buildDoc(fields); + this._activePaths = new ActiveRoster(); + var self = this; + this.schema.requiredPaths.forEach(function (path) { + self._activePaths.require(path); + }); + + this._saveError = null; + this._validationError = null; + this.isNew = true; + + if (obj) this.set(obj); + + this._registerHooks(); + this.doQueue(); + + this.errors = undefined; +}; + +/** + * Inherit from EventEmitter. + */ + +Document.prototype.__proto__ = EventEmitter.prototype; + +/** + * Base Mongoose instance for the model. Set by the Mongoose instance upon + * pre-compilation. + * + * @api public + */ + +Document.prototype.base; + +/** + * Document schema as a nested structure. + * + * @api public + */ + +Document.prototype.schema; + +/** + * Whether the document is new. + * + * @api public + */ + +Document.prototype.isNew; + +/** + * Validation errors. + * + * @api public + */ + +Document.prototype.errors; + +/** + * Builds the default doc structure + * + * @api private + */ + +Document.prototype.buildDoc = function (fields) { + var doc = {} + , self = this + , exclude + , keys + , key + , ki + + if ('boolean' === typeof fields) fields = undefined; + + // determine if this doc is a result of a query with + // excluded fields + if (fields && 'Object' === fields.constructor.name) { + keys = Object.keys(fields); + ki = keys.length; + + while (ki--) { + if ('_id' !== keys[ki]) { + exclude = 0 === fields[keys[ki]]; + break; + } + } + } + + var paths = Object.keys(this.schema.paths) + , plen = paths.length + , ii = 0 + + for (; ii < plen; ++ii) { + var p = paths[ii] + , type = this.schema.paths[p] + , path = p.split('.') + , len = path.length + , last = len-1 + , doc_ = doc + , i = 0 + + for (; i < len; ++i) { + var piece = path[i] + , def + + if (i === last) { + if (fields) { + if (exclude) { + // apply defaults to all non-excluded fields + if (p in fields) continue; + + def = type.getDefault(self); + if ('undefined' !== typeof def) doc_[piece] = def; + + } else { + // do nothing. only the fields specified in + // the query specified will be populated + } + } else { + def = type.getDefault(self); + if ('undefined' !== typeof def) doc_[piece] = def; + } + } else { + doc_ = doc_[piece] || (doc_[piece] = {}); + } + } + }; + + return doc; +}; + +/** + * Inits (hydrates) the document without setters. + * + * Called internally after a document is returned + * from mongodb. + * + * @param {Object} document returned by mongo + * @param {Function} callback + * @api private + */ + +Document.prototype.init = function (doc, fn) { + this.isNew = false; + + init(this, doc, this._doc); + this.emit('init'); + + if (fn) + fn(null); + + return this; +}; + +/** + * Init helper. + * @param {Object} instance + * @param {Object} obj - raw mongodb doc + * @param {Object} doc - object we are initializing + * @private + */ + +function init (self, obj, doc, prefix) { + prefix = prefix || ''; + + var keys = Object.keys(obj) + , len = keys.length + , schema + , path + , i; + + while (len--) { + i = keys[len]; + path = prefix + i; + schema = self.schema.path(path); + + if (!schema && obj[i] && 'Object' === obj[i].constructor.name) { + // assume nested object + doc[i] = {}; + init(self, obj[i], doc[i], path + '.'); + } else { + if (obj[i] === null) { + doc[i] = null; + } else if (obj[i] !== undefined) { + if (schema) { + self.try(function(){ + doc[i] = schema.cast(obj[i], self, true); + }); + } else { + doc[i] = obj[i]; + } + } + // mark as hydrated + self._activePaths.init(path); + } + } +}; + +// Set up middleware support +for (var k in hooks) { + Document.prototype[k] = Document[k] = hooks[k]; +} + +/** + * Sets a path, or many paths + * + * Examples: + * // path, value + * doc.set(path, value) + * + * // object + * doc.set({ + * path : value + * , path2 : { + * path : value + * } + * } + * + * @param {String|Object} key path, or object + * @param {Object} value, or undefined or a prefix if first parameter is an object + * @param @optional {Schema|String|...} specify a type if this is an on-the-fly attribute + * @api public + */ + +Document.prototype.set = function (path, val, type) { + var adhocs; + if (type) { + adhocs = this._adhocPaths || (this._adhocPaths = {}); + adhocs[path] = Schema.interpretAsType(path, type); + } + + if ('string' !== typeof path) { + if (null === path || undefined === path) { + var _ = path; + path = val; + val = _; + } else { + var prefix = val + ? val + '.' + : ''; + + if (path instanceof Document) { + path = path._doc; + } + + var keys = Object.keys(path); + var i = keys.length; + var key; + + while (i--) { + key = keys[i]; + if (null != path[key] && 'Object' === path[key].constructor.name + && !(this._path(prefix + key) instanceof MixedSchema)) { + this.set(path[key], prefix + key); + } else if (this._strictMode) { + if (this._path(prefix + key) !== undefined) { + this.set(prefix + key, path[key]); + } + } else if (undefined !== path[key]) { + this.set(prefix + key, path[key]); + } + } + + return this; + } + } + + var schema = this._path(path) + , parts = path.split('.') + , obj = this._doc + , self = this; + + if (this.schema.pathType(path) === 'virtual') { + schema = this.schema.virtualpath(path); + schema.applySetters(val, this); + return this; + } + + // When using the $set operator the path to the field must already exist. + // Else mongodb throws: "LEFT_SUBFIELD only supports Object" + var pathToMark + , subpaths + , subpath; + + if (parts.length <= 1) { + pathToMark = path; + } else { + subpaths = parts.map(function (part, i) { + return parts.slice(0, i).concat(part).join('.'); + }); + + for (var i = 0, l = subpaths.length; i < l; i++) { + subpath = subpaths[i]; + if (this.isDirectModified(subpath) // earlier prefixes that are already + // marked as dirty have precedence + || this.get(subpath) === null) { + pathToMark = subpath; + break; + } + } + + if (!pathToMark) pathToMark = path; + } + + if ((!schema || null === val || undefined === val) || + this.try(function(){ + var casted = schema.cast(val, self, false, self.get(path)); + val = schema.applySetters(casted, self); + })) { + + if (this.isNew) { + this.markModified(pathToMark); + } else { + var priorVal = this.get(path); + if (!this.isDirectModified(pathToMark) && !deepEqual(val, priorVal)) { + this.markModified(pathToMark); + } + } + + for (var i = 0, l = parts.length; i < l; i++) { + var next = i + 1 + , last = next === l; + + if (last) { + obj[parts[i]] = val; + } else { + if (obj[parts[i]] && 'Object' === obj[parts[i]].constructor.name) { + obj = obj[parts[i]]; + } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) { + obj = obj[parts[i]]; + } else { + obj = obj[parts[i]] = {}; + } + } + } + } + + return this; +}; + +/** + * Gets a raw value from a path (no getters) + * + * @param {String} path + * @api private + */ + +Document.prototype.getValue = function (path) { + var parts = path.split('.') + , obj = this._doc + , part; + + for (var i = 0, l = parts.length; i < l-1; i++) { + part = parts[i]; + path = convertIfInt(path); + obj = obj.getValue + ? obj.getValue(part) // If we have an embedded array document member + : obj[part]; + if (!obj) return obj; + } + + part = parts[l-1]; + path = convertIfInt(path); + + return obj.getValue + ? obj.getValue(part) // If we have an embedded array document member + : obj[part]; +}; + +function convertIfInt (string) { + if (/^\d+$/.test(string)) { + return parseInt(string, 10); + } + return string; +} + +/** + * Sets a raw value for a path (no casting, setters, transformations) + * + * @param {String} path + * @param {Object} value + * @api private + */ + +Document.prototype.setValue = function (path, val) { + var parts = path.split('.') + , obj = this._doc; + + for (var i = 0, l = parts.length; i < l-1; i++) { + obj = obj[parts[i]]; + } + + obj[parts[l-1]] = val; + return this; +}; + +/** + * Triggers casting on a specific path + * + * @todo - deprecate? not used anywhere + * @param {String} path + * @api public + */ + +Document.prototype.doCast = function (path) { + var schema = this.schema.path(path); + if (schema) + this.setValue(path, this.getValue(path)); +}; + +/** + * Gets a path + * + * @param {String} key path + * @param @optional {Schema|String|...} specify a type if this is an on-the-fly attribute + * @api public + */ + +Document.prototype.get = function (path, type) { + var adhocs; + if (type) { + adhocs = this._adhocPaths || (this._adhocPaths = {}); + adhocs[path] = Schema.interpretAsType(path, type); + } + + var schema = this._path(path) || this.schema.virtualpath(path) + , pieces = path.split('.') + , obj = this._doc; + + for (var i = 0, l = pieces.length; i < l; i++) { + obj = null == obj ? null : obj[pieces[i]]; + } + + if (schema) { + obj = schema.applyGetters(obj, this); + } + + return obj; +}; + +/** + * Finds the path in the ad hoc type schema list or + * in the schema's list of type schemas + * @param {String} path + * @api private + */ + +Document.prototype._path = function (path) { + var adhocs = this._adhocPaths + , adhocType = adhocs && adhocs[path]; + + if (adhocType) { + return adhocType; + } else { + return this.schema.path(path); + } +}; + +/** + * Commits a path, marking as modified if needed. Useful for mixed keys + * + * @api public + */ + +Document.prototype.commit = +Document.prototype.markModified = function (path) { + this._activePaths.modify(path); +}; + +/** + * Captures an exception that will be bubbled to `save` + * + * @param {Function} function to execute + * @param {Object} scope + */ + +Document.prototype.try = function (fn, scope) { + var res; + try { + fn.call(scope); + res = true; + } catch (e) { + this.error(e); + res = false; + } + return res; +}; + +/** + * Checks if a path or any full path containing path as part of + * its path chain has been directly modified. + * + * e.g., if we set `documents.0.title` to 'newTitle' + * then we have directly modified `documents.0.title` + * but not directly modified `documents` or `documents.0`. + * Nonetheless, we still say `documents` and `documents.0` + * are modified. They just are not considered direct modified. + * The distinction is important because we need to distinguish + * between what has been directly modified and what hasn't so + * that we can determine the MINIMUM set of dirty data + * that we want to send to MongoDB on a Document save. + * + * @param {String} path + */ + +Document.prototype.isModified = function (path) { + var directModifiedPaths = Object.keys(this._activePaths.states.modify); + + var allPossibleChains = directModifiedPaths.reduce(function (list, path) { + var parts = path.split('.'); + return list.concat(parts.reduce(function (chains, part, i) { + return chains.concat(parts.slice(0, i).concat(part).join('.')); + }, [])); + }, []); + + return !!~allPossibleChains.indexOf(path); +}; + +/** + * Checks if a path has been directly set and modified. False if + * the path is only part of a larger path that was directly set. + * + * e.g., if we set `documents.0.title` to 'newTitle' + * then we have directly modified `documents.0.title` + * but not directly modified `documents` or `documents.0`. + * Nonetheless, we still say `documents` and `documents.0` + * are modified. They just are not considered direct modified. + * The distinction is important because we need to distinguish + * between what has been directly modified and what hasn't so + * that we can determine the MINIMUM set of dirty data + * that we want to send to MongoDB on a Document save. + * + * @param {String} path + */ + +Document.prototype.isDirectModified = function (path) { + return (path in this._activePaths.states.modify); +}; + +/** + * Checks if a certain path was initialized + * + * @param {String} path + */ + +Document.prototype.isInit = function (path) { + return (path in this._activePaths.states.init); +}; + +/** + * Validation middleware + * + * @param {Function} next + * @api public + */ + +Document.prototype.validate = function (next) { + var total = 0 + , self = this + , validating = {} + + if (!this._activePaths.some('require', 'init', 'modify')) { + return complete(); + } + + function complete () { + next(self._validationError); + self._validationError = null; + } + + this._activePaths.forEach('require', 'init', 'modify', function validatePath (path) { + if (validating[path]) return; + + validating[path] = true; + total++; + + process.nextTick(function(){ + var p = self.schema.path(path); + if (!p) return --total || complete(); + + p.doValidate(self.getValue(path), function (err) { + if (err) { + self.invalidate(path, err); + } + --total || complete(); + }, self); + }); + }); + + return this; +}; + +/** + * Marks a path as invalid, causing a subsequent validation to fail. + * + * @param {String} path of the field to invalidate + * @param {String/Error} error of the path. + * @api public + */ + +Document.prototype.invalidate = function (path, err) { + if (!this._validationError) { + this._validationError = new ValidationError(this); + } + + if (!err || 'string' === typeof err) { + err = new ValidatorError(path, err); + } + + this._validationError.errors[path] = err; +} + +/** + * Resets the atomics and modified states of this document. + * + * @private + * @return {this} + */ + +Document.prototype._reset = function reset () { + var self = this; + DocumentArray || (DocumentArray = require('./types/documentarray')); + + this._activePaths + .map('init', 'modify', function (i) { + return self.getValue(i); + }) + .filter(function (val) { + return (val && val instanceof DocumentArray && val.length); + }) + .forEach(function (array) { + array.forEach(function (doc) { + doc._reset(); + }); + }); + + // clear atomics + this._dirty().forEach(function (dirt) { + var type = dirt.value; + if (type && type._path && type.doAtomics) { + type._atomics = {}; + } + }); + + // Clear 'modify'('dirty') cache + this._activePaths.clear('modify'); + var self = this; + this.schema.requiredPaths.forEach(function (path) { + self._activePaths.require(path); + }); + + return this; +} + +/** + * Returns the dirty paths / vals + * + * @api private + */ + +Document.prototype._dirty = function _dirty () { + var self = this; + + var all = this._activePaths.map('modify', function (path) { + return { path: path + , value: self.getValue(path) + , schema: self._path(path) }; + }); + + // Sort dirty paths in a flat hierarchy. + all.sort(function (a, b) { + return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0)); + }); + + // Ignore "foo.a" if "foo" is dirty already. + var minimal = [] + , lastReference = null; + + all.forEach(function (item, i) { + if (item.path.indexOf(lastReference) !== 0) { + lastReference = item.path + '.'; + minimal.push(item); + } + }); + + return minimal; +} + +/** + * Returns if the document has been modified + * + * @return {Boolean} + * @api public + */ + +Document.prototype.__defineGetter__('modified', function () { + return this._activePaths.some('modify'); +}); + +/** + * Compiles schemas. + * @api private + */ + +function compile (tree, proto, prefix) { + var keys = Object.keys(tree) + , i = keys.length + , limb + , key; + + while (i--) { + key = keys[i]; + limb = tree[key]; + + define(key + , (('Object' === limb.constructor.name + && Object.keys(limb).length) + && (!limb.type || limb.type.type) + ? limb + : null) + , proto + , prefix + , keys); + } +}; + +/** + * Defines the accessor named prop on the incoming prototype. + * @api private + */ + +function define (prop, subprops, prototype, prefix, keys) { + var prefix = prefix || '' + , path = (prefix ? prefix + '.' : '') + prop; + + if (subprops) { + + Object.defineProperty(prototype, prop, { + enumerable: true + , get: function () { + if (!this.__getters) + this.__getters = {}; + + if (!this.__getters[path]) { + var nested = Object.create(this); + + // save scope for nested getters/setters + if (!prefix) nested._scope = this; + + // shadow inherited getters from sub-objects so + // thing.nested.nested.nested... doesn't occur (gh-366) + var i = 0 + , len = keys.length; + + for (; i < len; ++i) { + // over-write the parents getter without triggering it + Object.defineProperty(nested, keys[i], { + enumerable: false // It doesn't show up. + , writable: true // We can set it later. + , configurable: true // We can Object.defineProperty again. + , value: undefined // It shadows its parent. + }); + } + + nested.toObject = function () { + return this.get(path); + }; + + compile(subprops, nested, path); + this.__getters[path] = nested; + } + + return this.__getters[path]; + } + , set: function (v) { + return this.set(v, path); + } + }); + + } else { + + Object.defineProperty(prototype, prop, { + enumerable: true + , get: function ( ) { return this.get.call(this._scope || this, path); } + , set: function (v) { return this.set.call(this._scope || this, path, v); } + }); + } +}; + +/** + * We override the schema setter to compile accessors + * + * @api private + */ + +Document.prototype.__defineSetter__('schema', function (schema) { + compile(schema.tree, this); + this._schema = schema; +}); + +/** + * We override the schema getter to return the internal reference + * + * @api private + */ + +Document.prototype.__defineGetter__('schema', function () { + return this._schema; +}); + +/** + * Register default hooks + * + * @api private + */ + +Document.prototype._registerHooks = function _registerHooks () { + if (!this.save) return; + + DocumentArray || (DocumentArray = require('./types/documentarray')); + + this.pre('save', function (next) { + // we keep the error semaphore to make sure we don't + // call `save` unnecessarily (we only need 1 error) + var subdocs = 0 + , error = false + , self = this; + + var arrays = this._activePaths + .map('init', 'modify', function (i) { + return self.getValue(i); + }) + .filter(function (val) { + return (val && val instanceof DocumentArray && val.length); + }); + + if (!arrays.length) + return next(); + + arrays.forEach(function (array) { + subdocs += array.length; + array.forEach(function (value) { + if (!error) + value.save(function (err) { + if (!error) { + if (err) { + error = true; + next(err); + } else + --subdocs || next(); + } + }); + }); + }); + }, function (err) { + this.db.emit('error', err); + }).pre('save', function checkForExistingErrors (next) { + if (this._saveError) { + next(this._saveError); + this._saveError = null; + } else { + next(); + } + }).pre('save', function validation (next) { + return this.validate(next); + }); +}; + +/** + * Registers an error + * TODO: handle multiple + * + * @param {Error} error + * @api private + */ + +Document.prototype.error = function (err) { + this._saveError = err; + return this; +}; + +/** + * Executes methods queued from the Schema definition + * + * @api private + */ + +Document.prototype.doQueue = function () { + if (this.schema && this.schema.callQueue) + for (var i = 0, l = this.schema.callQueue.length; i < l; i++) { + this[this.schema.callQueue[i][0]].apply(this, this.schema.callQueue[i][1]); + } + return this; +}; + +/** + * Gets the document + * + * @todo Should we apply getters? + * @return {Object} plain object + * @api public + */ + +Document.prototype.toObject = function (options) { + options || (options = {}); + options.minimize = true; + return clone(this._doc, options); +}; + +/** + * Returns a JSON string for the document + * + * @return {String} JSON representation + * @api public + */ + +Document.prototype.toJSON = function () { + return this.toObject(); +}; + +/** + * Helper for console.log + * + * @api public + */ + +Document.prototype.toString = +Document.prototype.inspect = function () { + return inspect(this.toObject()); +}; + +/** + * Returns true if the Document stores the same data as doc. + * @param {Document} doc to compare to + * @return {Boolean} + * @api public + */ + +Document.prototype.equals = function (doc) { + return this.get('_id') === doc.get('_id'); +}; + +/** + * Module exports. + */ + +module.exports = Document; + +/** + * Document Validation Error + */ + +function ValidationError (instance) { + MongooseError.call(this, "Validation failed"); + Error.captureStackTrace(this, arguments.callee); + this.name = 'ValidationError'; + this.errors = instance.errors = {}; +}; + +ValidationError.prototype.toString = function () { + return this.name + ': ' + Object.keys(this.errors).map(function (key) { + return String(this.errors[key]); + }, this).join(', '); +}; + +/** + * Inherits from MongooseError. + */ + +ValidationError.prototype.__proto__ = MongooseError.prototype; + +Document.ValidationError = ValidationError; + +/** + * Document Error + * + * @param text + */ + +function DocumentError () { + MongooseError.call(this, msg); + Error.captureStackTrace(this, arguments.callee); + this.name = 'DocumentError'; +}; + +/** + * Inherits from MongooseError. + */ + +DocumentError.prototype.__proto__ = MongooseError.prototype; + +exports.Error = DocumentError; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js new file mode 100644 index 0000000..52e6a03 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/binary.js @@ -0,0 +1,8 @@ + +/** + * Module dependencies. + */ + +var Binary = require('mongodb').BSONPure.Binary; + +module.exports = exports = Binary; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js new file mode 100644 index 0000000..dc154c9 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/collection.js @@ -0,0 +1,174 @@ + +/** + * Module dependencies. + */ + +var Collection = require('../../collection') + , NativeCollection = require('mongodb').Collection + , utils = require('../../utils') + +/** + * Native collection + * + * @api private + */ + +function MongooseCollection () { + Collection.apply(this, arguments); +}; + +/** + * Inherit from abstract Collection. + */ + +MongooseCollection.prototype.__proto__ = Collection.prototype; + +/** + * Called when the connection opens + * + * @api private + */ + +MongooseCollection.prototype.onOpen = function () { + var self = this; + this.conn.db.collection(this.name, function (err, collection) { + // TODO handle err + if (!err){ + self.collection = collection; + Collection.prototype.onOpen.call(self); + } + }); +}; + +/** + * Called when the connection closes + * + * @api private + */ + +MongooseCollection.prototype.onClose = function () { + Collection.prototype.onClose.call(this); +}; + +/** + * Copy the collection methods and make them subject to queues + */ + +for (var i in NativeCollection.prototype) + (function(i){ + MongooseCollection.prototype[i] = function () { + // BENCHMARKME: is it worth caching the prototype methods? probably + if (!this.buffer) { + var collection = this.collection + , args = arguments + , self = this; + + process.nextTick(function(){ + var debug = self.conn.base.options.debug; + + if (debug) { + if ('function' === typeof debug) { + debug.apply(debug + , [self.name, i].concat(utils.args(args, 0, args.length-1))); + } else { + console.error('\x1B[0;36mMongoose:\x1B[0m %s.%s(%s) %s %s %s' + , self.name + , i + , print(args[0]) + , print(args[1]) + , print(args[2]) + , print(args[3])) + } + } + + collection[i].apply(collection, args); + }); + } else { + this.addQueue(i, arguments); + } + }; + })(i); + +/** + * Debug print helper + * @private + */ + +function print (arg) { + var type = typeof arg; + if ('function' === type || 'undefined' === type) return ''; + return format(arg); +} + +/** + * Debug print helper + * @private + */ + +function format (obj, sub) { + var x = utils.clone(obj); + if (x) { + if ('Binary' === x.constructor.name) { + x = '[object Buffer]'; + } else if ('Object' === x.constructor.name) { + var keys = Object.keys(x) + , i = keys.length + , key + while (i--) { + key = keys[i]; + if (x[key]) { + if ('Binary' === x[key].constructor.name) { + x[key] = '[object Buffer]'; + } else if ('Object' === x[key].constructor.name) { + x[key] = format(x[key], true); + } else if (Array.isArray(x[key])) { + x[key] = x[key].map(function (o) { + return format(o, true) + }); + } + } + } + } + if (sub) return x; + } + + return require('util') + .inspect(x, false, 10, true) + .replace(/\n/g, '') + .replace(/\s{2,}/g, '') +} + +/** + * Implement getIndexes + * + * @param {Function} callback + * @api private + */ + +MongooseCollection.prototype.getIndexes = +MongooseCollection.prototype.indexInformation; + +/** + * Override signature of ensureIndex. -native one is not standard. + * + * @param {Object} spec + * @param {Object} options + * @param {Function} callback + * @api public + */ + +var oldEnsureIndex = NativeCollection.prototype.ensureIndex; + +function noop () {}; + +NativeCollection.prototype.ensureIndex = function(fields, options, fn){ + if (!this.buffer) { + return oldEnsureIndex.apply(this, arguments); + } +}; + +/** + * Module exports. + */ + +module.exports = MongooseCollection; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js new file mode 100644 index 0000000..3becd1b --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js @@ -0,0 +1,106 @@ +/** + * Module dependencies. + */ + +var Connection = require('../../connection') + , mongo = require('mongodb') + , Server = mongo.Server + , ReplSetServers = mongo.ReplSetServers; + +/** + * Connection for mongodb-native driver + * + * @api private + */ + +function NativeConnection() { + Connection.apply(this, arguments); +}; + +/** + * Inherits from Connection. + */ + +NativeConnection.prototype.__proto__ = Connection.prototype; + +/** + * Opens the connection. + * + * Example server options: + * auto_reconnect (default: false) + * poolSize (default: 1) + * + * Example db options: + * pk - custom primary key factory to generate `_id` values + * + * Some of these may break Mongoose. Use at your own risk. You have been warned. + * + * @param {Function} callback + * @api private + */ + +NativeConnection.prototype.doOpen = function (fn) { + var server; + + if (!this.db) { + server = new mongo.Server(this.host, Number(this.port), this.options.server); + this.db = new mongo.Db(this.name, server, this.options.db); + } + + this.db.open(fn); + + return this; +}; + +/** + * Opens a set connection + * + * See description of doOpen for server options. In this case options.replset + * is also passed to ReplSetServers. Some additional options there are + * + * reconnectWait (default: 1000) + * retries (default: 30) + * rs_name (default: false) + * read_secondary (default: false) Are reads allowed from secondaries? + * + * @param {Function} fn + * @api private + */ + +NativeConnection.prototype.doOpenSet = function (fn) { + if (!this.db) { + var servers = [] + , ports = this.port + , self = this + + this.host.forEach(function (host, i) { + servers.push(new mongo.Server(host, Number(ports[i]), self.options.server)); + }); + + var server = new ReplSetServers(servers, this.options.replset); + this.db = new mongo.Db(this.name, server, this.options.db); + } + + this.db.open(fn); + + return this; +}; + +/** + * Closes the connection + * + * @param {Function} callback + * @api private + */ + +NativeConnection.prototype.doClose = function (fn) { + this.db.close(); + if (fn) fn(); + return this; +} + +/** + * Module exports. + */ + +module.exports = NativeConnection; diff --git a/node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js b/node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js new file mode 100644 index 0000000..ee6d598 --- /dev/null +++ b/node_modules/mongoose/lib/drivers/node-mongodb-native/objectid.js @@ -0,0 +1,43 @@ + +/** + * Module dependencies. + */ + +var ObjectId = require('mongodb').BSONPure.ObjectID; + +/** + * Constructor export + * + * @api private + */ + +var ObjectIdToString = ObjectId.toString.bind(ObjectId); + +module.exports = exports = ObjectId; +/** + * Creates an ObjectID for this driver + * + * @param {Object} hex string or ObjectId + * @api private + */ + +exports.fromString = function(str){ + // patch native driver bug in V0.9.6.4 + if (!('string' === typeof str && 24 === str.length)) { + throw new Error("Invalid ObjectId"); + } + + return ObjectId.createFromHexString(str); +}; + +/** + * Gets an ObjectId and converts it to string. + * + * @param {ObjectId} -native objectid + * @api private + */ + +exports.toString = function(oid){ + if (!arguments.length) return ObjectIdToString(); + return oid.toHexString(); +}; diff --git a/node_modules/mongoose/lib/error.js b/node_modules/mongoose/lib/error.js new file mode 100644 index 0000000..bd4ee61 --- /dev/null +++ b/node_modules/mongoose/lib/error.js @@ -0,0 +1,25 @@ + +/** + * Mongoose error + * + * @api private + */ + +function MongooseError (msg) { + Error.call(this); + Error.captureStackTrace(this, arguments.callee); + this.message = msg; + this.name = 'MongooseError'; +}; + +/** + * Inherits from Error. + */ + +MongooseError.prototype.__proto__ = Error.prototype; + +/** + * Module exports. + */ + +module.exports = MongooseError; diff --git a/node_modules/mongoose/lib/index.js b/node_modules/mongoose/lib/index.js new file mode 100644 index 0000000..6fd449e --- /dev/null +++ b/node_modules/mongoose/lib/index.js @@ -0,0 +1,368 @@ + +/** + * Module dependencies. + */ + +var Schema = require('./schema') + , SchemaType = require('./schematype') + , VirtualType = require('./virtualtype') + , SchemaTypes = Schema.Types + , SchemaDefaults = require('./schemadefault') + , Types = require('./types') + , Query = require('./query') + , Promise = require('./promise') + , Model = require('./model') + , Document = require('./document') + , utils = require('./utils'); + +/** + * Mongoose constructor. Most apps will only use one instance. + * + * @api public + */ + +function Mongoose () { + this.connections = []; + this.plugins = []; + this.models = {}; + this.modelSchemas = {}; + this.options = {}; + this.createConnection(); // default connection +}; + +/** + * Sets/gets mongoose options + * + * Examples: + * mongoose.set('test') // returns the 'test' value + * mongoose.set('test', value) // sets the 'test' value + * + * @param {String} key + * @param {String} value + * @api public + */ + +Mongoose.prototype.set = +Mongoose.prototype.get = function (key, value) { + if (arguments.length == 1) + return this.options[key]; + this.options[key] = value; + return this; +}; + +/** + * Creates a Connection instance. + * + * Examples: + * + * // with mongodb:// URI + * db = mongoose.createConnection('mongodb://localhost:port/database'); + * + * // with [host, database_name[, port] signature + * db = mongoose.createConnection('localhost', 'database', port) + * + * // initialize now, connect later + * db = mongoose.createConnection(); + * db.open('localhost', 'database', port); + * + * @param {String} mongodb:// URI + * @return {Connection} the created Connection object + * @api public + */ + +Mongoose.prototype.createConnection = function () { + var conn = new Connection(this); + this.connections.push(conn); + if (arguments.length) + conn.open.apply(conn, arguments); + return conn; +}; + +/** + * Creates a replica set connection + * + * @see {Mongoose#createConnection} + * @api public + */ + +Mongoose.prototype.createSetConnection = function () { + var conn = new Connection(this); + this.connections.push(conn); + if (arguments.length) + conn.openSet.apply(conn, arguments); + return conn; +}; + +/** + * Connects the default mongoose connection + * + * @see {Mongoose#createConnection} + * @api public + */ + +Mongoose.prototype.connect = function (){ + this.connection.open.apply(this.connection, arguments); + return this; +}; + +/** + * Connects the default mongoose connection to a replica set + * + * @see {Mongoose#createConnection} + * @api public + */ + +Mongoose.prototype.connectSet = function (){ + this.connection.openSet.apply(this.connection, arguments); + return this; +}; + +/** + * Disconnects from all connections. + * + * @param {Function} optional callback + * @api public + */ + +Mongoose.prototype.disconnect = function (fn) { + var count = this.connections.length; + this.connections.forEach(function(conn){ + conn.close(function(err){ + if (err) return fn(err); + if (fn) + --count || fn(); + }); + }); + return this; +}; + +/** + * Defines a model or retrieves it + * + * @param {String} model name + * @param {Schema} schema object + * @param {String} collection name (optional, induced from model name) + * @param {Boolean} whether to skip initialization (defaults to false) + * @api public + */ + +Mongoose.prototype.model = function (name, schema, collection, skipInit) { + // normalize collection + if (!(schema instanceof Schema)) { + collection = schema; + schema = false; + } + + if ('boolean' === typeof collection) { + skipInit = collection; + collection = null; + } + + // look up models for the collection + if (!this.modelSchemas[name]) { + if (!schema && name in SchemaDefaults) { + schema = SchemaDefaults[name]; + } + + if (schema) { + this.modelSchemas[name] = schema; + for (var i = 0, l = this.plugins.length; i < l; i++) { + schema.plugin(this.plugins[i][0], this.plugins[i][1]); + } + } else { + throw new Error('Schema hasn\'t been registered for model "' + name + '".\n' + + 'Use mongoose.model(name, schema)'); + } + } + + if (!schema) { + schema = this.modelSchemas[name]; + } + + if (!collection) { + collection = schema.set('collection') || utils.toCollectionName(name); + } + + if (!this.models[name]) { + var model = Model.compile(name + , this.modelSchemas[name] + , collection + , this.connection + , this); + + if (!skipInit) model.init(); + + this.models[name] = model; + } + + return this.models[name]; +}; + +/** + * Declares a plugin executed on Schemas. Equivalent to calling `.plugin(fn)` + * on each Schema you create. + * + * @param {Function} plugin callback + * @api public + */ + +Mongoose.prototype.plugin = function (fn, opts) { + this.plugins.push([fn, opts]); + return this; +}; + +/** + * Default connection + * + * @api public + */ + +Mongoose.prototype.__defineGetter__('connection', function(){ + return this.connections[0]; +}); + +/** + * Driver depentend APIs + */ + +var driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native'; + +/** + * Connection + * + * @api public + */ + +var Connection = require(driver + '/connection'); + +/** + * Collection + * + * @api public + */ + +var Collection = require(driver + '/collection'); + +/** + * Export default singleton. + * + * @api public + */ + +module.exports = exports = new Mongoose(); + +/** + * Collection + * + * @api public + */ + +exports.Collection = Collection; + +/** + * Connection + * + * @api public + */ + +exports.Connection = Connection; + +/** + * Exports Mongoose version + * + * @param version + */ + +exports.version = JSON.parse( + require('fs').readFileSync(__dirname + '/../package.json', 'utf8') +).version; + +/** + * Export Mongoose constructor + * + * @api public + */ + +exports.Mongoose = Mongoose; + +/** + * Export Schema constructor + * + * @api public + */ + +exports.Schema = Schema; + +/** + * Export SchemaType constructor. + * + * @api public + */ + +exports.SchemaType = SchemaType; + +/** + * Export VirtualType constructor. + * + * @api public + */ + +exports.VirtualType = VirtualType; + +/** + * Export Schema types + * + * @api public + */ + +exports.SchemaTypes = SchemaTypes; + +/** + * Export types + * + * @api public + */ + +exports.Types = Types; + +/** + * Export Query + * + * @api public + */ + +exports.Query = Query; + +/** + * Export Promise + * + * @api public + */ + +exports.Promise = Promise; + +/** + * Export Model constructor + * + * @api public + */ + +exports.Model = Model; + +/** + * Export Document constructor + * + * @api public + */ + +exports.Document = Document; + +/** + * Export MongooseError + * + * @api public + */ + +exports.Error = require('./error'); + +exports.mongo = require('mongodb'); diff --git a/node_modules/mongoose/lib/model.js b/node_modules/mongoose/lib/model.js new file mode 100644 index 0000000..42e45f7 --- /dev/null +++ b/node_modules/mongoose/lib/model.js @@ -0,0 +1,861 @@ + +/** + * Module dependencies. + */ + +var Document = require('./document') + , MongooseArray = require('./types/array') + , MongooseBuffer = require('./types/buffer') + , MongooseError = require('./error') + , Query = require('./query') + , utils = require('./utils') + , isMongooseObject = utils.isMongooseObject + , EventEmitter = utils.EventEmitter + , merge = utils.merge + , Promise = require('./promise') + , tick = utils.tick + +/** + * Model constructor + * + * @param {Object} values to set + * @api public + */ + +function Model (doc, fields) { + Document.call(this, doc, fields); +}; + +/** + * Inherits from Document. + */ + +Model.prototype.__proto__ = Document.prototype; + +/** + * Connection the model uses. Set by the Connection or if absent set to the + * default mongoose connection; + * + * @api public + */ + +Model.prototype.db; + +/** + * Collection the model uses. Set by Mongoose instance + * + * @api public + */ + +Model.prototype.collection; + +/** + * Model name. + * + * @api public + */ + +Model.prototype.modelName; + +/** + * Returns what paths can be populated + * + * @param {query} query object + * @return {Object] population paths + * @api private + */ + +Model.prototype._getPopulationKeys = function getPopulationKeys (query) { + if (!(query && query.options.populate)) return; + + var names = Object.keys(query.options.populate) + , n = names.length + , name + , paths = {} + , hasKeys + , schema + + while (n--) { + name = names[n]; + schema = this.schema.path(name); + hasKeys = true; + + if (!schema) { + // if the path is not recognized, it's potentially embedded docs + // walk path atoms from right to left to find a matching path + var pieces = name.split('.') + , i = pieces.length; + + while (i--) { + var path = pieces.slice(0, i).join('.') + , pathSchema = this.schema.path(path); + + // loop until we find an array schema + if (pathSchema && pathSchema.caster) { + if (!paths[path]) { + paths[path] = { sub: {} }; + } + + paths[path].sub[pieces.slice(i).join('.')] = query.options.populate[name]; + hasKeys || (hasKeys = true); + break; + } + } + } else { + paths[name] = query.options.populate[name]; + hasKeys || (hasKeys = true); + } + } + + return hasKeys && paths; +}; + +/** + * Populates an object + * + * @param {SchemaType} schema type for the oid + * @param {Object} object id or array of object ids + * @param {Object} object specifying query conditions, fields, and options + * @param {Function} callback + * @api private + */ + +Model.prototype._populate = function populate (schema, oid, query, fn) { + if (!Array.isArray(oid)) { + var conditions = query.conditions || {}; + conditions._id = oid; + + return this + .model(schema.options.ref) + .findOne(conditions, query.fields, query.options, fn); + } + + if (!oid.length) { + return fn(null, oid); + } + + var model = this.model(schema.caster.options.ref) + , conditions = query && query.conditions || {}; + conditions._id || (conditions._id = { $in: oid }); + + model.find(conditions, query.fields, query.options, function (err, docs) { + if (err) return fn(err); + + // user specified sort order? + if (query.options && query.options.sort) { + return fn(null, docs); + } + + // put back in original id order (using a hash reduces complexity from n*n to 2n) + var docHash = {}; + docs.forEach(function (doc) { + docHash[doc._id] = doc; + }); + + var arr = []; + oid.forEach(function (id) { + if (id in docHash) arr.push(docHash[id]); + }); + + fn(null, arr); + }); +}; + +/** + * Performs auto-population of relations. + * + * @param {Object} document returned by mongo + * @param {Query} query that originated the initialization + * @param {Function} callback + * @api private + */ + +Model.prototype.init = function init (doc, query, fn) { + if ('function' == typeof query) { + fn = query; + query = null; + } + + var populate = this._getPopulationKeys(query); + + if (!populate) { + return Document.prototype.init.call(this, doc, fn); + } + + // population from other models is necessary + var self = this; + + init(doc, '', function (err) { + if (err) return fn(err); + Document.prototype.init.call(self, doc, fn); + }); + + return this; + + function init (obj, prefix, fn) { + prefix = prefix || ''; + + var keys = Object.keys(obj) + , len = keys.length; + + function next () { + if (--len < 0) return fn(); + + var i = keys[len] + , path = prefix + i + , schema = self.schema.path(path) + , total = 0 + , poppath + + if (!schema && obj[i] && 'Object' === obj[i].constructor.name) { + // assume nested object + return init(obj[i], path + '.', next); + } + + if (!(obj[i] && schema && populate[path])) return next(); + + // this query object is re-used and passed around. we clone + // it to prevent query condition contamination between + // one populate call to the next. + poppath = utils.clone(populate[path]); + + if (poppath.sub) { + obj[i].forEach(function (subobj) { + var pkeys = Object.keys(poppath.sub) + , pi = pkeys.length + , key + + while (pi--) { + key = pkeys[pi]; + + if (subobj[key]) (function (key) { + + total++; + self._populate(schema.schema.path(key), subobj[key], poppath.sub[key], done); + function done (err, doc) { + if (err) return error(err); + subobj[key] = doc; + --total || next(); + } + })(key); + } + }); + + if (0 === total) return next(); + + } else { + self._populate(schema, obj[i], poppath, function (err, doc) { + if (err) return error(err); + obj[i] = doc; + next(); + }); + } + }; + + next(); + }; + + function error (err) { + if (error.err) return; + fn(error.err = err); + } +}; + +function handleSave (promise, self) { + return tick(function handleSave (err) { + if (err) return promise.error(err); + self.emit('save', self); + promise.complete(self); + promise = null; + self = null; + }); +} + +/** + * Saves this document. + * + * @see Model#registerHooks + * @param {Function} fn + * @api public + */ + +Model.prototype.save = function save (fn) { + var promise = new Promise(fn) + , complete = handleSave(promise, this) + , options = {} + + if (this.options.safe) { + options.safe = this.options.safe; + } + + if (this.isNew) { + // send entire doc + this.collection.insert(this.toObject({ depopulate: 1 }), options, complete); + this._reset(); + this.isNew = false; + this.emit('isNew', false); + + } else { + var delta = this._delta(); + this._reset(); + + if (delta) { + this.collection.update({ _id: this._doc._id }, delta, options, complete); + } else { + complete(null); + } + } +}; + +/** + * Produces a special query document of the modified properties. + * @api private + */ + +Model.prototype._delta = function _delta () { + var dirty = this._dirty(); + + if (!dirty.length) return; + + var self = this + , useSet = this.options['use$SetOnSave']; + + return dirty.reduce(function (delta, data) { + var type = data.value + , schema = data.schema + , atomics + , val + , obj + + if (type === undefined) { + if (!delta.$unset) delta.$unset = {}; + delta.$unset[data.path] = 1; + + } else if (type === null) { + if (!delta.$set) delta.$set = {}; + delta.$set[data.path] = type; + + } else if (type._path && type.doAtomics) { + // a MongooseArray or MongooseNumber + atomics = type._atomics; + + var ops = Object.keys(atomics) + , i = ops.length + , op; + + while (i--) { + op = ops[i] + + if (op === '$pushAll' || op === '$pullAll') { + if (atomics[op].length === 1) { + val = atomics[op][0]; + delete atomics[op]; + op = op.replace('All', ''); + atomics[op] = val; + } + } + + val = atomics[op]; + obj = delta[op] = delta[op] || {}; + + if (op === '$pull' || op === '$push') { + if ('Object' !== val.constructor.name) { + if (Array.isArray(val)) val = [val]; + // TODO Should we place pull and push casting into the pull and push methods? + val = schema.cast(val)[0]; + } + } + + obj[data.path] = isMongooseObject(val) + ? val.toObject({ depopulate: 1 }) // MongooseArray + : Array.isArray(val) + ? val.map(function (mem) { + return isMongooseObject(mem) + ? mem.toObject({ depopulate: 1 }) + : mem.valueOf + ? mem.valueOf() + : mem; + }) + : val.valueOf + ? val.valueOf() // Numbers + : val; + + if ('$addToSet' === op) { + if (val.length > 1) { + obj[data.path] = { $each: obj[data.path] }; + } else { + obj[data.path] = obj[data.path][0]; + } + } + } + } else { + // normalize MongooseArray or MongooseNumber + if (type instanceof MongooseArray || + type instanceof MongooseBuffer) { + type = type.toObject({ depopulate: 1 }); + } else if (type._path) + type = type.valueOf(); + + if (useSet) { + if (!('$set' in delta)) + delta['$set'] = {}; + + delta['$set'][data.path] = type; + } else + delta[data.path] = type; + } + + return delta; + }, {}); +} + +/** + * Remove the document + * + * @param {Function} callback + * @api public + */ + +Model.prototype.remove = function remove (fn) { + if (this._removing) return this; + + var promise = this._removing = new Promise(fn) + , self = this; + + this.collection.remove({ _id: this._doc._id }, tick(function (err) { + if (err) { + this._removing = null; + return promise.error(err); + } + promise.complete(); + self.emit('remove'); + })); + + return this; +}; + +/** + * Register hooks override + * + * @api private + */ + +Model.prototype._registerHooks = function registerHooks () { + Document.prototype._registerHooks.call(this); +}; + +/** + * Shortcut to access another model. + * + * @param {String} model name + * @api public + */ + +Model.prototype.model = function model (name) { + return this.db.model(name); +}; + +/** + * Access the options defined in the schema + * + * @api private + */ + +Model.prototype.__defineGetter__('options', function () { + return this.schema ? this.schema.options : {}; +}); + +/** + * Give the constructor the ability to emit events. + */ + +for (var i in EventEmitter.prototype) + Model[i] = EventEmitter.prototype[i]; + +/** + * Called when the model compiles + * + * @api private + */ + +Model.init = function init () { + // build indexes + var self = this + , indexes = this.schema.indexes + , count = indexes.length; + + indexes.forEach(function (index) { + self.collection.ensureIndex(index[0], index[1], tick(function (err) { + if (err) return self.db.emit('error', err); + --count || self.emit('index'); + })); + }); +}; + +/** + * Document schema + * + * @api public + */ + +Model.schema; + +/** + * Database instance the model uses. + * + * @api public + */ + +Model.db; + +/** + * Collection the model uses. + * + * @api public + */ + +Model.collection; + +/** + * Define properties that access the prototype. + */ + +['db', 'collection', 'schema', 'options'].forEach(function(prop){ + Model.__defineGetter__(prop, function(){ + return this.prototype[prop]; + }); +}); + +/** + * Module exports. + */ + +module.exports = exports = Model; + +Model.remove = function remove (conditions, callback) { + if ('function' === typeof conditions) { + callback = conditions; + conditions = {}; + } + + var query = new Query(conditions).bind(this, 'remove'); + + if ('undefined' === typeof callback) + return query; + + this._applyNamedScope(query); + return query.remove(callback); +}; + +/** + * Finds documents + * + * Examples: + * // retrieve only certain keys + * MyModel.find({ name: /john/i }, ['name', 'friends'], function () { }) + * + * // pass options + * MyModel.find({ name: /john/i }, [], { skip: 10 } ) + * + * @param {Object} conditions + * @param {Object/Function} (optional) fields to hydrate or callback + * @param {Function} callback + * @api public + */ + +Model.find = function find (conditions, fields, options, callback) { + if ('function' == typeof conditions) { + callback = conditions; + conditions = {}; + fields = null; + options = null; + } else if ('function' == typeof fields) { + callback = fields; + fields = null; + options = null; + } else if ('function' == typeof options) { + callback = options; + options = null; + } + + var query = new Query(conditions, options).select(fields).bind(this, 'find'); + + if ('undefined' === typeof callback) + return query; + + this._applyNamedScope(query); + return query.find(callback); +}; + +/** + * Merges the current named scope query into `query`. + * + * @param {Query} query + * @api private + */ + +Model._applyNamedScope = function _applyNamedScope (query) { + var cQuery = this._cumulativeQuery; + + if (cQuery) { + merge(query._conditions, cQuery._conditions); + if (query._fields && cQuery._fields) + merge(query._fields, cQuery._fields); + if (query.options && cQuery.options) + merge(query.options, cQuery.options); + delete this._cumulativeQuery; + } + + return query; +} + +/** + * Finds by id + * + * @param {ObjectId/Object} objectid, or a value that can be casted to it + * @api public + */ + +Model.findById = function findById (id, fields, options, callback) { + return this.findOne({ _id: id }, fields, options, callback); +}; + +/** + * Finds one document + * + * @param {Object} conditions + * @param {Object/Function} (optional) fields to hydrate or callback + * @param {Function} callback + * @api public + */ + +Model.findOne = function findOne (conditions, fields, options, callback) { + if ('function' == typeof options) { + // TODO Handle all 3 of the following scenarios + // Hint: Only some of these scenarios are possible if cQuery is present + // Scenario: findOne(conditions, fields, callback); + // Scenario: findOne(fields, options, callback); + // Scenario: findOne(conditions, options, callback); + callback = options; + options = null; + } else if ('function' == typeof fields) { + // TODO Handle all 2 of the following scenarios + // Scenario: findOne(conditions, callback) + // Scenario: findOne(fields, callback) + // Scenario: findOne(options, callback); + callback = fields; + fields = null; + options = null; + } else if ('function' == typeof conditions) { + callback = conditions; + conditions = {}; + fields = null; + options = null; + } + + var query = new Query(conditions, options).select(fields).bind(this, 'findOne'); + + if ('undefined' == typeof callback) + return query; + + this._applyNamedScope(query); + return query.findOne(callback); +}; + +/** + * Counts documents + * + * @param {Object} conditions + * @param {Function} optional callback + * @api public + */ + +Model.count = function count (conditions, callback) { + if ('function' === typeof conditions) + callback = conditions, conditions = {}; + + var query = new Query(conditions).bind(this, 'count'); + if ('undefined' == typeof callback) + return query; + + this._applyNamedScope(query); + return query.count(callback); +}; + +Model.distinct = function distinct (field, conditions, callback) { + var query = new Query(conditions).bind(this, 'distinct'); + if ('undefined' == typeof callback) { + query._distinctArg = field; + return query; + } + + this._applyNamedScope(query); + return query.distinct(field, callback); +}; + +/** + * `where` enables a very nice sugary api for doing your queries. + * For example, instead of writing: + * User.find({age: {$gte: 21, $lte: 65}}, callback); + * we can instead write more readably: + * User.where('age').gte(21).lte(65); + * Moreover, you can also chain a bunch of these together like: + * User + * .where('age').gte(21).lte(65) + * .where('name', /^b/i) // All names that begin where b or B + * .where('friends').slice(10); + * @param {String} path + * @param {Object} val (optional) + * @return {Query} + * @api public + */ + +Model.where = function where (path, val) { + var q = new Query().bind(this, 'find'); + return q.where.apply(q, arguments); +}; + +/** + * Sometimes you need to query for things in mongodb using a JavaScript + * expression. You can do so via find({$where: javascript}), or you can + * use the mongoose shortcut method $where via a Query chain or from + * your mongoose Model. + * + * @param {String|Function} js is a javascript string or anonymous function + * @return {Query} + * @api public + */ + +Model.$where = function $where () { + var q = new Query().bind(this, 'find'); + return q.$where.apply(q, arguments); +}; + +/** + * Shortcut for creating a new Document that is automatically saved + * to the db if valid. + * + * @param {Object} doc + * @param {Function} callback + * @api public + */ + +Model.create = function create (doc, fn) { + if (1 === arguments.length) { + return 'function' === typeof doc && doc(null); + } + + var self = this + , docs = [null] + , promise + , count + , args + + if (Array.isArray(doc)) { + args = doc; + } else { + args = utils.args(arguments, 0, arguments.length - 1); + fn = arguments[arguments.length - 1]; + } + + if (0 === args.length) return fn(null); + + promise = new Promise(fn); + count = args.length; + + args.forEach(function (arg, i) { + var doc = new self(arg); + docs[i+1] = doc; + doc.save(function (err) { + if (err) return promise.error(err); + --count || fn.apply(null, docs); + }); + }); + + // TODO + // utilize collection.insertAll for batch processing +}; + +/** + * Updates documents. + * + * Examples: + * + * MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn); + * MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, fn); + * + * Valid options: + * - safe (boolean) safe mode (defaults to value set in schema (true)) + * - upsert (boolean) whether to create the doc if it doesn't match (false) + * - multi (boolean) whether multiple documents should be update (false) + * + * @param {Object} conditions + * @param {Object] doc + * @param {Object} options + * @param {Function} callback + * @return {Query} + * @api public + */ + +Model.update = function update (conditions, doc, options, callback) { + if (arguments.length < 4) { + if ('function' === typeof options) { + // Scenario: update(conditions, doc, callback) + callback = options; + options = null; + } else if ('function' === typeof doc) { + // Scenario: update(doc, callback); + callback = doc; + doc = conditions; + conditions = {}; + options = null; + } + } + + var query = new Query(conditions, options).bind(this, 'update', doc); + + if ('undefined' == typeof callback) + return query; + + this._applyNamedScope(query); + return query.update(doc, callback); +}; + +/** + * Compiler utility. + * + * @param {String} model name + * @param {Schema} schema object + * @param {String} collection name + * @param {Connection} connection to use + * @param {Mongoose} mongoose instance + * @api private + */ + +Model.compile = function compile (name, schema, collectionName, connection, base) { + // generate new class + function model () { + Model.apply(this, arguments); + }; + + model.modelName = name; + model.__proto__ = Model; + model.prototype.__proto__ = Model.prototype; + model.prototype.base = base; + model.prototype.schema = schema; + model.prototype.db = connection; + model.prototype.collection = connection.collection(collectionName); + + // apply methods + for (var i in schema.methods) + model.prototype[i] = schema.methods[i]; + + // apply statics + for (var i in schema.statics) + model[i] = schema.statics[i]; + + // apply named scopes + if (schema.namedScopes) schema.namedScopes.compile(model); + + return model; +}; diff --git a/node_modules/mongoose/lib/namedscope.js b/node_modules/mongoose/lib/namedscope.js new file mode 100644 index 0000000..1b3f5d4 --- /dev/null +++ b/node_modules/mongoose/lib/namedscope.js @@ -0,0 +1,70 @@ +var Query = require('./query'); +function NamedScope () {} + +NamedScope.prototype.query; + +NamedScope.prototype.where = function () { + var q = this.query || (this.query = new Query()); + q.where.apply(q, arguments); + return q; +}; + +/** + * Decorate + * + * @param {NamedScope} target + * @param {Object} getters + * @api private + */ + +NamedScope.prototype.decorate = function (target, getters) { + var name = this.name + , block = this.block + , query = this.query; + if (block) { + if (block.length === 0) { + Object.defineProperty(target, name, { + get: getters.block0(block) + }); + } else { + target[name] = getters.blockN(block); + } + } else { + Object.defineProperty(target, name, { + get: getters.basic(query) + }); + } +}; + +NamedScope.prototype.compile = function (model) { + var allScopes = this.scopesByName + , scope; + for (var k in allScopes) { + scope = allScopes[k]; + scope.decorate(model, { + block0: function (block) { + return function () { + var cquery = this._cumulativeQuery || (this._cumulativeQuery = new Query().bind(this)); + block.call(cquery); + return this; + }; + }, + blockN: function (block) { + return function () { + var cquery = this._cumulativeQuery || (this._cumulativeQuery = new Query().bind(this)); + block.apply(cquery, arguments); + return this; + }; + }, + basic: function (query) { + return function () { + var cquery = this._cumulativeQuery || (this._cumulativeQuery = new Query().bind(this)); + cquery.find(query); + return this; + }; + } + }); + } +}; + +module.exports = NamedScope; diff --git a/node_modules/mongoose/lib/promise.js b/node_modules/mongoose/lib/promise.js new file mode 100644 index 0000000..c632a03 --- /dev/null +++ b/node_modules/mongoose/lib/promise.js @@ -0,0 +1,145 @@ + +/** + * Module dependencies. + */ + +var util = require('./utils'); +var EventEmitter = util.EventEmitter; + +/** + * Promise constructor. + * + * @param {Function} a callback+errback that takes err, ... as signature + * @api public + */ + +function Promise (back) { + this.emitted = {}; + if ('function' == typeof back) + this.addBack(back); +}; + +/** + * Inherits from EventEmitter. + */ + +Promise.prototype.__proto__ = EventEmitter.prototype; + +/** + * Adds an event or fires the callback right away. + * + * @return promise + * @api public + */ + +Promise.prototype.on = function (event, callback) { + if (this.emitted[event]) + callback.apply(this, this.emitted[event]); + else + EventEmitter.prototype.on.call(this, event, callback); + + return this; +}; + +/** + * Keeps track of emitted events to run them on `on` + * + * @api private + */ + +Promise.prototype.emit = function (event) { + // ensures a promise can't be complete() or error() twice + if (event == 'err' || event == 'complete'){ + if (this.emitted.err || this.emitted.complete) { + return this; + } + this.emitted[event] = util.args(arguments, 1); + } + + return EventEmitter.prototype.emit.apply(this, arguments); +}; + +/** + * Shortcut for emitting complete event + * + * @api public + */ + +Promise.prototype.complete = function () { + var args = util.args(arguments); + return this.emit.apply(this, ['complete'].concat(args)); +}; + +/** + * Shortcut for emitting err event + * + * @api public + */ + +Promise.prototype.error = function () { + var args = util.args(arguments); + return this.emit.apply(this, ['err'].concat(args)); +}; + +/** + * Shortcut for `.on('complete', fn)` + * + * @return promise + * @api public + */ + +Promise.prototype.addCallback = function (fn) { + return this.on('complete', fn); +}; + +/** + * Shortcut for `.on('err', fn)` + * + * @return promise + * @api public + */ + +Promise.prototype.addErrback = function (fn) { + return this.on('err', fn); +}; + +/** + * Adds a single function that's both callback and errback + * + * @return promise + * @api private + */ + +Promise.prototype.addBack = function (fn) { + this.on('err', function(err){ + fn.call(this, err); + }); + + this.on('complete', function(){ + var args = util.args(arguments); + fn.apply(this, [null].concat(args)); + }); + + return this; +}; + +/** + * Sugar for handling cases where you may be + * resolving to either an error condition or a + * success condition. + * + * @param {Error} optional error or null + * @param {Object} value to complete the promise with + * @api public + */ + +Promise.prototype.resolve = function (err, val) { + if (err) return this.error(err); + return this.complete(val); +}; + +/** + * Module exports. + */ + +module.exports = Promise; diff --git a/node_modules/mongoose/lib/query.js b/node_modules/mongoose/lib/query.js new file mode 100644 index 0000000..f38f526 --- /dev/null +++ b/node_modules/mongoose/lib/query.js @@ -0,0 +1,1306 @@ +/** + * Module dependencies. + */ + +var utils = require('./utils') + , merge = utils.merge + , Promise = require('./promise') + , Document = require('./document') + , inGroupsOf = utils.inGroupsOf + , tick = utils.tick + , QueryStream = require('./querystream') + +/** + * Query constructor + * + * @api private + */ + +function Query (criteria, options) { + options = this.options = options || {}; + this.safe = options.safe + + // normalize population options + var pop = this.options.populate; + this.options.populate = {}; + + if (pop && Array.isArray(pop)) { + for (var i = 0, l = pop.length; i < l; i++) { + this.options.populate[pop[i]] = {}; + } + } + + this._conditions = {}; + if (criteria) this.find(criteria); +} + +/** + * Binds this query to a model. + * @param {Function} param + * @return {Query} + * @api public + */ + +Query.prototype.bind = function bind (model, op, updateArg) { + this.model = model; + this.op = op; + if (op === 'update') this._updateArg = updateArg; + return this; +}; + +/** + * Executes the query returning a promise. + * + * Examples: + * query.run(); + * query.run(callback); + * query.run('update'); + * query.run('find', callback); + * + * @param {String|Function} op (optional) + * @param {Function} callback (optional) + * @return {Promise} + * @api public + */ + +Query.prototype.run = +Query.prototype.exec = function (op, callback) { + var promise = new Promise(); + + switch (typeof op) { + case 'function': + callback = op; + op = null; + break; + case 'string': + this.op = op; + break; + } + + if (callback) promise.addBack(callback); + + if (!this.op) { + promise.complete(); + return promise; + } + + if ('update' == this.op) { + this.update(this._updateArg, promise.resolve.bind(promise)); + return promise; + } + + if ('distinct' == this.op) { + this.distinct(this._distinctArg, promise.resolve.bind(promise)); + return promise; + } + + this[this.op](promise.resolve.bind(promise)); + return promise; +}; + +/** + * Finds documents. + * + * @param {Object} criteria + * @param {Function} callback + * @api public + */ + +Query.prototype.find = function (criteria, callback) { + this.op = 'find'; + if ('function' === typeof criteria) { + callback = criteria; + criteria = {}; + } else if (criteria instanceof Query) { + // TODO Merge options, too + merge(this._conditions, criteria._conditions); + } else if (criteria instanceof Document) { + merge(this._conditions, criteria.toObject()); + } else if (criteria && 'Object' === criteria.constructor.name) { + merge(this._conditions, criteria); + } + if (!callback) return this; + return this.execFind(callback); +}; + +/** + * Casts obj, or if obj is not present, then this._conditions, + * based on the model's schema. + * + * @param {Function} model + * @param {Object} obj (optional) + * @api public + */ + +Query.prototype.cast = function (model, obj) { + obj || (obj= this._conditions); + + var schema = model.schema + , paths = Object.keys(obj) + , i = paths.length + , any$conditionals + , schematype + , nested + , path + , type + , val; + + while (i--) { + path = paths[i]; + val = obj[path]; + + if (path === '$or') { + var k = val.length + , orComponentQuery; + + while (k--) { + orComponentQuery = new Query(val[k]); + orComponentQuery.cast(model); + val[k] = orComponentQuery._conditions; + } + + } else if (path === '$where') { + type = typeof val; + + if ('string' !== type && 'function' !== type) { + throw new Error("Must have a string or function for $where"); + } + + if ('function' === type) { + obj[path] = val.toString(); + } + + continue; + + } else { + + if (!schema) { + // no casting for Mixed types + continue; + } + + schematype = schema.path(path); + + if (!schematype) { + // Handle potential embedded array queries + var split = path.split('.') + , j = split.length + , pathFirstHalf + , pathLastHalf + , remainingConds + , castingQuery; + + // Find the part of the var path that is a path of the Schema + while (j--) { + pathFirstHalf = split.slice(0, j).join('.'); + schematype = schema.path(pathFirstHalf); + if (schematype) break; + } + + // If a substring of the input path resolves to an actual real path... + if (schematype) { + // Apply the casting; similar code for $elemMatch in schema/array.js + if (schematype.caster && schematype.caster.schema) { + remainingConds = {}; + pathLastHalf = split.slice(j).join('.'); + remainingConds[pathLastHalf] = val; + castingQuery = new Query(remainingConds); + castingQuery.cast(schematype.caster); + obj[path] = castingQuery._conditions[pathLastHalf]; + } else { + obj[path] = val; + } + } + + } else if (val === null || val === undefined) { + continue; + } else if ('Object' === val.constructor.name) { + + any$conditionals = Object.keys(val).some(function (k) { + return k.charAt(0) === '$'; + }); + + if (!any$conditionals) { + obj[path] = schematype.castForQuery(val); + } else { + + var ks = Object.keys(val) + , k = ks.length + , $cond; + + while (k--) { + $cond = ks[k]; + nested = val[$cond]; + + if ('$exists' === $cond) { + if ('boolean' !== typeof nested) { + throw new Error("$exists parameter must be Boolean"); + } + continue; + } + + if ('$type' === $cond) { + if ('number' !== typeof nested) { + throw new Error("$type parameter must be Number"); + } + continue; + } + + if ('$not' === $cond) { + this.cast(model, nested); + } else { + val[$cond] = schematype.castForQuery($cond, nested); + } + } + } + } else { + obj[path] = schematype.castForQuery(val); + } + } + } +}; + +/** + * Returns default options. + * @api private + */ + +Query.prototype._optionsForExec = function (model) { + var options = utils.clone(this.options, { retainKeyOrder: true }); + delete options.populate; + if (! ('safe' in options)) options.safe = model.options.safe; + return options; +}; + +/** + * Sometimes you need to query for things in mongodb using a JavaScript + * expression. You can do so via find({$where: javascript}), or you can + * use the mongoose shortcut method $where via a Query chain or from + * your mongoose Model. + * + * @param {String|Function} js is a javascript string or anonymous function + * @return {Query} + * @api public + */ + +Query.prototype.$where = function (js) { + this._conditions['$where'] = js; + return this; +}; + +/** + * `where` enables a very nice sugary api for doing your queries. + * For example, instead of writing: + * User.find({age: {$gte: 21, $lte: 65}}, callback); + * we can instead write more readably: + * User.where('age').gte(21).lte(65); + * Moreover, you can also chain a bunch of these together like: + * User + * .where('age').gte(21).lte(65) + * .where('name', /^b/i) // All names that begin where b or B + * .where('friends').slice(10); + * @param {String} path + * @param {Object} val (optional) + * @return {Query} + * @api public + */ + +Query.prototype.where = function (path, val) { + if (2 === arguments.length) { + this._conditions[path] = val; + } + this._currPath = path; + return this; +}; + +Query.prototype.or = +Query.prototype.$or = function $or (array) { + var or = this._conditions.$or || (this._conditions.$or = []); + if (!Array.isArray(array)) array = [array]; + or.push.apply(or, array); + return this; +} + +// $lt, $lte, $gt, $gte can be used on Numbers or Dates +'gt gte lt lte ne in nin all regex size maxDistance'.split(' ').forEach( function ($conditional) { + Query.prototype['$' + $conditional] = + Query.prototype[$conditional] = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$' + $conditional] = val; + return this; + }; +}); + +Query.prototype.notEqualTo = Query.prototype.ne; + +;['mod', 'near'].forEach( function ($conditional) { + Query.prototype['$' + $conditional] = + Query.prototype[$conditional] = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath + } else if (arguments.length === 2 && !Array.isArray(val)) { + val = utils.args(arguments); + path = this._currPath; + } else if (arguments.length === 3) { + val = utils.args(arguments, 1); + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$' + $conditional] = val; + return this; + }; +}); + +Query.prototype['$exists'] = +Query.prototype.exists = function (path, val) { + if (arguments.length === 0) { + path = this._currPath + val = true; + } else if (arguments.length === 1) { + if ('boolean' === typeof path) { + val = path; + path = this._currPath; + } else { + val = true; + } + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$exists'] = val; + return this; +}; + +Query.prototype['$elemMatch'] = +Query.prototype.elemMatch = function (path, criteria) { + var block; + if ('Object' === path.constructor.name) { + criteria = path; + path = this._currPath; + } else if ('function' === typeof path) { + block = path; + path = this._currPath; + } else if ('Object' === criteria.constructor.name) { + } else if ('function' === typeof criteria) { + block = criteria; + } else { + throw new Error("Argument error"); + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + if (block) { + criteria = new Query(); + block(criteria); + conds['$elemMatch'] = criteria._conditions; + } else { + conds['$elemMatch'] = criteria; + } + return this; +}; + +/** + * @private + */ + +function me () { return this } + +/** + * Spatial queries + */ + +// query.within.box() +// query.within.center() +var within = 'within $within'.split(' '); +within.push('wherein', '$wherein'); // deprecated, an old mistake possibly? +within.forEach(function (getter) { + Object.defineProperty(Query.prototype, getter, { + get: me + }); +}); + +Query.prototype['$box'] = +Query.prototype.box = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath; + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$within'] = { '$box': [val.ll, val.ur] }; + return this; +}; + +Query.prototype['$center'] = +Query.prototype.center = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath; + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$within'] = { '$center': [val.center, val.radius] }; + return this; +}; + +Query.prototype['$centerSphere'] = +Query.prototype.centerSphere = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath; + } + var conds = this._conditions[path] || (this._conditions[path] = {}); + conds['$within'] = { '$centerSphere': [val.center, val.radius] }; + return this; +}; + +/** + * Chainable method for specifying which fields + * to include or exclude from the document that is + * returned from MongoDB. + * + * Examples: + * query.fields({a: 1, b: 1, c: 1, _id: 0}); + * query.fields('a b c'); + * + * @param {Object} + */ + +Query.prototype.select = +Query.prototype.fields = function () { + var arg0 = arguments[0]; + if (!arg0) return this; + if ('Object' === arg0.constructor.name || Array.isArray(arg0)) { + this._applyFields(arg0); + } else if (arguments.length === 1 && typeof arg0 === 'string') { + this._applyFields({only: arg0}); + } else { + this._applyFields({only: this._parseOnlyExcludeFields.apply(this, arguments)}); + } + return this; +}; + +/** + * Chainable method for adding the specified fields to the + * object of fields to only include. + * + * Examples: + * query.only('a b c'); + * query.only('a', 'b', 'c'); + * query.only(['a', 'b', 'c']); + * @param {String|Array} space separated list of fields OR + * an array of field names + * We can also take arguments as the "array" of field names + * @api public + */ + +Query.prototype.only = function (fields) { + fields = this._parseOnlyExcludeFields.apply(this, arguments); + this._applyFields({ only: fields }); + return this; +}; + +/** + * Chainable method for adding the specified fields to the + * object of fields to exclude. + * + * Examples: + * query.exclude('a b c'); + * query.exclude('a', 'b', 'c'); + * query.exclude(['a', 'b', 'c']); + * @param {String|Array} space separated list of fields OR + * an array of field names + * We can also take arguments as the "array" of field names + * @api public + */ + +Query.prototype.exclude = function (fields) { + fields = this._parseOnlyExcludeFields.apply(this, arguments); + this._applyFields({ exclude: fields }); + return this; +}; + +Query.prototype['$slice'] = +Query.prototype.slice = function (path, val) { + if (arguments.length === 1) { + val = path; + path = this._currPath + } else if (arguments.length === 2) { + if ('number' === typeof path) { + val = [path, val]; + path = this._currPath; + } + } else if (arguments.length === 3) { + val = utils.args(arguments, 1); + } + var myFields = this._fields || (this._fields = {}); + myFields[path] = { '$slice': val }; + return this; +}; + +/** + * Private method for interpreting the different ways + * you can pass in fields to both Query.prototype.only + * and Query.prototype.exclude. + * + * @param {String|Array|Object} fields + * @api private + */ + +Query.prototype._parseOnlyExcludeFields = function (fields) { + if (1 === arguments.length && 'string' === typeof fields) { + fields = fields.split(' '); + } else if (Array.isArray(fields)) { + // do nothing + } else { + fields = utils.args(arguments); + } + return fields; +}; + +/** + * Private method for interpreting and applying the different + * ways you can specify which fields you want to include + * or exclude. + * + * Example 1: Include fields 'a', 'b', and 'c' via an Array + * query.fields('a', 'b', 'c'); + * query.fields(['a', 'b', 'c']); + * + * Example 2: Include fields via 'only' shortcut + * query.only('a b c'); + * + * Example 3: Exclude fields via 'exclude' shortcut + * query.exclude('a b c'); + * + * Example 4: Include fields via MongoDB's native format + * query.fields({a: 1, b: 1, c: 1}) + * + * Example 5: Exclude fields via MongoDB's native format + * query.fields({a: 0, b: 0, c: 0}); + * + * @param {Object|Array} the formatted collection of fields to + * include and/or exclude + * @api private + */ + +Query.prototype._applyFields = function (fields) { + var $fields + , pathList; + + if (Array.isArray(fields)) { + $fields = fields.reduce(function ($fields, field) { + $fields[field] = 1; + return $fields; + }, {}); + } else if (pathList = fields.only || fields.exclude) { + $fields = + this._parseOnlyExcludeFields(pathList) + .reduce(function ($fields, field) { + $fields[field] = fields.only ? 1: 0; + return $fields; + }, {}); + } else if ('Object' === fields.constructor.name) { + $fields = fields; + } else { + throw new Error("fields is invalid"); + } + + var myFields = this._fields || (this._fields = {}); + for (var k in $fields) myFields[k] = $fields[k]; +}; + +/** + * Sets the sort + * + * Examples: + * query.sort('test', 1) + * query.sort('field', -1) + * query.sort('field', -1, 'test', 1) + * + * @api public + */ + +Query.prototype.sort = function () { + var sort = this.options.sort || (this.options.sort = []); + + inGroupsOf(2, arguments, function (field, value) { + sort.push([field, value]); + }); + + return this; +}; + +Query.prototype.asc = function () { + var sort = this.options.sort || (this.options.sort = []); + for (var i = 0, l = arguments.length; i < l; i++) { + sort.push([arguments[i], 1]); + } + return this; +}; + +Query.prototype.desc = function () { + var sort = this.options.sort || (this.options.sort = []); + for (var i = 0, l = arguments.length; i < l; i++) { + sort.push([arguments[i], -1]); + } + return this; +}; + +;['limit', 'skip', 'maxscan', 'snapshot', 'batchSize'].forEach( function (method) { + Query.prototype[method] = function (v) { + this.options[method] = v; + return this; + }; +}); + +/** + * Query hints. + * + * Examples: + * new Query().hint({ indexA: 1, indexB: -1}) + * new Query().hint("indexA", 1, "indexB", -1) + * + * @param {Object|String} v + * @param {Int} [multi] + * @return {Query} + * @api public + */ + +Query.prototype.hint = function (v, multi) { + var hint = this.options.hint || (this.options.hint = {}) + , k + + if (multi) { + inGroupsOf(2, arguments, function (field, val) { + hint[field] = val; + }); + } else if ('Object' === v.constructor.name) { + // must keep object keys in order so don't use Object.keys() + for (k in v) { + hint[k] = v[k]; + } + } + + return this; +}; + +/** + * Sets slaveOk option + * + * new Query().slaveOk() <== true + * new Query().slaveOk(true) + * new Query().slaveOk(false) + * + * @param {Boolean} v (defaults to true) + */ + +Query.prototype.slaveOk = function (v) { + this.options.slaveOk = arguments.length ? !!v : true; + return this; +}; + +Query.prototype.execFind = function (callback) { + var model = this.model + , options = this._optionsForExec(model) + , self = this + + var promise = new Promise(callback); + + try { + this.cast(model); + } catch (err) { + return promise.error(err); + } + + var fields = utils.clone(options.fields = this._fields); + var castQuery = this._conditions; + + model.collection.find(castQuery, options, function (err, cursor) { + if (err) return promise.error(err); + cursor.toArray(tick(cb)); + }); + + function cb (err, docs) { + if (err) return promise.error(err); + + var arr = [] + , count = docs.length; + + if (!count) return promise.complete([]); + + for (var i = 0, l = docs.length; i < l; i++) { + arr[i] = new model(undefined, fields); + + // skip _id for pre-init hooks + delete arr[i]._doc._id; + + arr[i].init(docs[i], self, function (err) { + if (err) return promise.error(err); + --count || promise.complete(arr); + }); + } + } + + return this; +}; + +/** + * Streaming cursors. + * + * The `callback` is called repeatedly for each document + * found in the collection as it's streamed. If an error + * occurs streaming stops. + * + * Example: + * query.each(function (err, user) { + * if (err) return res.end("aww, received an error. all done."); + * if (user) { + * res.write(user.name + '\n') + * } else { + * res.end("reached end of cursor. all done."); + * } + * }); + * + * A third parameter may also be used in the callback which + * allows you to iterate the cursor manually. + * + * Example: + * query.each(function (err, user, next) { + * if (err) return res.end("aww, received an error. all done."); + * if (user) { + * res.write(user.name + '\n') + * doSomethingAsync(next); + * } else { + * res.end("reached end of cursor. all done."); + * } + * }); + * + * @param {Function} callback + * @return {Query} + * @api public + */ + +Query.prototype.each = function (callback) { + var model = this.model + , options = this._optionsForExec(model) + , manual = 3 == callback.length + , self = this + + try { + this.cast(model); + } catch (err) { + return callback(err); + } + + var fields = utils.clone(options.fields = this._fields); + + function complete (err, val) { + if (complete.ran) return; + complete.ran = true; + callback(err, val); + } + + model.collection.find(this._conditions, options, function (err, cursor) { + if (err) return complete(err); + + var ticks = 0; + next(); + + function next () { + // nextTick is necessary to avoid stack overflows when + // dealing with large result sets. yield occasionally. + if (!(++ticks % 20)) { + process.nextTick(function () { + cursor.nextObject(onNextObject); + }); + } else { + cursor.nextObject(onNextObject); + } + } + + function onNextObject (err, doc) { + if (err) return complete(err); + + // when doc is null we hit the end of the cursor + if (!doc) return complete(null, null); + + var instance = new model(undefined, fields); + + // skip _id for pre-init hooks + delete instance._doc._id; + + instance.init(doc, self, function (err) { + if (err) return complete(err); + + if (manual) { + callback(null, instance, next); + } else { + callback(null, instance); + next(); + } + }); + } + + }); + + return this; +} + +/** + * Casts the query, sends the findOne command to mongodb. + * Upon receiving the document, we initialize a mongoose + * document based on the returned document from mongodb, + * and then we invoke a callback on our mongoose document. + * + * @param {Function} callback function (err, found) + * @api public + */ + +Query.prototype.findOne = function (callback) { + this.op = 'findOne'; + + if (!callback) return this; + + var model = this.model; + var promise = new Promise(callback); + + try { + this.cast(model); + } catch (err) { + promise.error(err); + return this; + } + + var self = this + , castQuery = this._conditions + , options = this._optionsForExec(model) + + var fields = utils.clone(options.fields = this._fields); + + model.collection.findOne(castQuery, options, tick(function (err, doc) { + if (err) return promise.error(err); + if (!doc) return promise.complete(null); + + var casted = new model(undefined, fields); + + // skip _id for pre-init hooks + delete casted._doc._id; + + casted.init(doc, self, function (err) { + if (err) return promise.error(err); + promise.complete(casted); + }); + })); + + return this; +}; + +/** + * Casts this._conditions and sends a count + * command to mongodb. Invokes a callback upon + * receiving results + * + * @param {Function} callback fn(err, cardinality) + * @api public + */ + +Query.prototype.count = function (callback) { + this.op = 'count'; + var model = this.model; + + try { + this.cast(model); + } catch (err) { + return callback(err); + } + + var castQuery = this._conditions; + model.collection.count(castQuery, tick(callback)); + + return this; +}; + +/** + * Casts this._conditions and sends a distinct + * command to mongodb. Invokes a callback upon + * receiving results + * + * @param {Function} callback fn(err, cardinality) + * @api public + */ + +Query.prototype.distinct = function (field, callback) { + this.op = 'distinct'; + var model = this.model; + + try { + this.cast(model); + } catch (err) { + return callback(err); + } + + var castQuery = this._conditions; + model.collection.distinct(field, castQuery, tick(callback)); + + return this; +}; + +/** + * These operators require casting docs + * to real Documents for Update operations. + * @private + */ + +var castOps = { + $push: 1 + , $pushAll: 1 + , $addToSet: 1 + , $set: 1 +}; + +/** + * These operators should be cast to numbers instead + * of their path schema type. + * @private + */ + +var numberOps = { + $pop: 1 + , $unset: 1 + , $inc: 1 +} + +/** + * Casts the `doc` according to the model Schema and + * sends an update command to MongoDB. + * + * _All paths passed that are not $atomic operations + * will become $set ops so we retain backwards compatibility._ + * + * Example: + * `Model.update({..}, { title: 'remove words' }, ...)` + * + * becomes + * + * `Model.update({..}, { $set: { title: 'remove words' }}, ...)` + * + * + * _Passing an empty object `{}` as the doc will result + * in a no-op. The update operation will be ignored and the + * callback executed without sending the command to MongoDB so as + * to prevent accidently overwritting the collection._ + * + * @param {Object} doc - the update + * @param {Function} callback - fn(err) + * @api public + */ + +Query.prototype.update = function update (doc, callback) { + this.op = 'update'; + this._updateArg = doc; + + var model = this.model + , options = this._optionsForExec(model) + , useSet = model.options['use$SetOnSave'] + , castQuery + , castDoc + + try { + this.cast(model); + castQuery = this._conditions; + } catch (err) { + return callback(err); + } + + try { + castDoc = this._castUpdate(doc); + } catch (err) { + return callback(err); + } + + if (castDoc) { + model.collection.update(castQuery, castDoc, options, tick(callback)); + } else { + process.nextTick(function () { + callback(null); + }); + } + + return this; +}; + +/** + * Casts obj for an update command. + * + * @param {Object} obj + * @return {Object} obj after casting its values + * @api private + */ + +Query.prototype._castUpdate = function _castUpdate (obj) { + var ops = Object.keys(obj) + , i = ops.length + , ret = {} + , hasKeys + , val + + while (i--) { + var op = ops[i]; + hasKeys = true; + if ('$' !== op[0]) { + // fix up $set sugar + if (!ret.$set) { + if (obj.$set) { + ret.$set = obj.$set; + } else { + ret.$set = {}; + } + } + ret.$set[op] = obj[op]; + ops.splice(i, 1); + if (!~ops.indexOf('$set')) ops.push('$set'); + } else if ('$set' === op) { + if (!ret.$set) { + ret[op] = obj[op]; + } + } else { + ret[op] = obj[op]; + } + } + + // cast each value + i = ops.length; + + while (i--) { + op = ops[i]; + val = ret[op]; + if ('Object' === val.constructor.name) { + this._walkUpdatePath(val, op); + } else { + var msg = 'Invalid atomic update value for ' + op + '. ' + + 'Expected an object, received ' + typeof val; + throw new Error(msg); + } + } + + return hasKeys && ret; +} + +/** + * Walk each path of obj and cast its values + * according to its schema. + * + * @param {Object} obj - part of a query + * @param {String} op - the atomic operator ($pull, $set, etc) + * @param {String} pref - path prefix (internal only) + * @private + */ + +Query.prototype._walkUpdatePath = function _walkUpdatePath (obj, op, pref) { + var prefix = pref ? pref + '.' : '' + , keys = Object.keys(obj) + , i = keys.length + , schema + , key + , val + + while (i--) { + key = keys[i]; + val = obj[key]; + + if (val && 'Object' === val.constructor.name) { + // watch for embedded doc schemas + schema = this._getSchema(prefix + key); + if (schema && schema.caster && op in castOps) { + // embedded doc schema + if ('$each' in val) { + obj[key] = { + $each: this._castUpdateVal(schema, val.$each, op) + } + } else { + obj[key] = this._castUpdateVal(schema, val, op); + } + } else { + this._walkUpdatePath(val, op, prefix + key); + } + } else { + schema = '$each' === key + ? this._getSchema(pref) + : this._getSchema(prefix + key); + + obj[key] = this._castUpdateVal(schema, val, op); + } + } +} + +/** + * Casts `val` according to `schema` and atomic `op`. + * + * @param {Schema} schema + * @param {Object} val + * @param {String} op - the atomic operator ($pull, $set, etc) + * @private + */ + +Query.prototype._castUpdateVal = function _castUpdateVal (schema, val, op) { + if (!schema) { + // non-existing schema path + return op in numberOps + ? Number(val) + : val + } + + if (schema.caster && op in castOps && + ('Object' === val.constructor.name || Array.isArray(val))) { + // Cast values for ops that add data to MongoDB. + // Ensures embedded documents get ObjectIds etc. + var tmp = schema.cast(val); + + if (Array.isArray(val)) { + val = tmp; + } else { + val = tmp[0]; + } + } + + return op in numberOps + ? Number(val) + : schema.castForQuery(val) +} + +/** + * Finds the schema for `path`. This is different than + * calling `schema.path` as it also resolves paths with + * positional selectors (something.$.another.$.path). + * + * @param {String} path + * @private + */ + +Query.prototype._getSchema = function _getSchema (path) { + var schema = this.model.schema + , pathschema = schema.path(path); + + if (pathschema) + return pathschema; + + // look for arrays + return (function search (parts, schema) { + var p = parts.length + 1 + , foundschema + , trypath + + while (p--) { + trypath = parts.slice(0, p).join('.'); + foundschema = schema.path(trypath); + if (foundschema) { + if (foundschema.caster) { + // Now that we found the array, we need to check if there + // are remaining document paths to look up for casting. + // Also we need to handle array.$.path since schema.path + // doesn't work for that. + if (p !== parts.length) { + if ('$' === parts[p]) { + // comments.$.comments.$.title + return search(parts.slice(p+1), foundschema.schema); + } else { + // this is the last path of the selector + return search(parts.slice(p), foundschema.schema); + } + } + } + return foundschema; + } + } + })(path.split('.'), schema) +} + +/** + * Casts the query, sends the remove command to + * mongodb where the query contents, and then + * invokes a callback upon receiving the command + * result. + * + * @param {Function} callback + * @api public + */ + +Query.prototype.remove = function (callback) { + this.op = 'remove'; + + var model = this.model + , options = this._optionsForExec(model); + + try { + this.cast(model); + } catch (err) { + return callback(err); + } + + var castQuery = this._conditions; + model.collection.remove(castQuery, options, tick(callback)); + return this; +}; + +/** + * Sets population options. + * @api public + */ + +Query.prototype.populate = function (path, fields, conditions, options) { + // The order of fields/conditions args is opposite Model.find but + // necessary to keep backward compatibility (fields could be + // an array, string, or object literal). + this.options.populate[path] = + new PopulateOptions(fields, conditions, options); + + return this; +}; + +/** + * Populate options constructor + * @private + */ + +function PopulateOptions (fields, conditions, options) { + this.conditions = conditions; + this.fields = fields; + this.options = options; +} + +// make it compatible with utils.clone +PopulateOptions.prototype.constructor = Object; + +/** + * Returns a stream interface + * + * Example: + * Thing.find({ name: /^hello/ }).stream().pipe(res) + * + * @api public + */ + +Query.prototype.stream = function stream () { + return new QueryStream(this); +} + +/** + * @private + * @TODO + */ + +Query.prototype.explain = function () { + throw new Error("Unimplemented"); +}; + +// TODO Add being able to skip casting -- e.g., this would be nice for scenarios like +// if you're migrating to usernames from user id numbers: +// query.where('user_id').in([4444, 'brian']); +// TODO "immortal" cursors - (only work on capped collections) +// TODO geoNear command + +/** + * Exports. + */ + +module.exports = Query; +module.exports.QueryStream = QueryStream; diff --git a/node_modules/mongoose/lib/querystream.js b/node_modules/mongoose/lib/querystream.js new file mode 100644 index 0000000..4093e60 --- /dev/null +++ b/node_modules/mongoose/lib/querystream.js @@ -0,0 +1,179 @@ + +/** + * Module dependencies. + */ + +var Stream = require('stream').Stream +var utils = require('./utils') + +/** + * QueryStream + * + * Returns a stream interface for the `query`. + * + * @param {Query} query + * @return {Stream} + */ + +function QueryStream (query) { + Stream.call(this); + + this.query = query; + this.readable = true; + this.paused = false; + this._cursor = null; + this._destroyed = null; + this._fields = null; + this._ticks = 0; + + // give time to hook up events + var self = this; + process.nextTick(function () { + self._init(); + }); +} + +/** + * Inherit from Stream + * @private + */ + +QueryStream.prototype.__proto__ = Stream.prototype; + +/** + * Flag stating whether or not this stream is readable. + */ + +QueryStream.prototype.readable; + +/** + * Flag stating whether or not this stream is paused. + */ + +QueryStream.prototype.paused; + +/** + * Initialize the query. + * @private + */ + +QueryStream.prototype._init = function () { + if (this._destroyed) return; + + var query = this.query + , model = query.model + , options = query._optionsForExec(model) + , self = this + + try { + query.cast(model); + } catch (err) { + return self.destroy(err); + } + + self._fields = utils.clone(options.fields = query._fields); + + model.collection.find(query._conditions, options, function (err, cursor) { + if (err) return self.destroy(err); + self._cursor = cursor; + self._next(); + }); +} + +/** + * Pull the next document from the cursor. + * @private + */ + +QueryStream.prototype._next = function () { + if (this.paused || this._destroyed) return; + + var self = this; + + // nextTick is necessary to avoid stack overflows when + // dealing with large result sets. yield occasionally. + if (!(++this._ticks % 20)) { + process.nextTick(function () { + self._cursor.nextObject(function (err, doc) { + self._onNextObject(err, doc); + }); + }); + } else { + self._cursor.nextObject(function (err, doc) { + self._onNextObject(err, doc); + }); + } +} + +/** + * Handle each document as its returned from the cursor + * transforming the raw `doc` from -native into a model + * instance. + * + * @private + */ + +QueryStream.prototype._onNextObject = function (err, doc) { + if (err) return this.destroy(err); + + // when doc is null we hit the end of the cursor + if (!doc) { + return this.destroy(); + } + + var instance = new this.query.model(undefined, this._fields); + + // skip _id for pre-init hooks + delete instance._doc._id; + + var self = this; + instance.init(doc, this.query, function (err) { + if (err) return self.destroy(err); + self.emit('data', instance); + self._next(); + }); +} + +/** + * Pauses this stream. + */ + +QueryStream.prototype.pause = function () { + this.paused = true; +} + +/** + * Resumes this stream. + */ + +QueryStream.prototype.resume = function () { + this.paused = false; + this._next(); +} + +/** + * Destroys the stream, closing the underlying + * cursor. No more events will be emitted. + */ + +QueryStream.prototype.destroy = function (err) { + if (this._destroyed) return; + this._destroyed = true; + this.readable = false; + + if (this._cursor) { + this._cursor.close(); + } + + if (err) { + this.emit('error', err); + } + + this.emit('close'); +} + +// TODO - maybe implement the -native raw option to pass binary? +//QueryStream.prototype.setEncoding = function () { +//} + +module.exports = exports = QueryStream; diff --git a/node_modules/mongoose/lib/schema.js b/node_modules/mongoose/lib/schema.js new file mode 100644 index 0000000..5496d21 --- /dev/null +++ b/node_modules/mongoose/lib/schema.js @@ -0,0 +1,551 @@ + +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , VirtualType = require('./virtualtype') + , utils = require('./utils') + , NamedScope + , Query + , Types + +/** + * Schema constructor. + * + * @param {Object} definition + * @api public + */ + +function Schema (obj, options) { + this.paths = {}; + this.virtuals = {}; + this.inherits = {}; + this.callQueue = []; + this._indexes = []; + this.methods = {}; + this.statics = {}; + this.tree = {}; + + // set options + this.options = utils.options({ + safe: true + , 'use$SetOnSave': true + , strict: false + }, options); + + // build paths + if (obj) + this.add(obj); + + if (!this.paths['_id'] && !this.options.noId) { + this.add({ _id: {type: ObjectId, auto: true} }); + } + + if (!this.paths['id'] && !this.options.noVirtualId) { + this.virtual('id').get(function () { + if (this.__id) { + return this.__id; + } + + return this.__id = null == this._id + ? null + : this._id.toString(); + }); + } + + delete this.options.noVirtualId; +}; + +/** + * Inherit from EventEmitter. + */ + +Schema.prototype.__proto__ = EventEmitter.prototype; + +/** + * Schema by paths + * + * Example (embedded doc): + * { + * 'test' : SchemaType, + * , 'test.test' : SchemaType, + * , 'first_name' : SchemaType + * } + * + * @api private + */ + +Schema.prototype.paths; + +/** + * Schema as a tree + * + * Example: + * { + * '_id' : ObjectId + * , 'nested' : { + * 'key': String + * } + * } + * + * @api private + */ + +Schema.prototype.tree; + +/** + * Sets the keys + * + * @param {Object} keys + * @param {String} prefix + * @api public + */ + +Schema.prototype.add = function add (obj, prefix) { + prefix = prefix || ''; + for (var i in obj) { + // make sure set of keys are in `tree` + if (!prefix && !this.tree[i]) + this.tree[i] = obj[i]; + + if (null == obj[i]) { + throw new TypeError('Invalid value for schema path `'+ prefix + i +'`'); + } + + if (obj[i].constructor.name == 'Object' && (!obj[i].type || obj[i].type.type)) { + if (Object.keys(obj[i]).length) + this.add(obj[i], prefix + i + '.'); + else + this.path(prefix + i, obj[i]); // mixed type + } else + this.path(prefix + i, obj[i]); + } +}; + +/** + * Sets a path (if arity 2) + * Gets a path (if arity 1) + * + * @param {String} path + * @param {Object} constructor + * @api public + */ + +Schema.prototype.path = function (path, obj) { + if (obj == undefined) { + if (this.paths[path]) return this.paths[path]; + + // Sometimes path will come in as + // pathNameA.4.pathNameB where 4 means the index + // of an embedded document in an embedded array. + // In this case, we need to jump to the Array's + // schema and call path() from there to resolve to + // the correct path type + + var last + , self = this + , subpaths = path.split(/\.(\d+)\.?/) + .filter(Boolean) // removes empty strings + + if (subpaths.length > 1) { + last = subpaths.length - 1; + return subpaths.reduce(function (val, subpath, i) { + if (val && !val.schema) { + if (i === last && !/\D/.test(subpath) && val instanceof Types.Array) { + return val.caster; // StringSchema, NumberSchema, etc + } else { + return val; + } + } + + if (!/\D/.test(subpath)) { // 'path.0.subpath' on path 0 + return val; + } + + return val ? val.schema.path(subpath) + : self.path(subpath); + }, null); + } + + return this.paths[subpaths[0]]; + } + + this.paths[path] = Schema.interpretAsType(path, obj); + return this; +}; + +/** + * Converts -- e.g., Number, [SomeSchema], + * { type: String, enum: ['m', 'f'] } -- into + * the appropriate Mongoose Type, which we use + * later for casting, validation, etc. + * @param {String} path + * @param {Object} constructor + */ + +Schema.interpretAsType = function (path, obj) { + if (obj.constructor.name != 'Object') + obj = { type: obj }; + + // Get the type making sure to allow keys named "type" + // and default to mixed if not specified. + // { type: { type: String, default: 'freshcut' } } + var type = obj.type && !obj.type.type + ? obj.type + : {}; + + if (type.constructor.name == 'Object') { + return new Types.Mixed(path, obj); + } + + if (Array.isArray(type) || type == Array) { + // if it was specified through { type } look for `cast` + var cast = type == Array + ? obj.cast + : type[0]; + + if (cast instanceof Schema) { + return new Types.DocumentArray(path, cast, obj); + } + + return new Types.Array(path, cast || Types.Mixed, obj); + } + return new Types[type.name](path, obj); +}; + +/** + * Iterates through the schema's paths, passing the path string and type object + * to the callback. + * + * @param {Function} callback function - fn(pathstring, type) + * @return {Schema} this for chaining + * @api public + */ + +Schema.prototype.eachPath = function (fn) { + var keys = Object.keys(this.paths) + , len = keys.length; + + for (var i = 0; i < len; ++i) { + fn(keys[i], this.paths[keys[i]]); + } + + return this; +}; + +/** + * Returns an Array of path strings that are required. + * @api public + */ + +Object.defineProperty(Schema.prototype, 'requiredPaths', { + get: function () { + var paths = this.paths + , pathnames = Object.keys(paths) + , i = pathnames.length + , pathname, path + , requiredPaths = []; + while (i--) { + pathname = pathnames[i]; + path = paths[pathname]; + if (path.isRequired) requiredPaths.push(pathname); + } + return requiredPaths; + } +}); + +/** + * Given a path, returns whether it is a real, virtual, or + * ad-hoc/undefined path + * + * @param {String} path + * @return {String} + * @api public + */ +Schema.prototype.pathType = function (path) { + if (path in this.paths) return 'real'; + if (path in this.virtuals) return 'virtual'; + return 'adhocOrUndefined'; +}; + +/** + * Adds a method call to the queue + * + * @param {String} method name + * @param {Array} arguments + * @api private + */ + +Schema.prototype.queue = function(name, args){ + this.callQueue.push([name, args]); + return this; +}; + +/** + * Defines a pre for the document + * + * @param {String} method + * @param {Function} callback + * @api public + */ + +Schema.prototype.pre = function(){ + return this.queue('pre', arguments); +}; + +/** + * Defines a post for the document + * + * @param {String} method + * @param {Function} callback + * @api public + */ + +Schema.prototype.post = function(method, fn){ + return this.queue('on', arguments); +}; + +/** + * Registers a plugin for this schema + * + * @param {Function} plugin callback + * @api public + */ + +Schema.prototype.plugin = function (fn, opts) { + fn(this, opts); + return this; +}; + +/** + * Adds a method + * + * @param {String} method name + * @param {Function} handler + * @api public + */ + +Schema.prototype.method = function (name, fn) { + if ('string' != typeof name) + for (var i in name) + this.methods[i] = name[i]; + else + this.methods[name] = fn; + return this; +}; + +/** + * Defines a static method + * + * @param {String} name + * @param {Function} handler + * @api public + */ + +Schema.prototype.static = function(name, fn) { + if ('string' != typeof name) + for (var i in name) + this.statics[i] = name[i]; + else + this.statics[name] = fn; + return this; +}; + +/** + * Defines an index (most likely compound) + * Example: + * schema.index({ first: 1, last: -1 }) + * + * @param {Object} field + * @param {Object} optional options object + * @api public + */ + +Schema.prototype.index = function (fields, options) { + this._indexes.push([fields, options || {}]); + return this; +}; + +/** + * Sets/gets an option + * + * @param {String} key + * @param {Object} optional value + * @api public + */ + +Schema.prototype.set = function (key, value) { + if (arguments.length == 1) + return this.options[key]; + this.options[key] = value; + return this; +}; + +/** + * Compiles indexes from fields and schema-level indexes + * + * @api public + */ + +Schema.prototype.__defineGetter__('indexes', function () { + var indexes = [] + , seenSchemas = []; + + collectIndexes(this); + + return indexes; + + function collectIndexes (schema, prefix) { + if (~seenSchemas.indexOf(schema)) return; + seenSchemas.push(schema); + + var index; + var paths = schema.paths; + prefix = prefix || ''; + + for (var i in paths) { + if (paths[i]) { + if (paths[i] instanceof Types.DocumentArray) { + collectIndexes(paths[i].schema, i + '.'); + } else { + index = paths[i]._index; + + if (index !== false && index !== null){ + var field = {}; + field[prefix + i] = '2d' === index ? index : 1; + indexes.push([field, 'Object' === index.constructor.name ? index : {} ]); + } + } + } + } + + if (prefix) { + fixSubIndexPaths(schema, prefix); + } else { + indexes = indexes.concat(schema._indexes); + } + } + + /** + * Checks for indexes added to subdocs using Schema.index(). + * These indexes need their paths prefixed properly. + * + * schema._indexes = [ [indexObj, options], [indexObj, options] ..] + */ + + function fixSubIndexPaths (schema, prefix) { + var subindexes = schema._indexes + , len = subindexes.length + , indexObj + , newindex + , klen + , keys + , key + , i = 0 + , j + + for (i = 0; i < len; ++i) { + indexObj = subindexes[i][0]; + keys = Object.keys(indexObj); + klen = keys.length; + newindex = {}; + + // use forward iteration, order matters + for (j = 0; j < klen; ++j) { + key = keys[j]; + newindex[prefix + key] = indexObj[key]; + } + + indexes.push([newindex, subindexes[i][1]]); + } + } + +}); + +/** + * Retrieves or creates the virtual type with the given name. + * + * @param {String} name + * @return {VirtualType} + */ + +Schema.prototype.virtual = function (name, options) { + var virtuals = this.virtuals || (this.virtuals = {}); + var parts = name.split('.'); + return virtuals[name] = parts.reduce(function (mem, part, i) { + mem[part] || (mem[part] = (i === parts.length-1) + ? new VirtualType(options) + : {}); + return mem[part]; + }, this.tree); +}; + +/** + * Fetches the virtual type with the given name. + * Should be distinct from virtual because virtual auto-defines a new VirtualType + * if the path doesn't exist. + * + * @param {String} name + * @return {VirtualType} + */ + +Schema.prototype.virtualpath = function (name) { + return this.virtuals[name]; +}; + +Schema.prototype.namedScope = function (name, fn) { + var namedScopes = this.namedScopes || (this.namedScopes = new NamedScope) + , newScope = Object.create(namedScopes) + , allScopes = namedScopes.scopesByName || (namedScopes.scopesByName = {}); + allScopes[name] = newScope; + newScope.name = name; + newScope.block = fn; + newScope.query = new Query(); + newScope.decorate(namedScopes, { + block0: function (block) { + return function () { + block.call(this.query); + return this; + }; + }, + blockN: function (block) { + return function () { + block.apply(this.query, arguments); + return this; + }; + }, + basic: function (query) { + return function () { + this.query.find(query); + return this; + }; + } + }); + return newScope; +}; + +/** + * ObjectId schema identifier. Not an actual ObjectId, only used for Schemas. + * + * @api public + */ + +function ObjectId () { + throw new Error('This is an abstract interface. Its only purpose is to mark ' + + 'fields as ObjectId in the schema creation.'); +} + +/** + * Module exports. + */ + +module.exports = exports = Schema; + +// require down here because of reference issues +exports.Types = Types = require('./schema/index'); +NamedScope = require('./namedscope') +Query = require('./query'); + +exports.ObjectId = ObjectId; + diff --git a/node_modules/mongoose/lib/schema/array.js b/node_modules/mongoose/lib/schema/array.js new file mode 100644 index 0000000..eee4f8d --- /dev/null +++ b/node_modules/mongoose/lib/schema/array.js @@ -0,0 +1,220 @@ +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError + , NumberSchema = require('./number') + , Types = { + Boolean: require('./boolean') + , Date: require('./date') + , Number: ArrayNumberSchema + , String: require('./string') + , ObjectId: require('./objectid') + , Buffer: require('./buffer') + } + , MongooseArray = require('../types').Array + , Mixed = require('./mixed') + , Query = require('../query') + , isMongooseObject = require('../utils').isMongooseObject + +/** + * Array SchemaType constructor + * + * @param {String} key + * @param {SchemaType} cast + * @api private + */ + +function SchemaArray (key, cast, options) { + SchemaType.call(this, key, options); + + if (cast) { + var castOptions = {}; + + if ('Object' === cast.constructor.name) { + if (cast.type) { + // support { type: Woot } + castOptions = cast; + cast = cast.type; + delete castOptions.type; + } else { + cast = Mixed; + } + } + + var caster = cast.name in Types ? Types[cast.name] : cast; + this.casterConstructor = caster; + this.caster = new caster(null, castOptions); + } + + var self = this + , defaultArr + , fn; + + if (this.defaultValue) { + defaultArr = this.defaultValue; + fn = 'function' == typeof defaultArr; + } + + this.default(function(){ + var arr = fn ? defaultArr() : defaultArr || []; + return new MongooseArray(arr, self.path, this); + }); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaArray.prototype.__proto__ = SchemaType.prototype; + +/** + * Check required + * + * @api private + */ + +SchemaArray.prototype.checkRequired = function (value) { + return !!(value && value.length); +}; + +/** + * Overrides the getters application for the population special-case + * TODO: implement this in SchemaObjectIdArray + * + * @param {Object} value + * @param {Object} scope + * @api private + */ + +SchemaArray.prototype.applyGetters = function (value, scope) { + if (this.caster.options && this.caster.options.ref) { + // means the object id was populated + return value; + } + + return SchemaType.prototype.applyGetters.call(this, value, scope); +}; + +/** + * Casts contents + * + * @param {Object} value + * @param {Document} document that triggers the casting + * @param {Boolean} whether this is an initialization cast + * @api private + */ + +SchemaArray.prototype.cast = function (value, doc, init) { + if (Array.isArray(value)) { + if (!(value instanceof MongooseArray)) { + value = new MongooseArray(value, this.path, doc); + } + + if (this.caster) { + try { + for (var i = 0, l = value.length; i < l; i++) { + value[i] = this.caster.cast(value[i], doc, init); + } + } catch (e) { + // rethrow + throw new CastError(e.type, value); + } + } + + return value; + } else { + return this.cast([value], doc, init); + } +}; + +SchemaArray.prototype.castForQuery = function ($conditional, val) { + var handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) + throw new Error("Can't use " + $conditional + " with Array."); + val = handler.call(this, val); + } else { + val = $conditional; + var proto = this.casterConstructor.prototype; + var method = proto.castForQuery || proto.cast; + if (Array.isArray(val)) { + val = val.map(function (v) { + if (method) v = method.call(proto, v); + return isMongooseObject(v) + ? v.toObject() + : v; + }); + } else if (method) { + val = method.call(proto, val); + } + } + return val && isMongooseObject(val) + ? val.toObject() + : val; +}; + +SchemaArray.prototype.$conditionalHandlers = { + '$all': function handle$all (val) { + if (!Array.isArray(val)) { + val = [val]; + } + return this.castForQuery(val); + } + , '$elemMatch': function (val) { + var query = new Query(val); + query.cast(this.casterConstructor) + return query._conditions; + } + , '$size': function (val) { + return ArrayNumberSchema.prototype.cast.call(this, val); + } + , '$ne': SchemaArray.prototype.castForQuery + , '$in': SchemaArray.prototype.castForQuery + , '$nin': SchemaArray.prototype.castForQuery + , '$regex': SchemaArray.prototype.castForQuery + , '$near': SchemaArray.prototype.castForQuery + , '$nearSphere': SchemaArray.prototype.castForQuery + , '$within': function(val) { + var query = new Query(val); + query.cast(this.casterConstructor) + return query._conditions; + } + , '$maxDistance': function (val) { + return ArrayNumberSchema.prototype.cast.call(this, val); + } +}; + +/** + * Number casting for arrays (equivalent, but without MongoseNumber) + * + * @see GH-176 + * @param {Object} value + * @api private + */ + +// subclass number schema to override casting +// to disallow non-numbers being saved +function ArrayNumberSchema (key, options) { + NumberSchema.call(this, key, options); +} + +ArrayNumberSchema.prototype.__proto__ = NumberSchema.prototype; + +ArrayNumberSchema.prototype.cast = function (value) { + if (!isNaN(value)) { + if (value instanceof Number || typeof value == 'number' || + (value.toString && value.toString() == Number(value))) + return Number(value); + } + + throw new CastError('number', value); +}; + +/** + * Module exports. + */ + +module.exports = SchemaArray; diff --git a/node_modules/mongoose/lib/schema/boolean.js b/node_modules/mongoose/lib/schema/boolean.js new file mode 100644 index 0000000..574fdd8 --- /dev/null +++ b/node_modules/mongoose/lib/schema/boolean.js @@ -0,0 +1,59 @@ + +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype'); + +/** + * Boolean SchemaType constructor. + * + * @param {String} path + * @param {Object} options + * @api private + */ + +function SchemaBoolean (path, options) { + SchemaType.call(this, path, options); +}; + +/** + * Inherits from SchemaType. + */ +SchemaBoolean.prototype.__proto__ = SchemaType.prototype; + +/** + * Required validator for date + * + * @api private + */ + +SchemaBoolean.prototype.checkRequired = function (value) { + return value === true || value === false; +}; + +/** + * Casts to boolean + * + * @param {Object} value to cast + * @api private + */ + +SchemaBoolean.prototype.cast = function (value) { + if (value === null) return value; + if (value === '0') return false; + return !!value; +}; + +SchemaBoolean.prototype.castForQuery = function ($conditional, val) { + if (arguments.length === 1) { + val = $conditional; + } + return this.cast(val); +}; + +/** + * Module exports. + */ + +module.exports = SchemaBoolean; diff --git a/node_modules/mongoose/lib/schema/buffer.js b/node_modules/mongoose/lib/schema/buffer.js new file mode 100644 index 0000000..bf696c2 --- /dev/null +++ b/node_modules/mongoose/lib/schema/buffer.js @@ -0,0 +1,104 @@ +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError + , BufferNumberSchema = function () {} + , MongooseBuffer = require('../types').Buffer + , Binary = MongooseBuffer.Binary + , Query = require('../query'); + +/** + * Buffer SchemaType constructor + * + * @param {String} key + * @param {SchemaType} cast + * @api private + */ + +function SchemaBuffer (key, options) { + SchemaType.call(this, key, options); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaBuffer.prototype.__proto__ = SchemaType.prototype; + +/** + * Check required + * + * @api private + */ + +SchemaBuffer.prototype.checkRequired = function (value) { + return !!(value && value.length); +}; + +/** + * Casts contents + * + * @param {Object} value + * @param {Document} document that triggers the casting + * @api private + */ + +SchemaBuffer.prototype.cast = function (value, doc) { + if (Buffer.isBuffer(value)) { + if (!(value instanceof MongooseBuffer)) { + value = new MongooseBuffer(value, [this.path, doc]); + } + + return value; + } else if (value instanceof Binary) { + return new MongooseBuffer(value.value(true), [this.path, doc]); + } + + if ('string' === typeof value || Array.isArray(value)) { + return new MongooseBuffer(value, [this.path, doc]); + } + + throw new CastError('buffer', value); +}; + +function handleSingle (val) { + return this.castForQuery(val); +} + +function handleArray (val) { + var self = this; + return val.map( function (m) { + return self.castForQuery(m); + }); +} + +SchemaBuffer.prototype.$conditionalHandlers = { + '$ne' : handleSingle + , '$in' : handleArray + , '$nin': handleArray + , '$gt' : handleSingle + , '$lt' : handleSingle + , '$gte': handleSingle + , '$lte': handleSingle +}; + +SchemaBuffer.prototype.castForQuery = function ($conditional, val) { + var handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) + throw new Error("Can't use " + $conditional + " with Buffer."); + return handler.call(this, val); + } else { + val = $conditional; + return this.cast(val).toObject(); + } +}; + +/** + * Module exports. + */ + +module.exports = SchemaBuffer; diff --git a/node_modules/mongoose/lib/schema/date.js b/node_modules/mongoose/lib/schema/date.js new file mode 100644 index 0000000..b5c06d2 --- /dev/null +++ b/node_modules/mongoose/lib/schema/date.js @@ -0,0 +1,115 @@ + +/** + * Module requirements. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError; + +/** + * Date SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @api private + */ + +function SchemaDate (key, options) { + SchemaType.call(this, key, options); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaDate.prototype.__proto__ = SchemaType.prototype; + +/** + * Required validator for date + * + * @api private + */ + +SchemaDate.prototype.checkRequired = function (value) { + return value instanceof Date; +}; + +/** + * Casts to date + * + * @param {Object} value to cast + * @api private + */ + +SchemaDate.prototype.cast = function (value) { + if (value === null || value === '') + return null; + + if (value instanceof Date) + return value; + + var date; + + // support for timestamps + if (value instanceof Number || 'number' == typeof value + || String(value) == Number(value)) + date = new Date(Number(value)); + + // support for date strings + else if (value.toString) + date = new Date(value.toString()); + + if (date.toString() != 'Invalid Date') + return date; + + throw new CastError('date', value); +}; + +/** + * Date Query casting. + * + * @api private + */ + +function handleSingle (val) { + return this.cast(val); +} + +function handleArray (val) { + var self = this; + return val.map( function (m) { + return self.cast(m); + }); +} + +SchemaDate.prototype.$conditionalHandlers = { + '$lt': handleSingle + , '$lte': handleSingle + , '$gt': handleSingle + , '$gte': handleSingle + , '$ne': handleSingle + , '$in': handleArray + , '$nin': handleArray +}; + +SchemaDate.prototype.castForQuery = function ($conditional, val) { + var handler; + + if (2 !== arguments.length) { + return this.cast($conditional); + } + + handler = this.$conditionalHandlers[$conditional]; + + if (!handler) { + throw new Error("Can't use " + $conditional + " with Date."); + } + + return handler.call(this, val); +}; + +/** + * Module exports. + */ + +module.exports = SchemaDate; diff --git a/node_modules/mongoose/lib/schema/documentarray.js b/node_modules/mongoose/lib/schema/documentarray.js new file mode 100644 index 0000000..45f3fd1 --- /dev/null +++ b/node_modules/mongoose/lib/schema/documentarray.js @@ -0,0 +1,138 @@ + +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , ArrayType = require('./array') + , MongooseDocumentArray = require('../types/documentarray') + , Subdocument = require('../types/embedded') + , CastError = SchemaType.CastError + , Document = require('../document'); + +/** + * SubdocsArray SchemaType constructor + * + * @param {String} key + * @param {Schema} schema + * @param {Object} options + * @api private + */ + +function DocumentArray (key, schema, options) { + // compile an embedded document for this schema + // TODO Move this into parent model compilation for performance improvement? + function EmbeddedDocument () { + Subdocument.apply(this, arguments); + }; + + EmbeddedDocument.prototype.__proto__ = Subdocument.prototype; + EmbeddedDocument.prototype.schema = schema; + EmbeddedDocument.schema = schema; + + // apply methods + for (var i in schema.methods) { + EmbeddedDocument.prototype[i] = schema.methods[i]; + } + + // apply statics + for (var i in schema.statics) + EmbeddedDocument[i] = schema.statics[i]; + + ArrayType.call(this, key, EmbeddedDocument, options); + + this.caster = EmbeddedDocument; + this.caster.options = options; + + var self = this; + + this.schema = schema; + this.default(function(){ + return new MongooseDocumentArray([], self.path, this); + }); +}; + +/** + * Inherits from ArrayType. + */ + +DocumentArray.prototype.__proto__ = ArrayType.prototype; + +/** + * Performs local validations first, then validations on each embedded doc + * + * @api private + */ + +DocumentArray.prototype.doValidate = function (array, fn, scope) { + var self = this; + SchemaType.prototype.doValidate.call(this, array, function(err){ + if (err) return fn(err); + + var count = array.length + , error = false; + + if (!count) return fn(); + + array.forEach(function(doc, index){ + doc.validate(function(err){ + if (err && !error){ + // rewrite they key + err.key = self.key + '.' + index + '.' + err.key; + fn(err); + error = true; + } else { + --count || fn(); + } + }); + }); + }, scope); +}; + +/** + * Casts contents + * + * @param {Object} value + * @param {Document} document that triggers the casting + * @api private + */ + +DocumentArray.prototype.cast = function (value, doc, init, prev) { + var subdoc + , i + + if (Array.isArray(value)) { + if (!(value instanceof MongooseDocumentArray)) { + value = new MongooseDocumentArray(value, this.path, doc); + } + + i = value.length; + + while (i--) { + if (!(value[i] instanceof Subdocument)) { + if (init) { + subdoc = new this.caster(null, value); + // skip _id for pre-init hooks + delete subdoc._doc._id; + value[i] = subdoc.init(value[i]); + } else { + subdoc = prev && prev.id(value[i]._id) || + new this.caster(null, value); + value[i] = subdoc.set(value[i]); + } + } + } + + return value; + } else { + return this.cast([value], doc, init, prev); + } + + throw new CastError('documentarray', value); +}; + +/** + * Module exports. + */ + +module.exports = DocumentArray; diff --git a/node_modules/mongoose/lib/schema/index.js b/node_modules/mongoose/lib/schema/index.js new file mode 100644 index 0000000..ac424c3 --- /dev/null +++ b/node_modules/mongoose/lib/schema/index.js @@ -0,0 +1,22 @@ + +/** + * Module exports. + */ + +exports.String = require('./string'); + +exports.Number = require('./number'); + +exports.Boolean = require('./boolean'); + +exports.DocumentArray = require('./documentarray'); + +exports.Array = require('./array'); + +exports.Buffer = require('./buffer'); + +exports.Date = require('./date'); + +exports.ObjectId = require('./objectid'); + +exports.Mixed = require('./mixed'); diff --git a/node_modules/mongoose/lib/schema/mixed.js b/node_modules/mongoose/lib/schema/mixed.js new file mode 100644 index 0000000..6f98be0 --- /dev/null +++ b/node_modules/mongoose/lib/schema/mixed.js @@ -0,0 +1,63 @@ + +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype'); + +/** + * Mixed SchemaType constructor. + * + * @param {String} path + * @param {Object} options + * @api private + */ + +function Mixed (path, options) { + // make sure empty array defaults are handled + if (options && + options.default && + Array.isArray(options.default) && + 0 === options.default.length) { + options.default = Array; + } + + SchemaType.call(this, path, options); +}; + +/** + * Inherits from SchemaType. + */ +Mixed.prototype.__proto__ = SchemaType.prototype; + +/** + * Required validator for mixed type + * + * @api private + */ + +Mixed.prototype.checkRequired = function (val) { + return true; +}; + +/** + * Noop casting + * + * @param {Object} value to cast + * @api private + */ + +Mixed.prototype.cast = function (val) { + return val; +}; + +Mixed.prototype.castForQuery = function ($cond, val) { + if (arguments.length === 2) return val; + return $cond; +}; + +/** + * Module exports. + */ + +module.exports = Mixed; diff --git a/node_modules/mongoose/lib/schema/number.js b/node_modules/mongoose/lib/schema/number.js new file mode 100644 index 0000000..8d4f4a2 --- /dev/null +++ b/node_modules/mongoose/lib/schema/number.js @@ -0,0 +1,142 @@ +/** + * Module requirements. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError + , MongooseNumber = require('../types/number'); + +/** + * Number SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @api private + */ + +function SchemaNumber (key, options) { + SchemaType.call(this, key, options, 'Number'); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaNumber.prototype.__proto__ = SchemaType.prototype; + +/** + * Required validator for number + * + * @api private + */ + +SchemaNumber.prototype.checkRequired = function checkRequired (value) { + if (SchemaType._isRef(this, value, true)) { + return null != value; + } else { + return typeof value == 'number' || value instanceof Number; + } +}; + +/** + * Sets a maximum number validator + * + * @param {Number} minimum number + * @api public + */ + +SchemaNumber.prototype.min = function (value, message) { + if (this.minValidator) + this.validators = this.validators.filter(function(v){ + return v[1] != 'min'; + }); + if (value != null) + this.validators.push([function(v){ + return v >= value; + }, 'min']); + return this; +}; + +/** + * Sets a maximum number validator + * + * @param {Number} maximum number + * @api public + */ + +SchemaNumber.prototype.max = function (value, message) { + if (this.maxValidator) + this.validators = this.validators.filter(function(v){ + return v[1] != 'max'; + }); + if (value != null) + this.validators.push([this.maxValidator = function(v){ + return v <= value; + }, 'max']); + return this; +}; + +/** + * Casts to number + * + * @param {Object} value to cast + * @param {Document} document that triggers the casting + * @api private + */ + +SchemaNumber.prototype.cast = function (value, doc, init) { + if (SchemaType._isRef(this, value, init)) return value; + + if (!isNaN(value)){ + if (null === value) return value; + if ('' === value) return null; + if ('string' === typeof value) value = Number(value); + if (value instanceof Number || typeof value == 'number' || + (value.toString && value.toString() == Number(value))) + return new MongooseNumber(value, this.path, doc); + } + + throw new CastError('number', value); +}; + +function handleSingle (val) { + return this.cast(val).valueOf(); +} + +function handleArray (val) { + var self = this; + return val.map( function (m) { + return self.cast(m).valueOf(); + }); +} + +SchemaNumber.prototype.$conditionalHandlers = { + '$lt' : handleSingle + , '$lte': handleSingle + , '$gt' : handleSingle + , '$gte': handleSingle + , '$ne' : handleSingle + , '$in' : handleArray + , '$nin': handleArray + , '$mod': handleArray + , '$all': handleArray +}; + +SchemaNumber.prototype.castForQuery = function ($conditional, val) { + var handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) + throw new Error("Can't use " + $conditional + " with Number."); + return handler.call(this, val); + } else { + val = this.cast($conditional); + return val == null ? val : val.valueOf(); + } +}; + +/** + * Module exports. + */ + +module.exports = SchemaNumber; diff --git a/node_modules/mongoose/lib/schema/objectid.js b/node_modules/mongoose/lib/schema/objectid.js new file mode 100644 index 0000000..31a9693 --- /dev/null +++ b/node_modules/mongoose/lib/schema/objectid.js @@ -0,0 +1,124 @@ +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError + , driver = global.MONGOOSE_DRIVER_PATH || './../drivers/node-mongodb-native' + , oid = require('../types/objectid'); + + +/** + * ObjectId SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @api private + */ + +function ObjectId (key, options) { + SchemaType.call(this, key, options, 'ObjectID'); +}; + +/** + * Inherits from SchemaType. + */ + +ObjectId.prototype.__proto__ = SchemaType.prototype; + +/** + * Check required + * + * @api private + */ + +ObjectId.prototype.checkRequired = function checkRequired (value) { + if (SchemaType._isRef(this, value, true)) { + return null != value; + } else { + return value instanceof oid; + } +}; + +/** + * Casts to ObjectId + * + * @param {Object} value + * @param {Object} scope + * @param {Boolean} whether this is an initialization cast + * @api private + */ + +ObjectId.prototype.cast = function (value, scope, init) { + if (SchemaType._isRef(this, value, init)) return value; + + if (value === null) return value; + + if (value instanceof oid) + return value; + + if (value._id && value._id instanceof oid) + return value._id; + + if (value.toString) + return oid.fromString(value.toString()); + + throw new CastError('object id', value); +}; + +function handleSingle (val) { + return this.cast(val); +} + +function handleArray (val) { + var self = this; + return val.map(function (m) { + return self.cast(m); + }); +} + +ObjectId.prototype.$conditionalHandlers = { + '$ne': handleSingle + , '$in': handleArray + , '$nin': handleArray + , '$gt': handleSingle + , '$lt': handleSingle + , '$gte': handleSingle + , '$lte': handleSingle +}; +ObjectId.prototype.castForQuery = function ($conditional, val) { + var handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) + throw new Error("Can't use " + $conditional + " with ObjectId."); + return handler.call(this, val); + } else { + val = $conditional; + return this.cast(val); + } +}; + +/** + * Adds an auto-generated ObjectId default if turnOn is true. + * @param {Boolean} turnOn auto generated ObjectId defaults + * @api private + */ + +ObjectId.prototype.auto = function (turnOn) { + if (turnOn) { + this.default(function(){ + return new oid(); + }); + this.set(function (v) { + this.__id = null; + return v; + }) + } +}; + +/** + * Module exports. + */ + +module.exports = ObjectId; diff --git a/node_modules/mongoose/lib/schema/string.js b/node_modules/mongoose/lib/schema/string.js new file mode 100644 index 0000000..5187e4f --- /dev/null +++ b/node_modules/mongoose/lib/schema/string.js @@ -0,0 +1,180 @@ + +/** + * Module dependencies. + */ + +var SchemaType = require('../schematype') + , CastError = SchemaType.CastError; + +/** + * String SchemaType constructor. + * + * @param {String} key + * @api private + */ + +function SchemaString (key, options) { + this.enumValues = []; + this.regExp = null; + SchemaType.call(this, key, options, 'String'); +}; + +/** + * Inherits from SchemaType. + */ + +SchemaString.prototype.__proto__ = SchemaType.prototype; + +/** + * Adds enumeration values + * + * @param {multiple} enumeration values + * @api public + */ + +SchemaString.prototype.enum = function () { + var len = arguments.length; + if (!len || undefined === arguments[0] || false === arguments[0]) { + if (this.enumValidator){ + this.enumValidator = false; + this.validators = this.validators.filter(function(v){ + return v[1] != 'enum'; + }); + } + return; + } + + for (var i = 0; i < len; i++) { + if (undefined !== arguments[i]) { + this.enumValues.push(this.cast(arguments[i])); + } + } + + if (!this.enumValidator) { + var values = this.enumValues; + this.enumValidator = function(v){ + return ~values.indexOf(v); + }; + this.validators.push([this.enumValidator, 'enum']); + } +}; + +/** + * Adds a lowercase setter + * + * @api public + */ + +SchemaString.prototype.lowercase = function () { + return this.set(function (v) { + return v.toLowerCase(); + }); +}; + +/** + * Adds an uppercase setter + * + * @api public + */ + +SchemaString.prototype.uppercase = function () { + return this.set(function (v) { + return v.toUpperCase(); + }); +}; + +/** + * Adds a trim setter + * + * @api public + */ + +SchemaString.prototype.trim = function () { + return this.set(function (v) { + return v.trim(); + }); +}; + +/** + * Sets a regexp test + * + * @param {RegExp} regular expression to test against + * @param {String} optional validator message + * @api public + */ + +SchemaString.prototype.match = function(regExp){ + this.validators.push([function(v){ + return regExp.test(v); + }, 'regexp']); +}; + +/** + * Check required + * + * @api private + */ + +SchemaString.prototype.checkRequired = function checkRequired (value) { + if (SchemaType._isRef(this, value, true)) { + return null != value; + } else { + return (value instanceof String || typeof value == 'string') && value.length; + } +}; + +/** + * Casts to String + * + * @api private + */ + +SchemaString.prototype.cast = function (value, scope, init) { + if (SchemaType._isRef(this, value, init)) return value; + if (value === null) return value; + if ('undefined' !== typeof value && value.toString) return value.toString(); + throw new CastError('string', value); +}; + +function handleSingle (val) { + return this.castForQuery(val); +} + +function handleArray (val) { + var self = this; + return val.map(function (m) { + return self.castForQuery(m); + }); +} + +SchemaString.prototype.$conditionalHandlers = { + '$ne' : handleSingle + , '$in' : handleArray + , '$nin': handleArray + , '$gt' : handleSingle + , '$lt' : handleSingle + , '$gte': handleSingle + , '$lte': handleSingle + , '$all': handleArray + , '$regex': handleSingle +}; + +SchemaString.prototype.castForQuery = function ($conditional, val) { + var handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) + throw new Error("Can't use " + $conditional + " with String."); + return handler.call(this, val); + } else { + val = $conditional; + if (val instanceof RegExp) return val; + return this.cast(val); + } +}; + +/** + * Module exports. + */ + +module.exports = SchemaString; diff --git a/node_modules/mongoose/lib/schemadefault.js b/node_modules/mongoose/lib/schemadefault.js new file mode 100644 index 0000000..a468c85 --- /dev/null +++ b/node_modules/mongoose/lib/schemadefault.js @@ -0,0 +1,32 @@ + +/** + * Module dependencies. + */ + +var Schema = require('./schema') + +/** + * Default model for querying the system.profiles + * collection (it only exists when profiling is + * enabled. + */ + +exports['system.profile'] = new Schema({ + ts: Date + , info: String // deprecated + , millis: Number + , op: String + , ns: String + , query: Schema.Types.Mixed + , updateobj: Schema.Types.Mixed + , ntoreturn: Number + , nreturned: Number + , nscanned: Number + , responseLength: Number + , client: String + , user: String + , idhack: Boolean + , scanAndOrder: Boolean + , keyUpdates: Number + , cursorid: Number +}, { noVirtualId: true, noId: true }); diff --git a/node_modules/mongoose/lib/schematype.js b/node_modules/mongoose/lib/schematype.js new file mode 100644 index 0000000..26f42e1 --- /dev/null +++ b/node_modules/mongoose/lib/schematype.js @@ -0,0 +1,375 @@ + +/** + * Module dependencies. + */ + +var MongooseError = require('./error'); +var utils = require('./utils'); + +/** + * SchemaType constructor + * + * @param {String} path + * @api public + */ + +function SchemaType (path, options, instance) { + this.path = path; + this.instance = instance; + this.validators = []; + this.setters = []; + this.getters = []; + this.options = options; + this._index = null; + + for (var i in options) if (this[i] && 'function' == typeof this[i]) { + var opts = Array.isArray(options[i]) + ? options[i] + : [options[i]]; + + this[i].apply(this, opts); + } +}; + +/** + * Base schema. Set by Schema when instantiated. + * + * @api private + */ + +SchemaType.prototype.base; + +/** + * Sets a default + * + * @param {Object} default value + * @api public + */ + +SchemaType.prototype.default = function (val) { + if (1 === arguments.length) { + this.defaultValue = typeof val === 'function' + ? val + : this.cast(val); + return this; + } else if (arguments.length > 1) { + this.defaultValue = utils.args(arguments); + } + return this.defaultValue; +}; + +/** + * Sets index. It can be a boolean or a hash of options + * Example: + * Schema.path('my.path').index(true); + * Schema.path('my.path').index({ unique: true }); + * + * "Direction doesn't matter for single key indexes" + * http://www.mongodb.org/display/DOCS/Indexes#Indexes-CompoundKeysIndexes + * + * @param {Object} true/ + * @api public + */ + +SchemaType.prototype.index = function (index) { + this._index = index; + return this; +}; + +/** + * Adds an unique index + * + * @param {Boolean} + * @api private + */ + +SchemaType.prototype.unique = function (bool) { + if (!this._index || 'Object' !== this._index.constructor.name) { + this._index = {}; + } + + this._index.unique = bool; + return this; +}; + +/** + * Adds an unique index + * + * @param {Boolean} + * @api private + */ + +SchemaType.prototype.sparse = function (bool) { + if (!this._index || 'Object' !== this._index.constructor.name) { + this._index = {}; + } + + this._index.sparse = bool; + return this; +}; + +/** + * Adds a setter + * + * @param {Function} setter + * @api public + */ + +SchemaType.prototype.set = function (fn) { + this.setters.push(fn); + return this; +}; + +/** + * Adds a getter + * + * @param {Function} getter + * @api public + */ + +SchemaType.prototype.get = function (fn) { + this.getters.push(fn); + return this; +}; + +/** + * Adds a validator + * + * @param {Object} validator + * @param {String} optional error message + * @api public + */ + +SchemaType.prototype.validate = function (obj, error) { + this.validators.push([obj, error]); + return this; +}; + +/** + * Adds a required validator + * + * @param {Boolean} enable/disable the validator + * @api public + */ + +SchemaType.prototype.required = function (required) { + var self = this; + + function __checkRequired (v) { + return self.checkRequired(v); + } + + if (false === required) { + this.isRequired = false; + this.validators = this.validators.filter(function (v) { + return v[0].name !== '__checkRequired'; + }); + } else { + this.isRequired = true; + this.validators.push([__checkRequired, 'required']); + } + + return this; +}; + +/** + * Gets the default value + * + * @param {Object} scope for callback defaults + * @api private + */ + +SchemaType.prototype.getDefault = function (scope) { + var ret = 'function' === typeof this.defaultValue + ? this.defaultValue.call(scope) + : this.defaultValue; + + if (null !== ret && undefined !== ret) { + return this.cast(ret, scope); + } else { + return ret; + } +}; + +/** + * Applies setters + * + * @param {Object} value + * @param {Object} scope + * @api private + */ + +SchemaType.prototype.applySetters = function (value, scope, init) { + if (SchemaType._isRef(this, value, init)) return value; + + var v = value + , setters = this.setters + , len = setters.length; + + for (var k = len - 1; k >= 0; k--) { + v = setters[k].call(scope, v); + if (null === v || undefined === v) return v; + v = this.cast(v, scope); + } + + if (!len) { + if (null === v || undefined === v) return v; + if (!init) { + // if we just initialized we dont recast + v = this.cast(v, scope, init); + } + } + + return v; +}; + +/** + * Applies getters to a value + * + * @param {Object} value + * @param {Object} scope + * @api private + */ + +SchemaType.prototype.applyGetters = function (value, scope) { + if (SchemaType._isRef(this, value, true)) return value; + + var v = value + , getters = this.getters + , len = getters.length; + + for (var k = len - 1; k >= 0; k--) { + v = this.getters[k].call(scope, v); + if (null === v || undefined === v) return v; + v = this.cast(v, scope); + } + + if (!len) { + if (null === v || undefined === v) return v; + v = this.cast(v, scope); + } + + return v; +}; + +/** + * Performs a validation + * + * @param {Function} callback + * @param {Object} scope + * @api private + */ + +SchemaType.prototype.doValidate = function (value, fn, scope) { + var err = false + , path = this.path + , count = this.validators.length; + + if (!count) return fn(null); + + function validate (val, msg) { + if (err) return; + if (val === undefined || val) { + --count || fn(null); + } else { + fn(new ValidatorError(path, msg)); + err = true; + } + } + + this.validators.forEach(function (v) { + var validator = v[0] + , message = v[1]; + + if (validator instanceof RegExp) { + validate(validator.test(value), message); + } else if ('function' === typeof validator) { + if (2 === validator.length) { + validator.call(scope, value, function (val) { + validate(val, message); + }); + } else { + validate(validator.call(scope, value), message); + } + } + }); +}; + +/** + * Determines if value is a valid Reference. + * + * @param {SchemaType} self + * @param {object} value + * @param {Boolean} init + * @param {MongooseType} instance + * @return Boolean + * @private + */ + +SchemaType._isRef = function (self, value, init) { + if (self.options && self.options.ref && init) { + if (null == value) return true; + if (value._id && value._id.constructor.name === self.instance) return true; + } + + return false; +} + +/** + * Schema validator error + * + * @param {String} path + * @param {String} msg + * @api private + */ + +function ValidatorError (path, type) { + var msg = type + ? '"' + type + '" ' + : ''; + MongooseError.call(this, 'Validator ' + msg + 'failed for path ' + path); + Error.captureStackTrace(this, arguments.callee); + this.name = 'ValidatorError'; + this.path = path; + this.type = type; +}; + +ValidatorError.prototype.toString = function() { + return this.message; +} + +/** + * Inherits from MongooseError + */ + +ValidatorError.prototype.__proto__ = MongooseError.prototype; + +/** + * Cast error + * + * @api private + */ + +function CastError (type, value) { + MongooseError.call(this, 'Cast to ' + type + ' failed for value "' + value + '"'); + Error.captureStackTrace(this, arguments.callee); + this.name = 'CastError'; + this.type = type; + this.value = value; +}; + +/** + * Inherits from MongooseError. + */ + +CastError.prototype.__proto__ = MongooseError.prototype; + +/** + * Module exports. + */ + +module.exports = exports = SchemaType; + +exports.CastError = CastError; + +exports.ValidatorError = ValidatorError; diff --git a/node_modules/mongoose/lib/statemachine.js b/node_modules/mongoose/lib/statemachine.js new file mode 100644 index 0000000..fb8ceb5 --- /dev/null +++ b/node_modules/mongoose/lib/statemachine.js @@ -0,0 +1,179 @@ + +/** + * Module dependencies. + */ + +var utils = require('./utils'); + +/** + * StateMachine represents a minimal `interface` for the + * constructors it builds via StateMachine.ctor(...). + * + * @api private + */ + +var StateMachine = module.exports = exports = function StateMachine () { + this.paths = {}; + this.states = {}; +} + +/** + * StateMachine.ctor('state1', 'state2', ...) + * A factory method for subclassing StateMachine. + * The arguments are a list of states. For each state, + * the constructor's prototype gets state transition + * methods named after each state. These transition methods + * place their path argument into the given state. + * + * @param {String} state + * @param {String} [state] + * @return {Function} subclass constructor + * @private + */ + +StateMachine.ctor = function () { + var states = utils.args(arguments); + + var ctor = function () { + StateMachine.apply(this, arguments); + this.stateNames = states; + + var i = states.length + , state; + + while (i--) { + state = states[i]; + this.states[state] = {}; + } + }; + + ctor.prototype.__proto__ = StateMachine.prototype; + + states.forEach(function (state) { + // Changes the `path`'s state to `state`. + ctor.prototype[state] = function (path) { + this._changeState(path, state); + } + }); + + return ctor; +}; + +/** + * This function is wrapped by the state change functions: + * - `require(path)` + * - `modify(path)` + * - `init(path)` + * @api private + */ + +StateMachine.prototype._changeState = function _changeState (path, nextState) { + var prevState = this.paths[path] + , prevBucket = this.states[prevState]; + + delete this.paths[path]; + if (prevBucket) delete prevBucket[path]; + + this.paths[path] = nextState; + this.states[nextState][path] = true; +} + +StateMachine.prototype.stateOf = function stateOf (path) { + return this.paths[path]; +} + +StateMachine.prototype.clear = function clear (state) { + var keys = Object.keys(this.states[state]) + , i = keys.length + , path + + while (i--) { + path = keys[i]; + delete this.states[state][path]; + delete this.paths[path]; + } +} + +/** + * Checks to see if at least one path is in the states passed in via `arguments` + * e.g., this.some('required', 'inited') + * @param {String} state that we want to check for. + * @private + */ + +StateMachine.prototype.some = function some () { + var self = this; + var what = arguments.length ? arguments : this.stateNames; + return Array.prototype.some.call(what, function (state) { + return Object.keys(self.states[state]).length; + }); +} + +/** + * This function builds the functions that get assigned to `forEach` and `map`, + * since both of those methods share a lot of the same logic. + * + * @param {String} iterMethod is either 'forEach' or 'map' + * @return {Function} + * @api private + */ + +StateMachine.prototype._iter = function _iter (iterMethod) { + return function () { + var numArgs = arguments.length + , states = utils.args(arguments, 0, numArgs-1) + , callback = arguments[numArgs-1]; + + if (!states.length) states = this.stateNames; + + var self = this; + + var paths = states.reduce(function (paths, state) { + return paths.concat(Object.keys(self.states[state])); + }, []); + + return paths[iterMethod](function (path, i, paths) { + return callback(path, i, paths); + }); + }; +} + +/** + * Iterates over the paths that belong to one of the parameter states. + * + * The function profile can look like: + * this.forEach(state1, fn); // iterates over all paths in state1 + * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2 + * this.forEach(fn); // iterates over all paths in all states + * + * @param {String} [state] + * @param {String} [state] + * @param {Function} callback + * @private + */ + +StateMachine.prototype.forEach = function forEach () { + this.forEach = this._iter('forEach'); + return this.forEach.apply(this, arguments); +} + +/** + * Maps over the paths that belong to one of the parameter states. + * + * The function profile can look like: + * this.forEach(state1, fn); // iterates over all paths in state1 + * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2 + * this.forEach(fn); // iterates over all paths in all states + * + * @param {String} [state] + * @param {String} [state] + * @param {Function} callback + * @return {Array} + * @private + */ + +StateMachine.prototype.map = function map () { + this.map = this._iter('map'); + return this.map.apply(this, arguments); +} + diff --git a/node_modules/mongoose/lib/types/_subarray.js b/node_modules/mongoose/lib/types/_subarray.js new file mode 100644 index 0000000..c60905f --- /dev/null +++ b/node_modules/mongoose/lib/types/_subarray.js @@ -0,0 +1,14 @@ + +// DO NOT REQUIRE THIS MODULE +// it is only meant to be read in through vm.createScript +// because it modifies natives + +/** + * Module dependencies. + */ + +/** + * VM script exports. + */ + +exports = Array; diff --git a/node_modules/mongoose/lib/types/array.js b/node_modules/mongoose/lib/types/array.js new file mode 100644 index 0000000..b6eb996 --- /dev/null +++ b/node_modules/mongoose/lib/types/array.js @@ -0,0 +1,425 @@ + +/** + * Module dependencies. + */ + +var EmbeddedDocument = require('./embedded'); +var Document = require('../document'); +var ObjectId = require('./objectid'); + +/** + * Mongoose Array constructor. + * Values always have to be passed to the constructor to initialize, since + * otherwise MongooseArray#push will mark the array as modified to the parent. + * + * @param {Array} values + * @param {String} key path + * @param {Document} parent document + * @api private + * @see http://bit.ly/f6CnZU + */ + +function MongooseArray (values, path, doc) { + var arr = []; + arr.push.apply(arr, values); + arr.__proto__ = MongooseArray.prototype; + + arr._atomics = {}; + arr.validators = []; + arr._path = path; + + if (doc) { + arr._parent = doc; + arr._schema = doc.schema.path(path); + } + + return arr; +}; + +/** + * Inherit from Array + */ + +MongooseArray.prototype = new Array; + +/** + * Stores a queue of atomic operations to perform + * + * @api private + */ + +MongooseArray.prototype._atomics; + +/** + * Parent owner document + * + * @api private + */ + +MongooseArray.prototype._parent; + +/** + * Casts a member + * + * @api private + */ + +MongooseArray.prototype._cast = function (value) { + var cast = this._schema.caster.cast + , doc = this._parent; + + return cast.call(null, value, doc); +}; + +/** + * Marks this array as modified. + * It is called during a nonAtomicPush, an atomic opteration, + * or by an existing embedded document that is modified. + * + * If it bubbles up from an embedded document change, + * then it takes the following arguments (otherwise, takes + * 0 arguments) + * @param {EmbeddedDocument} embeddedDoc that invokes this method on the Array + * @param {String} embeddedPath is what changed inthe embeddedDoc + * + * @api public + */ + +MongooseArray.prototype._markModified = function (embeddedDoc, embeddedPath) { + var parent = this._parent + , dirtyPath; + + if (parent) { + if (arguments.length) { + // If an embedded doc bubbled up the change + dirtyPath = [this._path, this.indexOf(embeddedDoc), embeddedPath].join('.'); + } else { + dirtyPath = this._path; + } + parent.markModified(dirtyPath); + } + + return this; +}; + +/** + * Register an atomic operation with the parent + * + * @param {Array} operation + * @api private + */ + +MongooseArray.prototype._registerAtomic = function (op, val) { + var atomics = this._atomics + if (op === '$pullAll' || op === '$pushAll' || op === '$addToSet') { + atomics[op] || (atomics[op] = []); + atomics[op] = atomics[op].concat(val); + } else if (op === '$pullDocs') { + var pullOp = atomics['$pull'] || (atomics['$pull'] = {}) + , selector = pullOp['_id'] || (pullOp['_id'] = {'$in' : [] }); + selector['$in'] = selector['$in'].concat(val); + } else { + atomics[op] = val; + } + this._markModified(); + return this; +}; + +/** + * Returns true if we have to perform atomics for this, and no normal + * operations + * + * @api public + */ + +MongooseArray.prototype.__defineGetter__('doAtomics', function () { + if (!(this._atomics && 'Object' === this._atomics.constructor.name)) { + return 0; + } + + return Object.keys(this._atomics).length; +}); + +/** + * Pushes item/s to the array atomically. Overrides Array#push + * + * @param {Object} value + * @api public + */ + +var oldPush = Array.prototype.push; + +MongooseArray.prototype.$push = +MongooseArray.prototype.push = function () { + var values = Array.prototype.map.call(arguments, this._cast, this) + , ret = oldPush.apply(this, values); + + // $pushAll might be fibbed (could be $push). But it makes it easier to + // handle what could have been $push, $pushAll combos + this._registerAtomic('$pushAll', values); + + return ret; +}; + +/** + * Pushes item/s to the array non-atomically + * + * @param {Object} value + * @api public + */ + +MongooseArray.prototype.nonAtomicPush = function () { + var values = Array.prototype.map.call(arguments, this._cast, this) + , ret = oldPush.apply(this, values); + + this._markModified(); + + return ret; +}; + +/** + * Pushes several items at once to the array atomically + * + * @param {Array} values + * @api public + */ + +MongooseArray.prototype.$pushAll = function (value) { + var length = this.length; + this.nonAtomicPush.apply(this, value); + // make sure we access the casted elements + this._registerAtomic('$pushAll', this.slice(length)); + return this; +}; + +/** + * Pops the array atomically + * + * @api public + */ + +MongooseArray.prototype.$pop = function () { + this._registerAtomic('$pop', 1); + return this.pop(); +}; + +/** + * Shifts the array + * + * @api public + */ + +MongooseArray.prototype.$shift = function () { + this._registerAtomic('$pop', -1); + return this.shift(); +}; + +/** + * Removes items from an array atomically + * + * Examples: + * doc.array.remove(ObjectId) + * doc.array.remove('tag 1', 'tag 2') + * + * @param {Object} value to remove + * @api public + */ + +MongooseArray.prototype.remove = function () { + var args = Array.prototype.map.call(arguments, this._cast, this); + if (args.length == 1) + this.$pull(args[0]); + else + this.$pullAll(args); + return args; +}; + +/** + * Pulls from the array + * + * @api public + */ + +MongooseArray.prototype.pull = +MongooseArray.prototype.$pull = function () { + var values = Array.prototype.map.call(arguments, this._cast, this) + , oldArr = this._parent.get(this._path) + , i = oldArr.length + , mem; + + while (i--) { + mem = oldArr[i]; + if (mem instanceof EmbeddedDocument) { + if (values.some(function (v) { return v.equals(mem); } )) { + oldArr.splice(i, 1); + } + } else if (~values.indexOf(mem)) { + oldArr.splice(i, 1); + } + } + + if (values[0] instanceof EmbeddedDocument) { + this._registerAtomic('$pullDocs', values.map( function (v) { return v._id; } )); + } else { + this._registerAtomic('$pullAll', values); + } + + return this; +}; + +/** + * Pulls many items from an array + * + * @api public + */ + +MongooseArray.prototype.$pullAll = function (values) { + if (values && values.length) { + var oldArr = this._parent.get(this._path) + , i = oldArr.length, mem; + while (i--) { + mem = oldArr[i]; + if (mem instanceof EmbeddedDocument) { + if (values.some( function (v) { return v.equals(mem); } )) { + oldArr.splice(i, 1); + } + } else if (~values.indexOf(mem)) oldArr.splice(i, 1); + } + if (values[0] instanceof EmbeddedDocument) { + this._registerAtomic('$pullDocs', values.map( function (v) { return v._id; } )); + } else { + this._registerAtomic('$pullAll', values); + } + } + return this; +}; + +/** + * Splices the array. + * + * Note: marks the _entire_ array as modified which + * will pass the entire thing to $set potentially + * overwritting any changes that happen between + * when you retrieved the object and when you save + * it. + * + * @api public + */ + +var oldSplice = Array.prototype.splice; +MongooseArray.prototype.splice = function () { + oldSplice.apply(this, arguments); + this._markModified(); + return this; +}; + +/** + * Unshifts onto the array. + * + * Note: marks the _entire_ array as modified which + * will pass the entire thing to $set potentially + * overwritting any changes that happen between + * when you retrieved the object and when you save + * it. + * + * @api public + */ + +var oldUnshift = Array.prototype.unshift; +MongooseArray.prototype.unshift = function () { + var values = Array.prototype.map.call(arguments, this._cast, this); + oldUnshift.apply(this, values); + this._markModified(); + return this.length; +}; + +/** + * Adds values to the array if not already present. + * @api public + */ + +MongooseArray.prototype.$addToSet = +MongooseArray.prototype.addToSet = function addToSet () { + var values = Array.prototype.map.call(arguments, this._cast, this) + , added = [] + , type = values[0] instanceof EmbeddedDocument ? 'doc' : + values[0] instanceof Date ? 'date' : + ''; + + values.forEach(function (v) { + var found; + switch (type) { + case 'doc': + found = this.some(function(doc){ return doc.equals(v) }); + break; + case 'date': + var val = +v; + found = this.some(function(d){ return +d === val }); + break; + default: + found = ~this.indexOf(v); + } + + if (!found) { + oldPush.call(this, v); + this._registerAtomic('$addToSet', v); + oldPush.call(added, v); + } + }, this); + + return added; +}; + +/** + * Returns an Array + * + * @return {Array} + * @api public + */ + +MongooseArray.prototype.toObject = function (options) { + if (options && options.depopulate && this[0] instanceof Document) { + return this.map(function (doc) { + return doc._id; + }); + } + + return this.map(function (doc) { + return doc; + }); +}; + +/** + * Helper for console.log + * + * @api public + */ + +MongooseArray.prototype.inspect = function () { + return '[' + this.map(function (doc) { + return ' ' + doc; + }) + ' ]'; +}; + +/** + * Return the index of `obj` or `-1.` + * + * @param {Object} obj + * @return {Number} + * @api public + */ + +MongooseArray.prototype.indexOf = function indexOf (obj) { + if (obj instanceof ObjectId) obj = obj.toString(); + for (var i = 0, len = this.length; i < len; ++i) { + if (obj == this[i]) + return i; + } + return -1; +}; + +/** + * Module exports. + */ + +module.exports = exports = MongooseArray; diff --git a/node_modules/mongoose/lib/types/buffer.js b/node_modules/mongoose/lib/types/buffer.js new file mode 100644 index 0000000..9092f20 --- /dev/null +++ b/node_modules/mongoose/lib/types/buffer.js @@ -0,0 +1,171 @@ + +/** + * Access driver. + */ + +var driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native'; + +/** + * Module dependencies. + */ + +var Binary = require(driver + '/binary'); + +/** + * Mongoose Buffer constructor. + * Values always have to be passed to the constructor to initialize, since + * otherwise MongooseBuffer#push will mark the buffer as modified to the parent. + * + * @param {Buffer} value + * @param {String} key path + * @param {Document} parent document + * @api private + * @see http://bit.ly/f6CnZU + */ + +function MongooseBuffer (value, encode, offset) { + var length = arguments.length; + var val; + + if (0 === length || null === arguments[0] || undefined === arguments[0]) { + val = 0; + } else { + val = value; + } + + var encoding; + var path; + var doc; + + if (Array.isArray(encode)) { + // internal casting + path = encode[0]; + doc = encode[1]; + } else { + encoding = encode; + } + + var buf = new Buffer(val, encoding, offset); + buf.__proto__ = MongooseBuffer.prototype; + + // make sure these internal props don't show up in Object.keys() + Object.defineProperties(buf, { + validators: { value: [] } + , _path: { value: path } + , _parent: { value: doc } + }); + + if (doc && "string" === typeof path) { + Object.defineProperty(buf, '_schema', { + value: doc.schema.path(path) + }); + } + + return buf; +}; + +/** + * Inherit from Buffer. + */ + +MongooseBuffer.prototype = new Buffer(0); + +/** + * Parent owner document + * + * @api private + */ + +MongooseBuffer.prototype._parent; + + +/** +* Marks this buffer as modified. +* +* @api public +*/ + +MongooseBuffer.prototype._markModified = function () { + var parent = this._parent; + + if (parent) { + parent.markModified(this._path); + } + return this; +}; + +/** +* Writes the buffer. +*/ + +MongooseBuffer.prototype.write = function () { + var written = Buffer.prototype.write.apply(this, arguments); + + if (written > 0) { + this._markModified(); + } + + return written; +}; + +/** +* Copy the buffer. +* +* Note: Buffer#copy will not mark target as modified so +* you must copy from a MongooseBuffer for it to work +* as expected. +* +* Work around since copy modifies the target, not this. +*/ + +MongooseBuffer.prototype.copy = function (target) { + var ret = Buffer.prototype.copy.apply(this, arguments); + + if (target instanceof MongooseBuffer) { + target._markModified(); + } + + return ret; +}; + +/** + * Compile other Buffer methods marking this buffer as modified. + */ + +;( +// node < 0.5 +'writeUInt8 writeUInt16 writeUInt32 writeInt8 writeInt16 writeInt32 ' + +'writeFloat writeDouble fill ' + +'utf8Write binaryWrite asciiWrite set ' + + +// node >= 0.5 +'writeUInt16LE writeUInt16BE writeUInt32LE writeUInt32BE ' + +'writeInt16LE writeInt16BE writeInt32LE writeInt32BE ' + +'writeFloatLE writeFloatBE writeDoubleLE writeDoubleBE' +).split(' ').forEach(function (method) { + if (!Buffer.prototype[method]) return; + MongooseBuffer.prototype[method] = new Function( + 'var ret = Buffer.prototype.'+method+'.apply(this, arguments);' + + 'this._markModified();' + + 'return ret;' + ) +}); + +/** + * Returns a Binary. + * + * @return {Buffer} + * @api public + */ + +MongooseBuffer.prototype.toObject = function () { + return new Binary(this, 0x00); +}; + +/** + * Module exports. + */ + +MongooseBuffer.Binary = Binary; + +module.exports = MongooseBuffer; diff --git a/node_modules/mongoose/lib/types/documentarray.js b/node_modules/mongoose/lib/types/documentarray.js new file mode 100644 index 0000000..f3c66bb --- /dev/null +++ b/node_modules/mongoose/lib/types/documentarray.js @@ -0,0 +1,133 @@ + +/** + * Module dependencies. + */ + +var MongooseArray = require('./array') + , driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native' + , ObjectId = require(driver + '/objectid') + , ObjectIdSchema = require('../schema/objectid'); + +/** + * Array of embedded documents + * Values always have to be passed to the constructor to initialize, since + * otherwise MongooseArray#push will mark the array as modified to the parent. + * + * @param {Array} values + * @param {String} key path + * @param {Document} parent document + * @api private + * @see http://bit.ly/f6CnZU + */ + +function MongooseDocumentArray (values, path, doc) { + var arr = []; + arr.push.apply(arr, values); + arr.__proto__ = MongooseDocumentArray.prototype; + + arr._atomics = {}; + arr.validators = []; + arr._path = path; + + if (doc) { + arr._parent = doc; + arr._schema = doc.schema.path(path); + doc.on('save', arr.notify('save')); + doc.on('isNew', arr.notify('isNew')); + } + + return arr; +}; + +/** + * Inherits from MongooseArray + */ + +MongooseDocumentArray.prototype.__proto__ = MongooseArray.prototype; + +/** + * Overrides cast + * + * @api private + */ + +MongooseDocumentArray.prototype._cast = function (value) { + var doc = new this._schema.caster(value, this); + return doc; +}; + +/** + * Filters items by id + * + * @param {Object} id + * @api public + */ + +MongooseDocumentArray.prototype.id = function (id) { + try { + var casted = ObjectId.toString(ObjectIdSchema.prototype.cast.call({}, id)); + } catch (e) { + var casted = null; + } + + for (var i = 0, l = this.length; i < l; i++) { + if (!(this[i].get('_id') instanceof ObjectId)) { + if (String(id) == this[i].get('_id').toString()) + return this[i]; + } else { + if (casted == this[i].get('_id').toString()) + return this[i]; + } + } + + return null; +}; + +/** + * Returns an Array and converts any Document + * members toObject. + * + * @return {Array} + * @api public + */ + +MongooseDocumentArray.prototype.toObject = function () { + return this.map(function (doc) { + return doc && doc.toObject() || null; + }); +}; + +/** + * Helper for console.log + * + * @api public + */ + +MongooseDocumentArray.prototype.inspect = function () { + return '[' + this.map(function (doc) { + return doc && doc.inspect() || 'null'; + }).join('\n') + ']'; +}; + +/** + * Create fn that notifies all child docs of event. + * + * @param {String} event + * @api private + */ + +MongooseDocumentArray.prototype.notify = function notify (event) { + var self = this; + return function notify (val) { + var i = self.length; + while (i--) { + self[i].emit(event, val); + } + } +} + +/** + * Module exports. + */ + +module.exports = MongooseDocumentArray; diff --git a/node_modules/mongoose/lib/types/embedded.js b/node_modules/mongoose/lib/types/embedded.js new file mode 100644 index 0000000..1f24730 --- /dev/null +++ b/node_modules/mongoose/lib/types/embedded.js @@ -0,0 +1,104 @@ + +/** + * Module dependencies. + */ + +var Document = require('../document') + , inspect = require('util').inspect; + +/** + * EmbeddedDocument constructor. + * + * @param {Object} object from db + * @param {MongooseDocumentArray} parent array + * @api private + */ + +function EmbeddedDocument (obj, parentArr) { + this.parentArray = parentArr; + this.parent = parentArr._parent; + Document.call(this, obj); + + var self = this; + this.on('isNew', function (val) { + self.isNew = val; + }); +}; + +/** + * Inherit from Document + * + */ + +EmbeddedDocument.prototype.__proto__ = Document.prototype; + +/** + * Marks parent array as modified + * + * @api private + */ + +EmbeddedDocument.prototype.commit = +EmbeddedDocument.prototype.markModified = function (path) { + this._activePaths.modify(path); + + if (this.isNew) { + // Mark the WHOLE parent array as modified + // if this is a new document (i.e., we are initializing + // a document), + this.parentArray._markModified(); + } else + this.parentArray._markModified(this, path); +}; + +/** + * Save the subdocument + * + * @api public + */ + +EmbeddedDocument.prototype.save = function(fn) { + if (fn) + fn(null); + return this; +}; + +/** + * Remove the subdocument + * + * @api public + */ + +EmbeddedDocument.prototype.remove = function (fn) { + var _id; + if (!this.willRemove) { + _id = this._doc._id; + if (!_id) { + throw new Error('For your own good, Mongoose does not know ' + + 'how to remove an EmbeddedDocument that has no _id'); + } + this.parentArray.$pull({ _id: _id }); + this.willRemove = true; + } + + if (fn) + fn(null); + + return this; +}; + +/** + * Helper for console.log + * + * @api public + */ + +EmbeddedDocument.prototype.inspect = function () { + return inspect(this.toObject()); +}; + +/** + * Module exports. + */ + +module.exports = EmbeddedDocument; diff --git a/node_modules/mongoose/lib/types/index.js b/node_modules/mongoose/lib/types/index.js new file mode 100644 index 0000000..d07878f --- /dev/null +++ b/node_modules/mongoose/lib/types/index.js @@ -0,0 +1,14 @@ + +/** + * Module exports. + */ + +exports.Array = require('./array'); +exports.Buffer = require('./buffer'); + +exports.Document = // @deprecate +exports.Embedded = require('./embedded'); + +exports.DocumentArray = require('./documentarray'); +exports.Number = require('./number'); +exports.ObjectId = require('./objectid'); diff --git a/node_modules/mongoose/lib/types/number.js b/node_modules/mongoose/lib/types/number.js new file mode 100644 index 0000000..b9b4545 --- /dev/null +++ b/node_modules/mongoose/lib/types/number.js @@ -0,0 +1,83 @@ + +/** + * Module dependencies. + */ + +/** + * MongooseNumber constructor. + * + * @param {Object} value to pass to Number + * @param {Document} parent document + * @api private + */ + +function MongooseNumber (value, path, doc) { + var number = new Number(value); + number.__proto__ = MongooseNumber.prototype; + number._atomics = {}; + number._path = path; + number._parent = doc; + return number; +}; + +/** + * Inherits from Number. + */ + +MongooseNumber.prototype = new Number(); + +/** + * Atomic increment + * + * @api public + */ + +MongooseNumber.prototype.$inc = +MongooseNumber.prototype.increment = function increment (value) { + var schema = this._parent.schema.path(this._path) + , value = Number(value) || 1; + if (isNaN(value)) value = 1; + this._parent.setValue(this._path, schema.cast(this + value)); + this._parent.getValue(this._path)._atomics['$inc'] = value || 1; + this._parent._activePaths.modify(this._path); + return this; +}; + +/** + * Returns true if we have to perform atomics for this, and no normal + * operations + * + * @api public + */ + +MongooseNumber.prototype.__defineGetter__('doAtomics', function () { + return Object.keys(this._atomics).length; +}); + +/** + * Atomic decrement + * + * @api public + */ + +MongooseNumber.prototype.decrement = function(){ + this.increment(-1); +}; + +/** + * Re-declare toString (for `console.log`) + * + * @api public + */ + +MongooseNumber.prototype.inspect = +MongooseNumber.prototype.toString = function () { + return String(this.valueOf()); +}; + + +/** + * Module exports + */ + +module.exports = MongooseNumber; diff --git a/node_modules/mongoose/lib/types/objectid.js b/node_modules/mongoose/lib/types/objectid.js new file mode 100644 index 0000000..e811fdf --- /dev/null +++ b/node_modules/mongoose/lib/types/objectid.js @@ -0,0 +1,12 @@ + +/** + * Access driver. + */ + +var driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native'; + +/** + * Module exports. + */ + +module.exports = require(driver + '/objectid'); diff --git a/node_modules/mongoose/lib/utils.js b/node_modules/mongoose/lib/utils.js new file mode 100644 index 0000000..548194c --- /dev/null +++ b/node_modules/mongoose/lib/utils.js @@ -0,0 +1,420 @@ +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter + , ObjectId = require('./types/objectid') + , MongooseBuffer + , MongooseArray + , Document + +/** + * Produces a collection name from a model name + * + * @param {String} model name + * @return {String} collection name + * @api private + */ + +exports.toCollectionName = function (name) { + if ('system.profile' === name) return name; + if ('system.indexes' === name) return name; + return pluralize(name.toLowerCase()); +}; + +/** + * Pluralization rules. + */ + +var rules = [ + [/(m)an$/gi, '$1en'], + [/(pe)rson$/gi, '$1ople'], + [/(child)$/gi, '$1ren'], + [/^(ox)$/gi, '$1en'], + [/(ax|test)is$/gi, '$1es'], + [/(octop|vir)us$/gi, '$1i'], + [/(alias|status)$/gi, '$1es'], + [/(bu)s$/gi, '$1ses'], + [/(buffal|tomat|potat)o$/gi, '$1oes'], + [/([ti])um$/gi, '$1a'], + [/sis$/gi, 'ses'], + [/(?:([^f])fe|([lr])f)$/gi, '$1$2ves'], + [/(hive)$/gi, '$1s'], + [/([^aeiouy]|qu)y$/gi, '$1ies'], + [/(x|ch|ss|sh)$/gi, '$1es'], + [/(matr|vert|ind)ix|ex$/gi, '$1ices'], + [/([m|l])ouse$/gi, '$1ice'], + [/(quiz)$/gi, '$1zes'], + [/s$/gi, 's'], + [/$/gi, 's'] +]; + +/** + * Uncountable words. + */ + +var uncountables = [ + 'advice', + 'energy', + 'excretion', + 'digestion', + 'cooperation', + 'health', + 'justice', + 'labour', + 'machinery', + 'equipment', + 'information', + 'pollution', + 'sewage', + 'paper', + 'money', + 'species', + 'series', + 'rain', + 'rice', + 'fish', + 'sheep', + 'moose', + 'deer', + 'news' +]; + +/** + * Pluralize function. + * + * @author TJ Holowaychuk (extracted from _ext.js_) + * @param {String} string to pluralize + * @api private + */ + +function pluralize (str) { + var rule, found; + if (!~uncountables.indexOf(str.toLowerCase())){ + found = rules.filter(function(rule){ + return str.match(rule[0]); + }); + if (found[0]) return str.replace(found[0][0], found[0][1]); + } + return str; +}; + +/** + * Add `once` to EventEmitter if absent + * + * @param {String} event name + * @param {Function} listener + * @api private + */ + +var Events = EventEmitter; + +if (!('once' in EventEmitter.prototype)){ + + Events = function () { + EventEmitter.apply(this, arguments); + }; + + /** + * Inherit from EventEmitter. + */ + + Events.prototype.__proto__ = EventEmitter.prototype; + + /** + * Add `once`. + */ + + Events.prototype.once = function (type, listener) { + var self = this; + self.on(type, function g(){ + self.removeListener(type, g); + listener.apply(this, arguments); + }); + }; + +} + +exports.EventEmitter = Events; + +// Modified from node/lib/assert.js +exports.deepEqual = function deepEqual (a, b) { + if (a === b) return true; + + if (a instanceof Date && b instanceof Date) + return a.getTime() === b.getTime(); + + if (a instanceof ObjectId && b instanceof ObjectId) { + return a.toString() === b.toString(); + } + + if (typeof a !== 'object' && typeof b !== 'object') + return a == b; + + if (a === null || b === null || a === undefined || b === undefined) + return false + + if (a.prototype !== b.prototype) return false; + + // Handle MongooseNumbers + if (a instanceof Number && b instanceof Number) { + return a.valueOf() === b.valueOf(); + } + + if (isMongooseObject(a)) a = a.toObject(); + if (isMongooseObject(b)) b = b.toObject(); + + try { + var ka = Object.keys(a), + kb = Object.keys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!deepEqual(a[key], b[key])) return false; + } + + return true; +}; + +/** + * Object clone with Mongoose natives support. + * Creates a minimal data Object. + * It does not clone empty Arrays, empty Objects, + * and undefined values. + * This makes the data payload sent to MongoDB as minimal + * as possible. + * + * @param {Object} object to clone + * @param {Object} options - minimize , retainKeyOrder + * @return {Object} cloned object + * @api private + */ + +var clone = exports.clone = function clone (obj, options) { + if (obj === undefined || obj === null) + return obj; + + if (Array.isArray(obj)) + return cloneArray(obj, options); + + if (isMongooseObject(obj)) + return obj.toObject(options); + + if ('Object' === obj.constructor.name) + return cloneObject(obj, options); + + if ('Date' === obj.constructor.name || 'Function' === obj.constructor.name) + return new obj.constructor(+obj); + + if ('RegExp' === obj.constructor.name) + return new RegExp(obj.source); + + if (obj instanceof ObjectId) + return ObjectId.fromString(ObjectId.toString(obj)); + + if (obj.valueOf) + return obj.valueOf(); +}; + +function cloneObject (obj, options) { + var retainKeyOrder = options && options.retainKeyOrder + , minimize = options && options.minimize + , ret = {} + , hasKeys + , keys + , val + , k + , i + + if (retainKeyOrder) { + for (k in obj) { + val = clone(obj[k], options); + + if (!minimize || ('undefined' !== typeof val)) { + hasKeys || (hasKeys = true); + ret[k] = val; + } + } + } else { + // faster + + keys = Object.keys(obj); + i = keys.length; + + while (i--) { + k = keys[i]; + val = clone(obj[k], options); + + if (!minimize || ('undefined' !== typeof val)) { + if (!hasKeys) hasKeys = true; + ret[k] = val; + } + } + } + + return minimize + ? hasKeys && ret + : ret; +}; + +function cloneArray (arr, options) { + var ret = []; + for (var i = 0, l = arr.length; i < l; i++) + ret.push(clone(arr[i], options)); + return ret; +}; + +/** + * Copies and merges options with defaults. + * + * @param {Object} defaults + * @param {Object} options + * @return {Object} (merged) object + * @api private + */ + +exports.options = function (defaults, options) { + var keys = Object.keys(defaults) + , i = keys.length + , k ; + + options = options || {}; + + while (i--) { + k = keys[i]; + if (!(k in options)) { + options[k] = defaults[k]; + } + } + + return options; +}; + +/** + * Generates a random string + * + * @api private + */ + +exports.random = function () { + return Math.random().toString().substr(3); +}; + +exports.inGroupsOf = function inGroupsOf (card, arr, fn) { + var group = []; + for (var i = 0, l = arr.length; i < l; i++) { + if (i && i % card === 0) { + fn.apply(this, group); + group.length = 0; + } + group.push(arr[i]); + } + fn.apply(this, group); +}; + +/** + * Merges `from` into `to` without overwriting + * existing properties of `to`. + * + * @param {Object} to + * @param {Object} from + */ + +exports.merge = function merge (to, from) { + var keys = Object.keys(from) + , i = keys.length + , key + + while (i--) { + key = keys[i]; + if ('undefined' === typeof to[key]) { + to[key] = from[key]; + } else { + merge(to[key], from[key]); + } + } +}; + +/** + * A faster Array.prototype.slice.call(arguments) alternative + */ + +exports.args = function (args, slice, sliceEnd) { + var ret = []; + var start = slice || 0; + var end = 3 === arguments.length + ? sliceEnd + : args.length; + + for (var i = start; i < end; ++i) { + ret[i - start] = args[i]; + } + + return ret; +} + +/** + * process.nextTick helper. + * + * Wraps `callback` in a try/catch + nextTick. + * + * -native has a habit of state corruption + * when an error is immediately thrown from within + * a collection callback. + * + * @param {Function} callback + * @api private + */ + +exports.tick = function tick (callback) { + if ('function' !== typeof callback) return; + return function () { + try { + callback.apply(this, arguments); + } catch (err) { + // only nextTick on err to get out of + // the event loop and avoid state corruption. + process.nextTick(function () { + throw err; + }); + } + } +} + +/** + * Returns if `v` is a mongoose object that has + * a `toObject()` method we can use. This is for + * compatibility with libs like Date.js which do + * foolish things to Natives. + */ + +var isMongooseObject = exports.isMongooseObject = function (v) { + Document || (Document = require('./document')); + MongooseArray || (MongooseArray = require('./types').Array); + MongooseBuffer || (MongooseBuffer = require('./types').Buffer); + + return v instanceof Document || + v instanceof MongooseArray || + v instanceof MongooseBuffer +} diff --git a/node_modules/mongoose/lib/virtualtype.js b/node_modules/mongoose/lib/virtualtype.js new file mode 100644 index 0000000..5779df7 --- /dev/null +++ b/node_modules/mongoose/lib/virtualtype.js @@ -0,0 +1,74 @@ +/** + * VirtualType constructor + * + * This is what mongoose uses to define virtual attributes via + * `Schema.prototype.virtual` + * + * @api public + */ + +function VirtualType (options) { + this.getters = []; + this.setters = []; + this.options = options || {}; +} + +/** + * Adds a getter + * + * @param {Function} fn + * @return {VirtualType} this + * @api public + */ + +VirtualType.prototype.get = function (fn) { + this.getters.push(fn); + return this; +}; + +/** + * Adds a setter + * + * @param {Function} fn + * @return {VirtualType} this + * @api public + */ + +VirtualType.prototype.set = function (fn) { + this.setters.push(fn); + return this; +}; + +/** + * Applies getters + * + * @param {Object} value + * @param {Object} scope + * @api public + */ + +VirtualType.prototype.applyGetters = function (value, scope) { + var v = value; + for (var l = this.getters.length - 1; l >= 0; l--){ + v = this.getters[l].call(scope, v); + } + return v; +}; + +/** + * Applies setters + * + * @param {Object} value + * @param {Object} scope + * @api public + */ + +VirtualType.prototype.applySetters = function (value, scope) { + var v = value; + for (var l = this.setters.length - 1; l >= 0; l--){ + this.setters[l].call(scope, v); + } + return v; +}; + +module.exports = VirtualType; diff --git a/node_modules/mongoose/package.json b/node_modules/mongoose/package.json new file mode 100644 index 0000000..4bd0fe4 --- /dev/null +++ b/node_modules/mongoose/package.json @@ -0,0 +1,24 @@ +{ + "name": "mongoose" + , "description": "Mongoose MongoDB ODM" + , "version": "2.4.8" + , "author": "Guillermo Rauch " + , "keywords": ["mongodb", "mongoose", "orm", "data", "datastore", "nosql"] + , "dependencies": { + "hooks": "0.1.9" + , "mongodb": "0.9.7-2-5" + , "colors": "0.5.1" + } + , "devDependencies": { + "should": "0.2.1" + , "gleak": "0.2.1" + } + , "directories": { "lib": "./lib/mongoose" } + , "scripts": { "test": "make test" } + , "main": "./index.js" + , "engines": { "node": ">= 0.4.0" } + , "repository": { + "type": "git" + , "url": "git://github.com/LearnBoost/mongoose.git" + } +} diff --git a/node_modules/mongoose/support/cli-table/.gitignore b/node_modules/mongoose/support/cli-table/.gitignore new file mode 100644 index 0000000..8ec3d50 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/.gitignore @@ -0,0 +1 @@ +lib-cov diff --git a/node_modules/mongoose/support/cli-table/History.md b/node_modules/mongoose/support/cli-table/History.md new file mode 100644 index 0000000..4f02434 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/History.md @@ -0,0 +1,5 @@ + +0.0.1 / 2011-01-03 +================== + +Initial release diff --git a/node_modules/mongoose/support/cli-table/Makefile b/node_modules/mongoose/support/cli-table/Makefile new file mode 100644 index 0000000..479a783 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/Makefile @@ -0,0 +1,14 @@ + +test: + @NODE_ENV=test ./support/expresso/bin/expresso \ + -I lib \ + -I support \ + -I support/should.js/lib \ + -I support/colors.js \ + $(TESTFLAGS) \ + test/*.test.js + +test-cov: + @TESTFLAGS=--cov $(MAKE) test + +.PHONY: test test-cov diff --git a/node_modules/mongoose/support/cli-table/README.md b/node_modules/mongoose/support/cli-table/README.md new file mode 100644 index 0000000..4248e7c --- /dev/null +++ b/node_modules/mongoose/support/cli-table/README.md @@ -0,0 +1,77 @@ + +CLI Table +========= + +This utility allows you to render unicode-aided tables on the command line from +your node.js scripts. + +![Screenshot](http://i.imgur.com/sYq4T.png) + +## Features + +- Customizable characters that constitute the table. +- Color/background styling in the header through + [colors.js](http://github.com/marak/colors.js) +- Column width customization +- Text truncation based on predefined widths +- Text alignment (left, right, center) +- Padding (left, right) +- Easy-to-use API + +## Installation + + npm install cli-table + +## How to use + + var Table = require('cli-table'); + + // instantiate + var table = new Table({ + head: ['TH 1 label', 'TH 2 label'] + , colWidths: [100, 200] + }); + + // table is an Array, so you can `push`, `unshift`, `splice` and friends + table.push( + ['First value', 'Second value'] + , ['First value', 'Second value'] + ); + + // render + console.log(table.toString()); + +## Running tests + +Clone the repository with all its submodules and run: + + $ make test + +## Credits + +- Guillermo Rauch <guillermo@learnboost.com> ([Guille](http://github.com/guille)) + +## License + +(The MIT License) + +Copyright (c) 2010 LearnBoost <dev@learnboost.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/mongoose/support/cli-table/examples/revs.js b/node_modules/mongoose/support/cli-table/examples/revs.js new file mode 100644 index 0000000..0f5fcc1 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/examples/revs.js @@ -0,0 +1,25 @@ + +/** + * Module requirements. + */ + +// this line is only needed if you refuse to use npm +require.paths.unshift(__dirname + '/../support/colors'); + +var Table = require('../lib/cli-table'); + +/** + * Example. + */ + +var table = new Table({ + head: ['Rel', 'Change', 'By', 'When'] + , colWidths: [6, 21, 25, 17] +}); + +table.push( + ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '7 minutes ago'] + , ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '8 minutes ago'] +); + +console.log(table.toString()); diff --git a/node_modules/mongoose/support/cli-table/index.js b/node_modules/mongoose/support/cli-table/index.js new file mode 100644 index 0000000..1c01669 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/index.js @@ -0,0 +1,2 @@ + +module.exports = require('./lib/cli-table'); diff --git a/node_modules/mongoose/support/cli-table/lib/cli-table/index.js b/node_modules/mongoose/support/cli-table/lib/cli-table/index.js new file mode 100644 index 0000000..6882a52 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/lib/cli-table/index.js @@ -0,0 +1,205 @@ + +/** + * Module dependencies. + */ + +var utils = require('./utils') + , repeat = utils.repeat + , truncate = utils.truncate + , pad = utils.pad; + +require('colors'); + +/** + * Table constructor + * + * @param {Object} options + * @api public + */ + +function Table (options){ + this.options = utils.options({ + chars: { + 'top': '━' + , 'top-mid': '┳' + , 'top-left': '┏' + , 'top-right': '┓' + , 'bottom': '━' + , 'bottom-mid': '┻' + , 'bottom-left': '┗' + , 'bottom-right': '┛' + , 'left': '┃' + , 'left-mid': '┣' + , 'mid': '━' + , 'mid-mid': '╋' + , 'right': '┃' + , 'right-mid': '┫' + } + , truncate: '…' + , colWidths: [] + , colAligns: [] + , style: { + 'padding-left': 1 + , 'padding-right': 1 + , head: ['cyan'] + } + , head: [] + }, options); +}; + +/** + * Inherit from Array. + */ + +Table.prototype.__proto__ = Array.prototype; + +/** + * Width getter + * + * @return {Number} width + * @api public + */ + +Table.prototype.__defineGetter__('width', function (){ + var str = this.toString().split("\n"); + if (str.length) return str[0].length; + return 0; +}); + +/** + * Render to a string. + * + * @return {String} table representation + * @api public + */ + +Table.prototype.render +Table.prototype.toString = function (){ + var ret = '' + , options = this.options + , style = options.style + , head = options.head + , chars = options.chars + , truncater = options.truncate + , colWidths = options.colWidths || [] + , totalWidth = 0; + + if (!head.length && !this.length) return ''; + + if (!colWidths.length){ + this.concat([head]).forEach(function(cells){ + cells.forEach(function(cell, i){ + var width = typeof cell == 'object' && cell.width != undefined + ? cell.width + : ((typeof cell == 'object' ? String(cell.text) : String(cell)).length + (style['padding-left'] || 0) + (style['padding-right'] || 0)) + colWidths[i] = Math.max(colWidths[i] || 0, width || 0); + }); + }); + }; + + totalWidth = (colWidths.length == 1 ? colWidths[0] : colWidths.reduce( + function (a, b){ + return a + b + })) + colWidths.length + 1; + + // draws a line + function line (line, left, right, intersection){ + var width = 0 + , line = + left + + repeat(line, totalWidth - 2) + + right; + + colWidths.forEach(function (w, i){ + if (i == colWidths.length - 1) return; + width += w + 1; + line = line.substr(0, width) + intersection + line.substr(width + 1); + }); + + ret += line; + }; + + // draws the top line + function lineTop (){ + line(chars.top + , chars['top-left'] || chars.top + , chars['top-right'] || chars.top + , chars['top-mid']); + ret += "\n"; + }; + + // renders a string, by padding it or truncating it + function string (str, index){ + var str = String(typeof str == 'object' && str.text ? str.text : str) + , length = str.length + , width = colWidths[index] + - (style['padding-left'] || 0) + - (style['padding-right'] || 0) + , align = options.colAligns[index] || 'left'; + + return repeat(' ', style['padding-left'] || 0) + + (length == width ? str : + (length < width + ? pad(str, width, ' ', align == 'left' ? 'right' : + (align == 'middle' ? 'both' : 'left')) + : (truncater ? truncate(str, width, truncater) : str)) + ) + + repeat(' ', style['padding-right'] || 0); + }; + + if (head.length){ + lineTop(); + + ret += chars.left; + + head.forEach(function (th, index){ + var text = string(th, index); + if (style.head){ + style.head.forEach(function(style){ + text = text[style]; + }); + } + + ret += text; + ret += chars.right; + }); + + ret += "\n"; + } + + if (this.length) + this.forEach(function (cells, i){ + if (!head.length && i == 0) + lineTop(); + else { + line(chars.mid + , chars['left-mid'] + , chars['right-mid'] + , chars['mid-mid']); + + ret += "\n" + chars.left; + + cells.forEach(function(cell, i){ + ret += string(cell, i); + ret += chars.right; + }); + + ret += "\n"; + } + }); + + line(chars.bottom + , chars['bottom-left'] || chars.bottom + , chars['bottom-right'] || chars.bottom + , chars['bottom-mid']); + + return ret; +}; + +/** + * Module exports. + */ + +module.exports = Table; + +module.exports.version = '0.0.1'; diff --git a/node_modules/mongoose/support/cli-table/lib/cli-table/utils.js b/node_modules/mongoose/support/cli-table/lib/cli-table/utils.js new file mode 100644 index 0000000..9b0c137 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/lib/cli-table/utils.js @@ -0,0 +1,81 @@ + +/** + * Repeats a string. + * + * @param {String} char(s) + * @param {Number} number of times + * @return {String} repeated string + */ + +exports.repeat = function (str, times){ + return Array(times + 1).join(str); +}; + +/** + * Pads a string + * + * @api public + */ + +exports.pad = function (str, len, pad, dir) { + if (len + 1 >= str.length) + switch (dir){ + case 'left': + str = Array(len + 1 - str.length).join(pad) + str; + break; + + case 'both': + var right = Math.ceil((padlen = len - str.length) / 2); + var left = padlen - right; + str = Array(left + 1).join(pad) + str + Array(right + 1).join(pad); + break; + + default: + str = str + Array(len + 1 - str.length).join(pad); + }; + + return str; +}; + +/** + * Truncates a string + * + * @api public + */ + +exports.truncate = function (str, length, chr){ + chr = chr || '…'; + return str.length >= length ? str.substr(0, length - chr.length) + chr : str; +}; + +/** + * Copies and merges options with defaults. + * + * @param {Object} defaults + * @param {Object} supplied options + * @return {Object} new (merged) object + */ + +function clone(a){ + var b; + if (Array.isArray(a)){ + b = []; + for (var i = 0, l = a.length; i < l; i++) + b.push(typeof a[i] == 'object' ? clone(a[i]) : a[i]); + return b; + } else if (typeof a == 'object'){ + b = {}; + for (var i in a) + b[i] = typeof a[i] == 'object' ? clone(a[i]) : a[i]; + return b; + } + return a; +}; + +exports.options = function (defaults, opts){ + var c = clone(opts); + for (var i in defaults) + if (!(i in opts)) + c[i] = defaults[i]; + return c; +}; diff --git a/node_modules/mongoose/support/cli-table/package.json b/node_modules/mongoose/support/cli-table/package.json new file mode 100644 index 0000000..33c4295 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/package.json @@ -0,0 +1,11 @@ +{ "name": "cli-table" + , "description": "Pretty unicode tables for the CLI" + , "version": "0.0.1" + , "author": "Guillermo Rauch " + , "keywords": ["cli", "colors", "table"] + , "dependencies": { + "colors": "0.3.0" + } + , "main": "./index.js" + , "engines": { "node": ">= 0.2.0" } +} diff --git a/node_modules/mongoose/support/cli-table/test/common.js b/node_modules/mongoose/support/cli-table/test/common.js new file mode 100644 index 0000000..9b65e08 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/test/common.js @@ -0,0 +1,6 @@ + +/** + * Module dependencies. + */ + +require('should'); diff --git a/node_modules/mongoose/support/cli-table/test/index.test.js b/node_modules/mongoose/support/cli-table/test/index.test.js new file mode 100644 index 0000000..2711e11 --- /dev/null +++ b/node_modules/mongoose/support/cli-table/test/index.test.js @@ -0,0 +1,52 @@ + +/** + * Module requirements. + */ + +require('./common'); + +var Table = require('cli-table'); + +/** + * Tests. + */ + +module.exports = { + + 'test complete table': function (){ + var table = new Table({ + head: ['Rel', 'Change', 'By', 'When'] + , style: { + 'padding-left': 1 + , 'padding-right': 1 + } + , colWidths: [6, 21, 25, 17] + }); + + table.push( + ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '7 minutes ago'] + , ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '8 minutes ago'] + ); + + var expected = [ + '┏━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓' + , '┃ Rel ┃ Change ┃ By ┃ When ┃' + , '┣━━━━━━╋━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┫' + , '┃ v0.1 ┃ Testing something … ┃ rauchg@gmail.com ┃ 7 minutes ago ┃' + , '┣━━━━━━╋━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┫' + , '┃ v0.1 ┃ Testing something … ┃ rauchg@gmail.com ┃ 8 minutes ago ┃' + , '┗━━━━━━┻━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━┛' + ]; + + table.toString().should.eql(expected.join("\n")); + }, + + 'test width property': function (){ + var table = new Table({ + head: ['Cool'] + }); + + table.width.should.eql(8); + } + +}; diff --git a/node_modules/mongoose/support/expresso/.gitignore b/node_modules/mongoose/support/expresso/.gitignore new file mode 100644 index 0000000..432563f --- /dev/null +++ b/node_modules/mongoose/support/expresso/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +lib-cov +*.seed \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/.gitmodules b/node_modules/mongoose/support/expresso/.gitmodules new file mode 100644 index 0000000..191ddeb --- /dev/null +++ b/node_modules/mongoose/support/expresso/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/jscoverage"] + path = deps/jscoverage + url = git://github.com/visionmedia/node-jscoverage.git diff --git a/node_modules/mongoose/support/expresso/History.md b/node_modules/mongoose/support/expresso/History.md new file mode 100644 index 0000000..cb16fa9 --- /dev/null +++ b/node_modules/mongoose/support/expresso/History.md @@ -0,0 +1,128 @@ + +0.7.2 / 2010-12-29 +================== + + * Fixed problem with `listen()` sometimes firing on the same tick [guillermo] + +0.7.1 / 2010-12-28 +================== + + * Fixed `assert.request()` client logic into an issue() function, fired upon the `listen()` callback if the server doesn't have an assigned fd. [guillermo] + * Removed `--watch` + +0.7.0 / 2010-11-19 +================== + + * Removed `assert` from test function signature + Just use `require('assert')` :) this will make integration + with libraries like [should](http://github.com/visionmedia/should) cleaner. + +0.6.4 / 2010-11-02 +================== + + * Added regexp support to `assert.response()` headers + * Removed `waitForExit` code, causing issues + +0.6.3 / 2010-11-02 +================== + + * Added `assert.response()` body RegExp support + * Fixed issue with _--serial_ not executing files sequentially. Closes #42 + * Fixed hang when modules use `setInterval` - monitor running tests & force the process to quit after all have completed + timeout [Steve Mason] + +0.6.2 / 2010-09-17 +================== + + * Added _node-jsocoverage_ to package.json (aka will respect npm's binroot) + * Added _-t, --timeout_ MS option, defaulting to 2000 ms + * Added _-s, --serial_ + * __PREFIX__ clobberable + * Fixed `assert.response()` for latest node + * Fixed cov reporting from exploding on empty files + +0.6.2 / 2010-08-03 +================== + + * Added `assert.type()` + * Renamed `assert.isNotUndefined()` to `assert.isDefined()` + * Fixed `assert.includes()` param ordering + +0.6.0 / 2010-07-31 +================== + + * Added _docs/api.html_ + * Added -w, --watch + * Added `Array` support to `assert.includes()` + * Added; outputting exceptions immediately. Closes #19 + * Fixed `assert.includes()` param ordering + * Fixed `assert.length()` param ordering + * Fixed jscoverage links + +0.5.0 / 2010-07-16 +================== + + * Added support for async exports + * Added timeout support to `assert.response()`. Closes #3 + * Added 4th arg callback support to `assert.response()` + * Added `assert.length()` + * Added `assert.match()` + * Added `assert.isUndefined()` + * Added `assert.isNull()` + * Added `assert.includes()` + * Added growlnotify support via -g, --growl + * Added -o, --only TESTS. Ex: --only "test foo()" --only "test foo(), test bar()" + * Removed profanity + +0.4.0 / 2010-07-09 +================== + + * Added reporting source coverage (respects --boring for color haters) + * Added callback to assert.response(). Closes #12 + * Fixed; putting exceptions to stderr. Closes #13 + +0.3.1 / 2010-06-28 +================== + + * Faster assert.response() + +0.3.0 / 2010-06-28 +================== + + * Added -p, --port NUM flags + * Added assert.response(). Closes #11 + +0.2.1 / 2010-06-25 +================== + + * Fixed issue with reporting object assertions + +0.2.0 / 2010-06-21 +================== + + * Added `make uninstall` + * Added better readdir() failure message + * Fixed `make install` for kiwi + +0.1.0 / 2010-06-15 +================== + + * Added better usage docs via --help + * Added better conditional color support + * Added pre exit assertion support + +0.0.3 / 2010-06-02 +================== + + * Added more room for filenames in test coverage + * Added boring output support via --boring (suppress colored output) + * Fixed async failure exit status + +0.0.2 / 2010-05-30 +================== + + * Fixed exit status for CI support + +0.0.1 / 2010-05-30 +================== + + * Initial release \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/Makefile b/node_modules/mongoose/support/expresso/Makefile new file mode 100644 index 0000000..8acfe56 --- /dev/null +++ b/node_modules/mongoose/support/expresso/Makefile @@ -0,0 +1,53 @@ + +PREFIX ?= /usr/local +BIN = bin/expresso +JSCOV = deps/jscoverage/node-jscoverage +DOCS = docs/index.md +HTMLDOCS = $(DOCS:.md=.html) + +test: $(BIN) + @./$(BIN) -I lib --growl $(TEST_FLAGS) test/*.test.js + +test-cov: + @./$(BIN) -I lib --cov $(TEST_FLAGS) test/*.test.js + +test-serial: + @./$(BIN) --serial -I lib $(TEST_FLAGS) test/serial/*.test.js + +install: install-jscov install-expresso + +uninstall: + rm -f $(PREFIX)/bin/expresso + rm -f $(PREFIX)/bin/node-jscoverage + +install-jscov: $(JSCOV) + install $(JSCOV) $(PREFIX)/bin + +install-expresso: + install $(BIN) $(PREFIX)/bin + +$(JSCOV): + cd deps/jscoverage && ./configure && make && mv jscoverage node-jscoverage + +clean: + @cd deps/jscoverage && git clean -fd + +docs: docs/api.html $(HTMLDOCS) + +%.html: %.md + @echo "... $< > $@" + @ronn -5 --pipe --fragment $< \ + | cat docs/layout/head.html - docs/layout/foot.html \ + > $@ + +docs/api.html: bin/expresso + dox \ + --title "Expresso" \ + --ribbon "http://github.com/visionmedia/expresso" \ + --desc "Insanely fast TDD framework for [node](http://nodejs.org) featuring code coverage reporting." \ + $< > $@ + +docclean: + rm -f docs/*.html + +.PHONY: test test-cov install uninstall install-expresso install-jscov clean docs docclean \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/Readme.md b/node_modules/mongoose/support/expresso/Readme.md new file mode 100644 index 0000000..05c972e --- /dev/null +++ b/node_modules/mongoose/support/expresso/Readme.md @@ -0,0 +1,61 @@ + +# Expresso + + TDD framework for [nodejs](http://nodejs.org). + +## Features + + - light-weight + - intuitive async support + - intuitive test runner executable + - test coverage support and reporting + - uses the _assert_ module + - `assert.eql()` alias of `assert.deepEqual()` + - `assert.response()` http response utility + - `assert.includes()` + - `assert.type()` + - `assert.isNull()` + - `assert.isUndefined()` + - `assert.isNotNull()` + - `assert.isDefined()` + - `assert.match()` + - `assert.length()` + +## Installation + +To install both expresso _and_ node-jscoverage run: + + $ make install + +To install expresso alone (no build required) run: + + $ make install-expresso + +Install via npm: + + $ npm install expresso + +## License + +(The MIT License) + +Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/mongoose/support/expresso/bin/expresso b/node_modules/mongoose/support/expresso/bin/expresso new file mode 100755 index 0000000..fd98fff --- /dev/null +++ b/node_modules/mongoose/support/expresso/bin/expresso @@ -0,0 +1,874 @@ +#!/usr/bin/env node + +/* + * Expresso + * Copyright(c) TJ Holowaychuk + * (MIT Licensed) + */ + +/** + * Module dependencies. + */ + +var assert = require('assert'), + childProcess = require('child_process'), + http = require('http'), + path = require('path'), + sys = require('sys'), + cwd = process.cwd(), + fs = require('fs'), + defer; + +/** + * Expresso version. + */ + +var version = '0.7.2'; + +/** + * Failure count. + */ + +var failures = 0; + + +/** + * Number of tests executed. + */ + +var testcount = 0; + +/** + * Whitelist of tests to run. + */ + +var only = []; + +/** + * Boring output. + */ + +var boring = false; + +/** + * Growl notifications. + */ + +var growl = false; + +/** + * Server port. + */ + +var port = 5555; + +/** + * Execute serially. + */ + +var serial = false; + +/** + * Default timeout. + */ + +var timeout = 2000; + +/** + * Quiet output. + */ + +var quiet = false; + +/** + * Usage documentation. + */ + +var usage = '' + + '[bold]{Usage}: expresso [options] ' + + '\n' + + '\n[bold]{Options}:' + + '\n -g, --growl Enable growl notifications' + + '\n -c, --coverage Generate and report test coverage' + + '\n -q, --quiet Suppress coverage report if 100%' + + '\n -t, --timeout MS Timeout in milliseconds, defaults to 2000' + + '\n -r, --require PATH Require the given module path' + + '\n -o, --only TESTS Execute only the comma sperated TESTS (can be set several times)' + + '\n -I, --include PATH Unshift the given path to require.paths' + + '\n -p, --port NUM Port number for test servers, starts at 5555' + + '\n -s, --serial Execute tests serially' + + '\n -b, --boring Suppress ansi-escape colors' + + '\n -v, --version Output version number' + + '\n -h, --help Display help information' + + '\n'; + +// Parse arguments + +var files = [], + args = process.argv.slice(2); + +while (args.length) { + var arg = args.shift(); + switch (arg) { + case '-h': + case '--help': + print(usage + '\n'); + process.exit(1); + break; + case '-v': + case '--version': + sys.puts(version); + process.exit(1); + break; + case '-i': + case '-I': + case '--include': + if (arg = args.shift()) { + require.paths.unshift(arg); + } else { + throw new Error('--include requires a path'); + } + break; + case '-o': + case '--only': + if (arg = args.shift()) { + only = only.concat(arg.split(/ *, */)); + } else { + throw new Error('--only requires comma-separated test names'); + } + break; + case '-p': + case '--port': + if (arg = args.shift()) { + port = parseInt(arg, 10); + } else { + throw new Error('--port requires a number'); + } + break; + case '-r': + case '--require': + if (arg = args.shift()) { + require(arg); + } else { + throw new Error('--require requires a path'); + } + break; + case '-t': + case '--timeout': + if (arg = args.shift()) { + timeout = parseInt(arg, 10); + } else { + throw new Error('--timeout requires an argument'); + } + break; + case '-c': + case '--cov': + case '--coverage': + defer = true; + childProcess.exec('rm -fr lib-cov && node-jscoverage lib lib-cov', function(err){ + if (err) throw err; + require.paths.unshift('lib-cov'); + run(files); + }) + break; + case '-q': + case '--quiet': + quiet = true; + break; + case '-b': + case '--boring': + boring = true; + break; + case '-g': + case '--growl': + growl = true; + break; + case '-s': + case '--serial': + serial = true; + break; + default: + if (/\.js$/.test(arg)) { + files.push(arg); + } + break; + } +} + +/** + * Colorized sys.error(). + * + * @param {String} str + */ + +function print(str){ + sys.error(colorize(str)); +} + +/** + * Colorize the given string using ansi-escape sequences. + * Disabled when --boring is set. + * + * @param {String} str + * @return {String} + */ + +function colorize(str){ + var colors = { bold: 1, red: 31, green: 32, yellow: 33 }; + return str.replace(/\[(\w+)\]\{([^]*?)\}/g, function(_, color, str){ + return boring + ? str + : '\x1B[' + colors[color] + 'm' + str + '\x1B[0m'; + }); +} + +// Alias deepEqual as eql for complex equality + +assert.eql = assert.deepEqual; + +/** + * Assert that `val` is null. + * + * @param {Mixed} val + * @param {String} msg + */ + +assert.isNull = function(val, msg) { + assert.strictEqual(null, val, msg); +}; + +/** + * Assert that `val` is not null. + * + * @param {Mixed} val + * @param {String} msg + */ + +assert.isNotNull = function(val, msg) { + assert.notStrictEqual(null, val, msg); +}; + +/** + * Assert that `val` is undefined. + * + * @param {Mixed} val + * @param {String} msg + */ + +assert.isUndefined = function(val, msg) { + assert.strictEqual(undefined, val, msg); +}; + +/** + * Assert that `val` is not undefined. + * + * @param {Mixed} val + * @param {String} msg + */ + +assert.isDefined = function(val, msg) { + assert.notStrictEqual(undefined, val, msg); +}; + +/** + * Assert that `obj` is `type`. + * + * @param {Mixed} obj + * @param {String} type + * @api public + */ + +assert.type = function(obj, type, msg){ + var real = typeof obj; + msg = msg || 'typeof ' + sys.inspect(obj) + ' is ' + real + ', expected ' + type; + assert.ok(type === real, msg); +}; + +/** + * Assert that `str` matches `regexp`. + * + * @param {String} str + * @param {RegExp} regexp + * @param {String} msg + */ + +assert.match = function(str, regexp, msg) { + msg = msg || sys.inspect(str) + ' does not match ' + sys.inspect(regexp); + assert.ok(regexp.test(str), msg); +}; + +/** + * Assert that `val` is within `obj`. + * + * Examples: + * + * assert.includes('foobar', 'bar'); + * assert.includes(['foo', 'bar'], 'foo'); + * + * @param {String|Array} obj + * @param {Mixed} val + * @param {String} msg + */ + +assert.includes = function(obj, val, msg) { + msg = msg || sys.inspect(obj) + ' does not include ' + sys.inspect(val); + assert.ok(obj.indexOf(val) >= 0, msg); +}; + +/** + * Assert length of `val` is `n`. + * + * @param {Mixed} val + * @param {Number} n + * @param {String} msg + */ + +assert.length = function(val, n, msg) { + msg = msg || sys.inspect(val) + ' has length of ' + val.length + ', expected ' + n; + assert.equal(n, val.length, msg); +}; + +/** + * Assert response from `server` with + * the given `req` object and `res` assertions object. + * + * @param {Server} server + * @param {Object} req + * @param {Object|Function} res + * @param {String} msg + */ + +assert.response = function(server, req, res, msg){ + // Check that the server is ready or defer + if (!server.fd) { + if (!('__deferred' in server)) { + server.__deferred = []; + } + server.__deferred.push(arguments); + if (!server.__started) { + server.listen(server.__port = port++, '127.0.0.1', function(){ + if (server.__deferred) { + process.nextTick(function(){ + server.__deferred.forEach(function(args){ + assert.response.apply(assert, args); + }); + }); + } + }); + server.__started = true; + } + return; + } + + // Callback as third or fourth arg + var callback = typeof res === 'function' + ? res + : typeof msg === 'function' + ? msg + : function(){}; + + // Default messate to test title + if (typeof msg === 'function') msg = null; + msg = msg || assert.testTitle; + msg += '. '; + + // Pending responses + server.__pending = server.__pending || 0; + server.__pending++; + + // Create client + if (!server.fd) { + server.listen(server.__port = port++, '127.0.0.1', issue); + } else { + issue(); + } + + function issue(){ + if (!server.client) + server.client = http.createClient(server.__port); + + // Issue request + var timer, + client = server.client, + method = req.method || 'GET', + status = res.status || res.statusCode, + data = req.data || req.body, + requestTimeout = req.timeout || 0; + + var request = client.request(method, req.url, req.headers); + + // Timeout + if (requestTimeout) { + timer = setTimeout(function(){ + --server.__pending || server.close(); + delete req.timeout; + assert.fail(msg + 'Request timed out after ' + requestTimeout + 'ms.'); + }, requestTimeout); + } + + if (data) request.write(data); + request.addListener('response', function(response){ + response.body = ''; + response.setEncoding('utf8'); + response.addListener('data', function(chunk){ response.body += chunk; }); + response.addListener('end', function(){ + --server.__pending || server.close(); + if (timer) clearTimeout(timer); + + // Assert response body + if (res.body !== undefined) { + var eql = res.body instanceof RegExp + ? res.body.test(response.body) + : res.body === response.body; + assert.ok( + eql, + msg + 'Invalid response body.\n' + + ' Expected: ' + sys.inspect(res.body) + '\n' + + ' Got: ' + sys.inspect(response.body) + ); + } + + // Assert response status + if (typeof status === 'number') { + assert.equal( + response.statusCode, + status, + msg + colorize('Invalid response status code.\n' + + ' Expected: [green]{' + status + '}\n' + + ' Got: [red]{' + response.statusCode + '}') + ); + } + + // Assert response headers + if (res.headers) { + var keys = Object.keys(res.headers); + for (var i = 0, len = keys.length; i < len; ++i) { + var name = keys[i], + actual = response.headers[name.toLowerCase()], + expected = res.headers[name], + eql = expected instanceof RegExp + ? expected.test(actual) + : expected == actual; + assert.ok( + eql, + msg + colorize('Invalid response header [bold]{' + name + '}.\n' + + ' Expected: [green]{' + expected + '}\n' + + ' Got: [red]{' + actual + '}') + ); + } + } + + // Callback + callback(response); + }); + }); + request.end(); + } +}; + +/** + * Pad the given string to the maximum width provided. + * + * @param {String} str + * @param {Number} width + * @return {String} + */ + +function lpad(str, width) { + str = String(str); + var n = width - str.length; + if (n < 1) return str; + while (n--) str = ' ' + str; + return str; +} + +/** + * Pad the given string to the maximum width provided. + * + * @param {String} str + * @param {Number} width + * @return {String} + */ + +function rpad(str, width) { + str = String(str); + var n = width - str.length; + if (n < 1) return str; + while (n--) str = str + ' '; + return str; +} + +/** + * Report test coverage. + * + * @param {Object} cov + */ + +function reportCoverage(cov) { + // Stats + print('\n [bold]{Test Coverage}\n'); + var sep = ' +------------------------------------------+----------+------+------+--------+', + lastSep = ' +----------+------+------+--------+'; + sys.puts(sep); + sys.puts(' | filename | coverage | LOC | SLOC | missed |'); + sys.puts(sep); + for (var name in cov) { + var file = cov[name]; + if (Array.isArray(file)) { + sys.print(' | ' + rpad(name, 40)); + sys.print(' | ' + lpad(file.coverage.toFixed(2), 8)); + sys.print(' | ' + lpad(file.LOC, 4)); + sys.print(' | ' + lpad(file.SLOC, 4)); + sys.print(' | ' + lpad(file.totalMisses, 6)); + sys.print(' |\n'); + } + } + sys.puts(sep); + sys.print(' ' + rpad('', 40)); + sys.print(' | ' + lpad(cov.coverage.toFixed(2), 8)); + sys.print(' | ' + lpad(cov.LOC, 4)); + sys.print(' | ' + lpad(cov.SLOC, 4)); + sys.print(' | ' + lpad(cov.totalMisses, 6)); + sys.print(' |\n'); + sys.puts(lastSep); + // Source + for (var name in cov) { + if (name.match(/\.js$/)) { + var file = cov[name]; + if ((file.coverage < 100) || !quiet) { + print('\n [bold]{' + name + '}:'); + print(file.source); + sys.print('\n'); + } + } + } +} + +/** + * Populate code coverage data. + * + * @param {Object} cov + */ + +function populateCoverage(cov) { + cov.LOC = + cov.SLOC = + cov.totalFiles = + cov.totalHits = + cov.totalMisses = + cov.coverage = 0; + for (var name in cov) { + var file = cov[name]; + if (Array.isArray(file)) { + // Stats + ++cov.totalFiles; + cov.totalHits += file.totalHits = coverage(file, true); + cov.totalMisses += file.totalMisses = coverage(file, false); + file.totalLines = file.totalHits + file.totalMisses; + cov.SLOC += file.SLOC = file.totalLines; + if (!file.source) file.source = []; + cov.LOC += file.LOC = file.source.length; + file.coverage = (file.totalHits / file.totalLines) * 100; + // Source + var width = file.source.length.toString().length; + file.source = file.source.map(function(line, i){ + ++i; + var hits = file[i] === 0 ? 0 : (file[i] || ' '); + if (!boring) { + if (hits === 0) { + hits = '\x1b[31m' + hits + '\x1b[0m'; + line = '\x1b[41m' + line + '\x1b[0m'; + } else { + hits = '\x1b[32m' + hits + '\x1b[0m'; + } + } + return '\n ' + lpad(i, width) + ' | ' + hits + ' | ' + line; + }).join(''); + } + } + cov.coverage = (cov.totalHits / cov.SLOC) * 100; +} + +/** + * Total coverage for the given file data. + * + * @param {Array} data + * @return {Type} + */ + +function coverage(data, val) { + var n = 0; + for (var i = 0, len = data.length; i < len; ++i) { + if (data[i] !== undefined && data[i] == val) ++n; + } + return n; +} + +/** + * Test if all files have 100% coverage + * + * @param {Object} cov + * @return {Boolean} + */ + +function hasFullCoverage(cov) { + for (var name in cov) { + var file = cov[name]; + if (file instanceof Array) { + if (file.coverage !== 100) { + return false; + } + } + } + return true; +} + +/** + * Run the given test `files`, or try _test/*_. + * + * @param {Array} files + */ + +function run(files) { + cursor(false); + if (!files.length) { + try { + files = fs.readdirSync('test').map(function(file){ + return 'test/' + file; + }); + } catch (err) { + print('\n failed to load tests in [bold]{./test}\n'); + ++failures; + process.exit(1); + } + } + runFiles(files); +} + +/** + * Show the cursor when `show` is true, otherwise hide it. + * + * @param {Boolean} show + */ + +function cursor(show) { + if (show) { + sys.print('\x1b[?25h'); + } else { + sys.print('\x1b[?25l'); + } +} + +/** + * Run the given test `files`. + * + * @param {Array} files + */ + +function runFiles(files) { + if (serial) { + (function next(){ + if (files.length) { + runFile(files.shift(), next); + } + })(); + } else { + files.forEach(runFile); + } +} + +/** + * Run tests for the given `file`, callback `fn()` when finished. + * + * @param {String} file + * @param {Function} fn + */ + +function runFile(file, fn) { + if (file.match(/\.js$/)) { + var title = path.basename(file), + file = path.join(cwd, file), + mod = require(file.replace(/\.js$/, '')); + (function check(){ + var len = Object.keys(mod).length; + if (len) { + runSuite(title, mod, fn); + } else { + setTimeout(check, 20); + } + })(); + } +} + +/** + * Report `err` for the given `test` and `suite`. + * + * @param {String} suite + * @param {String} test + * @param {Error} err + */ + +function error(suite, test, err) { + ++failures; + var name = err.name, + stack = err.stack.replace(err.name, ''), + label = test === 'uncaught' + ? test + : suite + ' ' + test; + print('\n [bold]{' + label + '}: [red]{' + name + '}' + stack + '\n'); +} + +/** + * Run the given tests, callback `fn()` when finished. + * + * @param {String} title + * @param {Object} tests + * @param {Function} fn + */ + +var dots = 0; +function runSuite(title, tests, fn) { + // Keys + var keys = only.length + ? only.slice(0) + : Object.keys(tests); + + // Setup + var setup = tests.setup || function(fn){ fn(); }; + + // Iterate tests + (function next(){ + if (keys.length) { + var key, + test = tests[key = keys.shift()]; + // Non-tests + if (key === 'setup') return next(); + + // Run test + if (test) { + try { + ++testcount; + assert.testTitle = key; + if (serial) { + sys.print('.'); + if (++dots % 25 === 0) sys.print('\n'); + setup(function(){ + if (test.length < 1) { + test(); + next(); + } else { + var id = setTimeout(function(){ + throw new Error("'" + key + "' timed out"); + }, timeout); + test(function(){ + clearTimeout(id); + next(); + }); + } + }); + } else { + test(function(fn){ + addBeforeExit(function(){ + try { + fn(); + } catch (err) { + error(title, key, err); + } + }); + }); + } + } catch (err) { + error(title, key, err); + } + } + if (!serial) next(); + } else if (serial) { + fn(); + } + })(); +} + +/** + * Adds before exit handlers with only one event handler + * + * @param {Function} callback + * @api private + */ + +var beforeExitHandlers = []; + +function addBeforeExit (fn) { + if (!beforeExitHandlers.length) + process.on('beforeExit', function () { + for (var i = 0, l = beforeExitHandlers.length; i < l; i++) + beforeExitHandlers[i](); + }); + beforeExitHandlers.push(fn); +}; + +/** + * Report exceptions. + */ + +function report() { + cursor(true); + process.emit('beforeExit'); + if (failures) { + print('\n [bold]{Failures}: [red]{' + failures + '}\n\n'); + notify('Failures: ' + failures); + } else { + if (serial) print(''); + print('\n [green]{100%} ' + testcount + ' tests\n'); + notify('100% ok'); + } + if (typeof _$jscoverage === 'object') { + populateCoverage(_$jscoverage); + if (!hasFullCoverage(_$jscoverage) || !quiet) { + reportCoverage(_$jscoverage); + } + } +} + +/** + * Growl notify the given `msg`. + * + * @param {String} msg + */ + +function notify(msg) { + if (growl) { + childProcess.exec('growlnotify -name Expresso -m "' + msg + '"'); + } +} + +// Report uncaught exceptions + +process.addListener('uncaughtException', function(err){ + error('uncaught', 'uncaught', err); +}); + +// Show cursor + +['INT', 'TERM', 'QUIT'].forEach(function(sig){ + process.addListener('SIG' + sig, function(){ + cursor(true); + process.exit(1); + }); +}); + +// Report test coverage when available +// and emit "beforeExit" event to perform +// final assertions + +var orig = process.emit; +process.emit = function(event){ + if (event === 'exit') { + report(); + process.reallyExit(failures); + } + orig.apply(this, arguments); +}; + +// Run test files + +if (!defer) run(files); diff --git a/node_modules/mongoose/support/expresso/docs/api.html b/node_modules/mongoose/support/expresso/docs/api.html new file mode 100644 index 0000000..7b8fb2b --- /dev/null +++ b/node_modules/mongoose/support/expresso/docs/api.html @@ -0,0 +1,1080 @@ +
    Fork me on GitHub + + Expresso + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Expresso

    Insanely fast TDD framework for node featuring code coverage reporting.

    expresso

    bin/expresso
    +

    !/usr/bin/env node

    +
    +
    
    + * Expresso
    + * Copyright(c) TJ Holowaychuk &lt;tj@vision-media.ca&gt;
    + * (MIT Licensed)
    + 
    +
    +

    Module dependencies. +

    +
    +
    var assert = require('assert'),
    +    childProcess = require('child_process'),
    +    http = require('http'),
    +    path = require('path'),
    +    sys = require('sys'),
    +    cwd = process.cwd(),
    +    fs = require('fs'),
    +    defer;
    +
    +

    Expresso version. +

    +
    +
    var version = '0.6.4';
    +
    +

    Failure count. +

    +
    +
    var failures = 0;
    +
    +

    Number of tests executed. +

    +
    +
    var testcount = 0;
    +
    +

    Whitelist of tests to run. +

    +
    +
    var only = [];
    +
    +

    Boring output. +

    +
    +
    var boring = false;
    +
    +

    Growl notifications. +

    +
    +
    var growl = false;
    +
    +

    Server port. +

    +
    +
    var port = 5555;
    +
    +

    Watch mode. +

    +
    +
    var watch = false;
    +
    +

    Execute serially. +

    +
    +
    var serial = false;
    +
    +

    Default timeout. +

    +
    +
    var timeout = 2000;
    +
    +

    Usage documentation. +

    +
    +
    var usage = ''
    +    + '[bold]{Usage}: expresso [options] <file ...>'
    +    + '\n'
    +    + '\n[bold]{Options}:'
    +    + '\n  -w, --watch          Watch for modifications and re-execute tests'
    +    + '\n  -g, --growl          Enable growl notifications'
    +    + '\n  -c, --coverage       Generate and report test coverage'
    +    + '\n  -t, --timeout MS     Timeout in milliseconds, defaults to 2000'
    +    + '\n  -r, --require PATH   Require the given module path'
    +    + '\n  -o, --only TESTS     Execute only the comma sperated TESTS (can be set several times)'
    +    + '\n  -I, --include PATH   Unshift the given path to require.paths'
    +    + '\n  -p, --port NUM       Port number for test servers, starts at 5555'
    +    + '\n  -s, --serial         Execute tests serially'
    +    + '\n  -b, --boring         Suppress ansi-escape colors'
    +    + '\n  -v, --version        Output version number'
    +    + '\n  -h, --help           Display help information'
    +    + '\n';
    +
    +// Parse arguments
    +
    +var files = [],
    +    args = process.argv.slice(2);
    +
    +while (args.length) {
    +    var arg = args.shift();
    +    switch (arg) {
    +        case '-h':
    +        case '--help':
    +            print(usage + '\n');
    +            process.exit(1);
    +            break;
    +        case '-v':
    +        case '--version':
    +            sys.puts(version);
    +            process.exit(1);
    +            break;
    +        case '-i':
    +        case '-I':
    +        case '--include':
    +            if (arg = args.shift()) {
    +                require.paths.unshift(arg);
    +            } else {
    +                throw new Error('--include requires a path');
    +            }
    +            break;
    +        case '-o':
    +        case '--only':
    +            if (arg = args.shift()) {
    +                only = only.concat(arg.split(/ *, */));
    +            } else {
    +                throw new Error('--only requires comma-separated test names');
    +            }
    +            break;
    +        case '-p':
    +        case '--port':
    +            if (arg = args.shift()) {
    +                port = parseInt(arg, 10);
    +            } else {
    +                throw new Error('--port requires a number');
    +            }
    +            break;
    +        case '-r':
    +        case '--require':
    +            if (arg = args.shift()) {
    +                require(arg);
    +            } else {
    +                throw new Error('--require requires a path');
    +            }
    +            break;
    +        case '-t':
    +        case '--timeout':
    +          if (arg = args.shift()) {
    +            timeout = parseInt(arg, 10);
    +          } else {
    +            throw new Error('--timeout requires an argument');
    +          }
    +          break;
    +        case '-c':
    +        case '--cov':
    +        case '--coverage':
    +            defer = true;
    +            childProcess.exec('rm -fr lib-cov && node-jscoverage lib lib-cov', function(err){
    +                if (err) throw err;
    +                require.paths.unshift('lib-cov');
    +                run(files);
    +            })
    +            break;
    +        case '-b':
    +        case '--boring':
    +        	boring = true;
    +        	break;
    +        case '-w':
    +        case '--watch':
    +            watch = true;
    +            break;
    +        case '-g':
    +        case '--growl':
    +            growl = true;
    +            break;
    +        case '-s':
    +        case '--serial':
    +            serial = true;
    +            break;
    +        default:
    +            if (/\.js$/.test(arg)) {
    +                files.push(arg);
    +            }
    +            break;
    +    }
    +}
    +
    +

    Colorized sys.error().

    + +

    + +
    • param: String str

    +
    +
    function print(str){
    +    sys.error(colorize(str));
    +}
    +
    +

    Colorize the given string using ansi-escape sequences. +Disabled when --boring is set.

    + +

    + +
    • param: String str

    • return: String

    +
    +
    function colorize(str){
    +    var colors = { bold: 1, red: 31, green: 32, yellow: 33 };
    +    return str.replace(/\[(\w+)\]\{([^]*?)\}/g, function(_, color, str){
    +        return boring
    +            ? str
    +            : '\x1B[' + colors[color] + 'm' + str + '\x1B[0m';
    +    });
    +}
    +
    +// Alias deepEqual as eql for complex equality
    +
    +assert.eql = assert.deepEqual;
    +
    +

    Assert that val is null.

    + +

    + +
    • param: Mixed val

    • param: String msg

    +
    +
    assert.isNull = function(val, msg) {
    +    assert.strictEqual(null, val, msg);
    +};
    +
    +

    Assert that val is not null.

    + +

    + +
    • param: Mixed val

    • param: String msg

    +
    +
    assert.isNotNull = function(val, msg) {
    +    assert.notStrictEqual(null, val, msg);
    +};
    +
    +

    Assert that val is undefined.

    + +

    + +
    • param: Mixed val

    • param: String msg

    +
    +
    assert.isUndefined = function(val, msg) {
    +    assert.strictEqual(undefined, val, msg);
    +};
    +
    +

    Assert that val is not undefined.

    + +

    + +
    • param: Mixed val

    • param: String msg

    +
    +
    assert.isDefined = function(val, msg) {
    +    assert.notStrictEqual(undefined, val, msg);
    +};
    +
    +

    Assert that obj is type.

    + +

    + +
    • param: Mixed obj

    • param: String type

    • api: public

    +
    +
    assert.type = function(obj, type, msg){
    +    var real = typeof obj;
    +    msg = msg || 'typeof ' + sys.inspect(obj) + ' is ' + real + ', expected ' + type;
    +    assert.ok(type === real, msg);
    +};
    +
    +

    Assert that str matches regexp.

    + +

    + +
    • param: String str

    • param: RegExp regexp

    • param: String msg

    +
    +
    assert.match = function(str, regexp, msg) {
    +    msg = msg || sys.inspect(str) + ' does not match ' + sys.inspect(regexp);
    +    assert.ok(regexp.test(str), msg);
    +};
    +
    +

    Assert that val is within obj.

    + +

    Examples

    + +

    assert.includes('foobar', 'bar'); + assert.includes(['foo', 'bar'], 'foo');

    + +

    + +
    • param: String | Array obj

    • param: Mixed val

    • param: String msg

    +
    +
    assert.includes = function(obj, val, msg) {
    +    msg = msg || sys.inspect(obj) + ' does not include ' + sys.inspect(val);
    +    assert.ok(obj.indexOf(val) &gt;= 0, msg);
    +};
    +
    +

    Assert length of val is n.

    + +

    + +
    • param: Mixed val

    • param: Number n

    • param: String msg

    +
    +
    assert.length = function(val, n, msg) {
    +    msg = msg || sys.inspect(val) + ' has length of ' + val.length + ', expected ' + n;
    +    assert.equal(n, val.length, msg);
    +};
    +
    +

    Assert response from server with +the given req object and res assertions object.

    + +

    + +
    • param: Server server

    • param: Object req

    • param: Object | Function res

    • param: String msg

    +
    +
    assert.response = function(server, req, res, msg){
    +    // Callback as third or fourth arg
    +    var callback = typeof res === 'function'
    +        ? res
    +        : typeof msg === 'function'
    +            ? msg
    +            : function(){};
    +
    +    // Default messate to test title
    +    if (typeof msg === 'function') msg = null;
    +    msg = msg || assert.testTitle;
    +    msg += '. ';
    +
    +    // Pending responses
    +    server.__pending = server.__pending || 0;
    +    server.__pending++;
    +
    +    // Create client
    +    if (!server.fd) {
    +        server.listen(server.__port = port++, '127.0.0.1');
    +        server.client = http.createClient(server.__port);
    +    }
    +
    +    // Issue request
    +    var timer,
    +        client = server.client,
    +        method = req.method || 'GET',
    +        status = res.status || res.statusCode,
    +        data = req.data || req.body,
    +        requestTimeout = req.timeout || 0;
    +
    +    var request = client.request(method, req.url, req.headers);
    +
    +    // Timeout
    +    if (requestTimeout) {
    +        timer = setTimeout(function(){
    +            --server.__pending || server.close();
    +            delete req.timeout;
    +            assert.fail(msg + 'Request timed out after ' + requestTimeout + 'ms.');
    +        }, requestTimeout);
    +    }
    +
    +    if (data) request.write(data);
    +    request.addListener('response', function(response){
    +        response.body = '';
    +        response.setEncoding('utf8');
    +        response.addListener('data', function(chunk){ response.body += chunk; });
    +        response.addListener('end', function(){
    +            --server.__pending || server.close();
    +            if (timer) clearTimeout(timer);
    +
    +            // Assert response body
    +            if (res.body !== undefined) {
    +                var eql = res.body instanceof RegExp
    +                  ? res.body.test(response.body)
    +                  : res.body === response.body;
    +                assert.ok(
    +                    eql,
    +                    msg + 'Invalid response body.\n'
    +                        + '    Expected: ' + sys.inspect(res.body) + '\n'
    +                        + '    Got: ' + sys.inspect(response.body)
    +                );
    +            }
    +
    +            // Assert response status
    +            if (typeof status === 'number') {
    +                assert.equal(
    +                    response.statusCode,
    +                    status,
    +                    msg + colorize('Invalid response status code.\n'
    +                        + '    Expected: [green]{' + status + '}\n'
    +                        + '    Got: [red]{' + response.statusCode + '}')
    +                );
    +            }
    +
    +            // Assert response headers
    +            if (res.headers) {
    +                var keys = Object.keys(res.headers);
    +                for (var i = 0, len = keys.length; i &lt; len; ++i) {
    +                    var name = keys[i],
    +                        actual = response.headers[name.toLowerCase()],
    +                        expected = res.headers[name],
    +                        eql = expected instanceof RegExp
    +                          ? expected.test(actual)
    +                          : expected == actual;
    +                    assert.ok(
    +                        eql,
    +                        msg + colorize('Invalid response header [bold]{' + name + '}.\n'
    +                            + '    Expected: [green]{' + expected + '}\n'
    +                            + '    Got: [red]{' + actual + '}')
    +                    );
    +                }
    +            }
    +
    +            // Callback
    +            callback(response);
    +        });
    +    });
    +    request.end();
    +};
    +
    +

    Pad the given string to the maximum width provided.

    + +

    + +
    • param: String str

    • param: Number width

    • return: String

    +
    +
    function lpad(str, width) {
    +    str = String(str);
    +    var n = width - str.length;
    +    if (n &lt; 1) return str;
    +    while (n--) str = ' ' + str;
    +    return str;
    +}
    +
    +

    Pad the given string to the maximum width provided.

    + +

    + +
    • param: String str

    • param: Number width

    • return: String

    +
    +
    function rpad(str, width) {
    +    str = String(str);
    +    var n = width - str.length;
    +    if (n &lt; 1) return str;
    +    while (n--) str = str + ' ';
    +    return str;
    +}
    +
    +

    Report test coverage.

    + +

    + +
    • param: Object cov

    +
    +
    function reportCoverage(cov) {
    +    populateCoverage(cov);
    +    // Stats
    +    print('\n   [bold]{Test Coverage}\n');
    +    var sep = '   +------------------------------------------+----------+------+------+--------+',
    +        lastSep = '                                              +----------+------+------+--------+';
    +    sys.puts(sep);
    +    sys.puts('   | filename                                 | coverage | LOC  | SLOC | missed |');
    +    sys.puts(sep);
    +    for (var name in cov) {
    +        var file = cov[name];
    +        if (Array.isArray(file)) {
    +            sys.print('   | ' + rpad(name, 40));
    +            sys.print(' | ' + lpad(file.coverage.toFixed(2), 8));
    +            sys.print(' | ' + lpad(file.LOC, 4));
    +            sys.print(' | ' + lpad(file.SLOC, 4));
    +            sys.print(' | ' + lpad(file.totalMisses, 6));
    +            sys.print(' |\n');
    +        }
    +    }
    +    sys.puts(sep);
    +    sys.print('     ' + rpad('', 40));
    +    sys.print(' | ' + lpad(cov.coverage.toFixed(2), 8));
    +    sys.print(' | ' + lpad(cov.LOC, 4));
    +    sys.print(' | ' + lpad(cov.SLOC, 4));
    +    sys.print(' | ' + lpad(cov.totalMisses, 6));
    +    sys.print(' |\n');
    +    sys.puts(lastSep);
    +    // Source
    +    for (var name in cov) {
    +        if (name.match(/\.js$/)) {
    +            var file = cov[name];
    +            print('\n   [bold]{' + name + '}:');
    +            print(file.source);
    +            sys.print('\n');
    +        }
    +    }
    +}
    +
    +

    Populate code coverage data.

    + +

    + +
    • param: Object cov

    +
    +
    function populateCoverage(cov) {
    +    cov.LOC = 
    +    cov.SLOC =
    +    cov.totalFiles =
    +    cov.totalHits =
    +    cov.totalMisses = 
    +    cov.coverage = 0;
    +    for (var name in cov) {
    +        var file = cov[name];
    +        if (Array.isArray(file)) {
    +            // Stats
    +            ++cov.totalFiles;
    +            cov.totalHits += file.totalHits = coverage(file, true);
    +            cov.totalMisses += file.totalMisses = coverage(file, false);
    +            file.totalLines = file.totalHits + file.totalMisses;
    +            cov.SLOC += file.SLOC = file.totalLines;
    +            if (!file.source) file.source = [];
    +            cov.LOC += file.LOC = file.source.length;
    +            file.coverage = (file.totalHits / file.totalLines) * 100;
    +            // Source
    +            var width = file.source.length.toString().length;
    +            file.source = file.source.map(function(line, i){
    +                ++i;
    +                var hits = file[i] === 0 ? 0 : (file[i] || ' ');
    +                if (!boring) {
    +                    if (hits === 0) {
    +                        hits = '\x1b[31m' + hits + '\x1b[0m';
    +                        line = '\x1b[41m' + line + '\x1b[0m';
    +                    } else {
    +                        hits = '\x1b[32m' + hits + '\x1b[0m';
    +                    }
    +                }
    +                return '\n     ' + lpad(i, width) + ' | ' + hits + ' | ' + line;
    +            }).join('');
    +        }
    +    }
    +    cov.coverage = (cov.totalHits / cov.SLOC) * 100;
    +}
    +
    +

    Total coverage for the given file data.

    + +

    + +
    • param: Array data

    • return: Type

    +
    +
    function coverage(data, val) {
    +    var n = 0;
    +    for (var i = 0, len = data.length; i &lt; len; ++i) {
    +        if (data[i] !== undefined &amp;&amp; data[i] == val) ++n;
    +    }
    +    return n;  
    +}
    +
    +

    Run the given test files, or try test/*.

    + +

    + +
    • param: Array files

    +
    +
    function run(files) {
    +    if (!files.length) {
    +        try {
    +            files = fs.readdirSync('test').map(function(file){
    +                return 'test/' + file;
    +            });
    +        } catch (err) {
    +            print('\n  failed to load tests in [bold]{./test}\n');
    +            ++failures;
    +            process.exit(1);
    +        }
    +    }
    +    if (watch) watchFiles(files);
    +    runFiles(files);
    +}
    +
    +

    Show the cursor when show is true, otherwise hide it.

    + +

    + +
    • param: Boolean show

    +
    +
    function cursor(show) {
    +    if (show) {
    +        sys.print('\x1b[?25h');
    +    } else {
    +        sys.print('\x1b[?25l');
    +    }
    +}
    +
    +

    Run the given test files.

    + +

    + +
    • param: Array files

    +
    +
    function runFiles(files) {
    +    if (serial) {
    +        (function next(){
    +            if (files.length) {
    +                runFile(files.shift(), next);
    +            }
    +        })();
    +    } else {
    +      files.forEach(runFile);
    +    }
    +}
    +
    +

    Run tests for the given file, callback fn() when finished.

    + +

    + +
    • param: String file

    • param: Function fn

    +
    +
    function runFile(file, fn) {
    +    if (file.match(/\.js$/)) {
    +        var title = path.basename(file),
    +            file = path.join(cwd, file),
    +            mod = require(file.replace(/\.js$/, ''));
    +        (function check(){
    +           var len = Object.keys(mod).length;
    +           if (len) {
    +               runSuite(title, mod, fn);
    +           } else {
    +               setTimeout(check, 20);
    +           }
    +        })();
    +    }
    +}
    +
    +

    Clear the module cache for the given file.

    + +

    + +
    • param: String file

    +
    +
    function clearCache(file) {
    +    var keys = Object.keys(module.moduleCache);
    +    for (var i = 0, len = keys.length; i &lt; len; ++i) {
    +        var key = keys[i];
    +        if (key.indexOf(file) === key.length - file.length) {
    +            delete module.moduleCache[key];
    +        }
    +    }
    +}
    +
    +

    Watch the given files for changes.

    + +

    + +
    • param: Array files

    +
    +
    function watchFiles(files) {
    +    var p = 0,
    +        c = ['▫   ', '▫▫  ', '▫▫▫ ', ' ▫▫▫',
    +             '  ▫▫', '   ▫', '   ▫', '  ▫▫',
    +             '▫▫▫ ', '▫▫  ', '▫   '],
    +        l = c.length;
    +    cursor(false);
    +    setInterval(function(){
    +        sys.print(colorize('  [green]{' + c[p++ % l] + '} watching\r'));
    +    }, 100);
    +    files.forEach(function(file){
    +        fs.watchFile(file, { interval: 100 }, function(curr, prev){
    +            if (curr.mtime &gt; prev.mtime) {
    +                print('  [yellow]{◦} ' + file);
    +                clearCache(file);
    +                runFile(file);
    +            }
    +        });
    +    });
    +}
    +
    +

    Report err for the given test and suite.

    + +

    + +
    • param: String suite

    • param: String test

    • param: Error err

    +
    +
    function error(suite, test, err) {
    +    ++failures;
    +    var name = err.name,
    +        stack = err.stack.replace(err.name, ''),
    +        label = test === 'uncaught'
    +            ? test
    +            : suite + ' ' + test;
    +    print('\n   [bold]{' + label + '}: [red]{' + name + '}' + stack + '\n');
    +    if (watch) notify(label + ' failed');
    +}
    +
    +

    Run the given tests, callback fn() when finished.

    + +

    + +
    • param: String title

    • param: Object tests

    • param: Function fn

    +
    +
    var dots = 0;
    +function runSuite(title, tests, fn) {
    +    // Keys
    +    var keys = only.length
    +        ? only.slice(0)
    +        : Object.keys(tests);
    +
    +    // Setup
    +    var setup = tests.setup || function(fn){ fn(); };
    +
    +    // Iterate tests
    +    (function next(){
    +        if (keys.length) {
    +            var key,
    +                test = tests[key = keys.shift()];
    +            // Non-tests
    +            if (key === 'setup') return next();
    +
    +            // Run test
    +            if (test) {
    +                try {
    +                    ++testcount;
    +                    assert.testTitle = key;
    +                    if (serial) {
    +                        if (!watch) {
    +                            sys.print('.');
    +                            if (++dots % 25 === 0) sys.print('\n');
    +                        }
    +                        setup(function(){
    +                            if (test.length &lt; 1) {
    +                                test();
    +                                next();
    +                            } else {
    +                                var id = setTimeout(function(){
    +                                    throw new Error(&quot;'" + key + "' timed out&quot;);
    +                                }, timeout);
    +                                test(function(){
    +                                    clearTimeout(id);
    +                                    next();
    +                                });
    +                            } 
    +                        });
    +                    } else {
    +                        test(function(fn){
    +                            process.addListener('beforeExit', function(){
    +                                try {
    +                                    fn();
    +                                } catch (err) {
    +                                    error(title, key, err);
    +                                }
    +                            });
    +                        });
    +                    }
    +                } catch (err) {
    +                    error(title, key, err);
    +                }
    +            }
    +            if (!serial) next();
    +        } else if (serial) {
    +          fn();
    +        }
    +    })();
    +}
    +
    +

    Report exceptions. +

    +
    +
    function report() {
    +    process.emit('beforeExit');
    +    if (failures) {
    +        print('\n   [bold]{Failures}: [red]{' + failures + '}\n\n');
    +        notify('Failures: ' + failures);
    +    } else {
    +        if (serial) print('');
    +        print('\n   [green]{100%} ' + testcount + ' tests\n');
    +        notify('100% ok');
    +    }
    +    if (typeof _$jscoverage === 'object') {
    +        reportCoverage(_$jscoverage);
    +    }
    +}
    +
    +

    Growl notify the given msg.

    + +

    + +
    • param: String msg

    +
    +
    function notify(msg) {
    +    if (growl) {
    +        childProcess.exec('growlnotify -name Expresso -m "' + msg + '"');
    +    }
    +}
    +
    +// Report uncaught exceptions
    +
    +process.addListener('uncaughtException', function(err){
    +    error('uncaught', 'uncaught', err);
    +});
    +
    +// Show cursor
    +
    +['INT', 'TERM', 'QUIT'].forEach(function(sig){
    +    process.addListener('SIG' + sig, function(){
    +        cursor(true);
    +        process.exit(1);
    +    });
    +});
    +
    +// Report test coverage when available
    +// and emit "beforeExit" event to perform
    +// final assertions
    +
    +var orig = process.emit;
    +process.emit = function(event){
    +    if (event === 'exit') {
    +        report();
    +        process.reallyExit(failures);
    +    }
    +    orig.apply(this, arguments);
    +};
    +
    +// Run test files
    +
    +if (!defer) run(files);
    +
    +
    \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/docs/index.html b/node_modules/mongoose/support/expresso/docs/index.html new file mode 100644 index 0000000..064313f --- /dev/null +++ b/node_modules/mongoose/support/expresso/docs/index.html @@ -0,0 +1,377 @@ + + + Expresso - TDD Framework For Node + + + + + Fork me on GitHub + +
    +

    Expresso

    +
    +

    NAME

    +

    + index +

    +

    Expresso is a JavaScript TDD framework written for nodejs. Expresso is extremely fast, and is packed with features such as additional assertion methods, code coverage reporting, CI support, and more.

    + +

    Features

    + +
      +
    • light-weight
    • +
    • intuitive async support
    • +
    • intuitive test runner executable
    • +
    • test coverage support and reporting via node-jscoverage
    • +
    • uses and extends the core assert module
    • +
    • assert.eql() alias of assert.deepEqual()
    • +
    • assert.response() http response utility
    • +
    • assert.includes()
    • +
    • assert.isNull()
    • +
    • assert.isUndefined()
    • +
    • assert.isNotNull()
    • +
    • assert.isDefined()
    • +
    • assert.match()
    • +
    • assert.length()
    • +
    + + +

    Installation

    + +

    To install both expresso and node-jscoverage run +the command below, which will first compile node-jscoverage:

    + +
    $ make install
    +
    + +

    To install expresso alone without coverage reporting run:

    + +
    $ make install-expresso
    +
    + +

    Install via npm:

    + +
    $ npm install expresso
    +
    + +

    Examples

    + +

    To define tests we simply export several functions:

    + +
    exports['test String#length'] = function(){
    +    assert.equal(6, 'foobar'.length);
    +};
    +
    + +

    Alternatively for large numbers of tests you may want to +export your own object containing the tests, however this +is essentially the as above:

    + +
    module.exports = {
    +    'test String#length': function(){
    +        assert.equal(6, 'foobar'.length);
    +    }
    +};
    +
    + +

    If you prefer not to use quoted keys:

    + +
    exports.testsStringLength = function(){
    +    assert.equal(6, 'foobar'.length);
    +};
    +
    + +

    The argument passed to each callback is beforeExit, +which is typically used to assert that callbacks have been +invoked.

    + +
    exports.testAsync = function(beforeExit){
    +    var n = 0;
    +    setTimeout(function(){
    +        ++n;
    +        assert.ok(true);
    +    }, 200);
    +    setTimeout(function(){
    +        ++n;
    +        assert.ok(true);
    +    }, 200);
    +    beforeExit(function(){
    +        assert.equal(2, n, 'Ensure both timeouts are called');
    +    });
    +};
    +
    + +

    Assert Utilities

    + +

    assert.isNull(val[, msg])

    + +

    Asserts that the given val is null.

    + +
    assert.isNull(null);
    +
    + +

    assert.isNotNull(val[, msg])

    + +

    Asserts that the given val is not null.

    + +
    assert.isNotNull(undefined);
    +assert.isNotNull(false);
    +
    + +

    assert.isUndefined(val[, msg])

    + +

    Asserts that the given val is undefined.

    + +
    assert.isUndefined(undefined);
    +
    + +

    assert.isDefined(val[, msg])

    + +

    Asserts that the given val is not undefined.

    + +
    assert.isDefined(null);
    +assert.isDefined(false);
    +
    + +

    assert.match(str, regexp[, msg])

    + +

    Asserts that the given str matches regexp.

    + +
    assert.match('foobar', /^foo(bar)?/);
    +assert.match('foo', /^foo(bar)?/);
    +
    + +

    assert.length(val, n[, msg])

    + +

    Assert that the given val has a length of n.

    + +
    assert.length([1,2,3], 3);
    +assert.length('foo', 3);
    +
    + +

    assert.type(obj, type[, msg])

    + +

    Assert that the given obj is typeof type.

    + +
    assert.type(3, 'number');
    +
    + +

    assert.eql(a, b[, msg])

    + +

    Assert that object b is equal to object a. This is an +alias for the core assert.deepEqual() method which does complex +comparisons, opposed to assert.equal() which uses ==.

    + +
    assert.eql('foo', 'foo');
    +assert.eql([1,2], [1,2]);
    +assert.eql({ foo: 'bar' }, { foo: 'bar' });
    +
    + +

    assert.includes(obj, val[, msg])

    + +

    Assert that obj is within val. This method supports Array_s +and Strings_s.

    + +
    assert.includes([1,2,3], 3);
    +assert.includes('foobar', 'foo');
    +assert.includes('foobar', 'bar');
    +
    + +

    assert.response(server, req, res|fn[, msg|fn])

    + +

    Performs assertions on the given server, which should not call +listen(), as this is handled internally by expresso and the server +is killed after all responses have completed. This method works with +any http.Server instance, so Connect and Express servers will work +as well.

    + +

    The req object may contain:

    + +
      +
    • url request url
    • +
    • timeout timeout in milliseconds
    • +
    • method HTTP method
    • +
    • data request body
    • +
    • headers headers object
    • +
    + + +

    The res object may be a callback function which +receives the response for assertions, or an object +which is then used to perform several assertions +on the response with the following properties:

    + +
      +
    • body assert response body (regexp or string)
    • +
    • status assert response status code
    • +
    • header assert that all given headers match (unspecified are ignored, use a regexp or string)
    • +
    + + +

    When providing res you may then also pass a callback function +as the fourth argument for additional assertions.

    + +

    Below are some examples:

    + +
    assert.response(server, {
    +    url: '/', timeout: 500
    +}, {
    +    body: 'foobar'
    +});
    +
    +assert.response(server, {
    +    url: '/',
    +    method: 'GET'
    +},{
    +    body: '{"name":"tj"}',
    +    status: 200,
    +    headers: {
    +        'Content-Type': 'application/json; charset=utf8',
    +        'X-Foo': 'bar'
    +    }
    +});
    +
    +assert.response(server, {
    +    url: '/foo',
    +    method: 'POST',
    +    data: 'bar baz'
    +},{
    +    body: '/foo bar baz',
    +    status: 200
    +}, 'Test POST');
    +
    +assert.response(server, {
    +    url: '/foo',
    +    method: 'POST',
    +    data: 'bar baz'
    +},{
    +    body: '/foo bar baz',
    +    status: 200
    +}, function(res){
    +    // All done, do some more tests if needed
    +});
    +
    +assert.response(server, {
    +    url: '/'
    +}, function(res){
    +    assert.ok(res.body.indexOf('tj') >= 0, 'Test assert.response() callback');
    +});
    +
    + +

    expresso(1)

    + +

    To run a single test suite (file) run:

    + +
    $ expresso test/a.test.js
    +
    + +

    To run several suites we may simply append another:

    + +
    $ expresso test/a.test.js test/b.test.js
    +
    + +

    We can also pass a whitelist of tests to run within all suites:

    + +
    $ expresso --only "foo()" --only "bar()"
    +
    + +

    Or several with one call:

    + +
    $ expresso --only "foo(), bar()"
    +
    + +

    Globbing is of course possible as well:

    + +
    $ expresso test/*
    +
    + +

    When expresso is called without any files, test/* is the default, +so the following is equivalent to the command above:

    + +
    $ expresso
    +
    + +

    If you wish to unshift a path to require.paths before +running tests, you may use the -I or --include flag.

    + +
    $ expresso --include lib test/*
    +
    + +

    The previous example is typically what I would recommend, since expresso +supports test coverage via node-jscoverage (bundled with expresso), +so you will need to expose an instrumented version of you library.

    + +

    To instrument your library, simply run node-jscoverage, +passing the src and dest directories:

    + +
    $ node-jscoverage lib lib-cov
    +
    + +

    Now we can run our tests again, using the lib-cov directory that has been +instrumented with coverage statements:

    + +
    $ expresso -I lib-cov test/*
    +
    + +

    The output will look similar to below, depending on your test coverage of course :)

    + +

    node coverage

    + +

    To make this process easier expresso has the -c or --cov which essentially +does the same as the two commands above. The following two commands will +run the same tests, however one will auto-instrument, and unshift lib-cov, +and the other will run tests normally:

    + +
    $ expresso -I lib test/*
    +$ expresso -I lib --cov test/*
    +
    + +

    Currently coverage is bound to the lib directory, however in the +future --cov will most likely accept a path.

    + +

    Async Exports

    + +

    Sometimes it is useful to postpone running of tests until a callback or event has fired, currently the exports.foo = function(){}; syntax is supported for this:

    + +
    setTimeout(function(){
    +    exports['test async exports'] = function(){
    +        assert.ok('wahoo');
    +    };
    +}, 100);
    +
    + +
    +
    + + \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/docs/index.md b/node_modules/mongoose/support/expresso/docs/index.md new file mode 100644 index 0000000..489f931 --- /dev/null +++ b/node_modules/mongoose/support/expresso/docs/index.md @@ -0,0 +1,290 @@ + +[Expresso](http://github.com/visionmedia/expresso) is a JavaScript [TDD](http://en.wikipedia.org/wiki/Test-driven_development) framework written for [nodejs](http://nodejs.org). Expresso is extremely fast, and is packed with features such as additional assertion methods, code coverage reporting, CI support, and more. + +## Features + + - light-weight + - intuitive async support + - intuitive test runner executable + - test coverage support and reporting via [node-jscoverage](http://github.com/visionmedia/node-jscoverage) + - uses and extends the core _assert_ module + - `assert.eql()` alias of `assert.deepEqual()` + - `assert.response()` http response utility + - `assert.includes()` + - `assert.isNull()` + - `assert.isUndefined()` + - `assert.isNotNull()` + - `assert.isDefined()` + - `assert.match()` + - `assert.length()` + +## Installation + +To install both expresso _and_ node-jscoverage run +the command below, which will first compile node-jscoverage: + + $ make install + +To install expresso alone without coverage reporting run: + + $ make install-expresso + +Install via npm: + + $ npm install expresso + +## Examples + +To define tests we simply export several functions: + + exports['test String#length'] = function(){ + assert.equal(6, 'foobar'.length); + }; + +Alternatively for large numbers of tests you may want to +export your own object containing the tests, however this +is essentially the as above: + + module.exports = { + 'test String#length': function(){ + assert.equal(6, 'foobar'.length); + } + }; + +If you prefer not to use quoted keys: + + exports.testsStringLength = function(){ + assert.equal(6, 'foobar'.length); + }; + +The argument passed to each callback is _beforeExit_, +which is typically used to assert that callbacks have been +invoked. + + exports.testAsync = function(beforeExit){ + var n = 0; + setTimeout(function(){ + ++n; + assert.ok(true); + }, 200); + setTimeout(function(){ + ++n; + assert.ok(true); + }, 200); + beforeExit(function(){ + assert.equal(2, n, 'Ensure both timeouts are called'); + }); + }; + +## Assert Utilities + +### assert.isNull(val[, msg]) + +Asserts that the given _val_ is _null_. + + assert.isNull(null); + +### assert.isNotNull(val[, msg]) + +Asserts that the given _val_ is not _null_. + + assert.isNotNull(undefined); + assert.isNotNull(false); + +### assert.isUndefined(val[, msg]) + +Asserts that the given _val_ is _undefined_. + + assert.isUndefined(undefined); + +### assert.isDefined(val[, msg]) + +Asserts that the given _val_ is not _undefined_. + + assert.isDefined(null); + assert.isDefined(false); + +### assert.match(str, regexp[, msg]) + +Asserts that the given _str_ matches _regexp_. + + assert.match('foobar', /^foo(bar)?/); + assert.match('foo', /^foo(bar)?/); + +### assert.length(val, n[, msg]) + +Assert that the given _val_ has a length of _n_. + + assert.length([1,2,3], 3); + assert.length('foo', 3); + +### assert.type(obj, type[, msg]) + +Assert that the given _obj_ is typeof _type_. + + assert.type(3, 'number'); + +### assert.eql(a, b[, msg]) + +Assert that object _b_ is equal to object _a_. This is an +alias for the core _assert.deepEqual()_ method which does complex +comparisons, opposed to _assert.equal()_ which uses _==_. + + assert.eql('foo', 'foo'); + assert.eql([1,2], [1,2]); + assert.eql({ foo: 'bar' }, { foo: 'bar' }); + +### assert.includes(obj, val[, msg]) + +Assert that _obj_ is within _val_. This method supports _Array_s +and _Strings_s. + + assert.includes([1,2,3], 3); + assert.includes('foobar', 'foo'); + assert.includes('foobar', 'bar'); + +### assert.response(server, req, res|fn[, msg|fn]) + +Performs assertions on the given _server_, which should _not_ call +listen(), as this is handled internally by expresso and the server +is killed after all responses have completed. This method works with +any _http.Server_ instance, so _Connect_ and _Express_ servers will work +as well. + +The _req_ object may contain: + + - _url_ request url + - _timeout_ timeout in milliseconds + - _method_ HTTP method + - _data_ request body + - _headers_ headers object + +The _res_ object may be a callback function which +receives the response for assertions, or an object +which is then used to perform several assertions +on the response with the following properties: + + - _body_ assert response body (regexp or string) + - _status_ assert response status code + - _header_ assert that all given headers match (unspecified are ignored, use a regexp or string) + +When providing _res_ you may then also pass a callback function +as the fourth argument for additional assertions. + +Below are some examples: + + assert.response(server, { + url: '/', timeout: 500 + }, { + body: 'foobar' + }); + + assert.response(server, { + url: '/', + method: 'GET' + },{ + body: '{"name":"tj"}', + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf8', + 'X-Foo': 'bar' + } + }); + + assert.response(server, { + url: '/foo', + method: 'POST', + data: 'bar baz' + },{ + body: '/foo bar baz', + status: 200 + }, 'Test POST'); + + assert.response(server, { + url: '/foo', + method: 'POST', + data: 'bar baz' + },{ + body: '/foo bar baz', + status: 200 + }, function(res){ + // All done, do some more tests if needed + }); + + assert.response(server, { + url: '/' + }, function(res){ + assert.ok(res.body.indexOf('tj') >= 0, 'Test assert.response() callback'); + }); + + +## expresso(1) + +To run a single test suite (file) run: + + $ expresso test/a.test.js + +To run several suites we may simply append another: + + $ expresso test/a.test.js test/b.test.js + +We can also pass a whitelist of tests to run within all suites: + + $ expresso --only "foo()" --only "bar()" + +Or several with one call: + + $ expresso --only "foo(), bar()" + +Globbing is of course possible as well: + + $ expresso test/* + +When expresso is called without any files, _test/*_ is the default, +so the following is equivalent to the command above: + + $ expresso + +If you wish to unshift a path to `require.paths` before +running tests, you may use the `-I` or `--include` flag. + + $ expresso --include lib test/* + +The previous example is typically what I would recommend, since expresso +supports test coverage via [node-jscoverage](http://github.com/visionmedia/node-jscoverage) (bundled with expresso), +so you will need to expose an instrumented version of you library. + +To instrument your library, simply run [node-jscoverage](http://github.com/visionmedia/node-jscoverage), +passing the _src_ and _dest_ directories: + + $ node-jscoverage lib lib-cov + +Now we can run our tests again, using the _lib-cov_ directory that has been +instrumented with coverage statements: + + $ expresso -I lib-cov test/* + +The output will look similar to below, depending on your test coverage of course :) + +![node coverage](http://dl.dropbox.com/u/6396913/cov.png) + +To make this process easier expresso has the _-c_ or _--cov_ which essentially +does the same as the two commands above. The following two commands will +run the same tests, however one will auto-instrument, and unshift _lib-cov_, +and the other will run tests normally: + + $ expresso -I lib test/* + $ expresso -I lib --cov test/* + +Currently coverage is bound to the _lib_ directory, however in the +future `--cov` will most likely accept a path. + +## Async Exports + +Sometimes it is useful to postpone running of tests until a callback or event has fired, currently the _exports.foo = function(){};_ syntax is supported for this: + + setTimeout(function(){ + exports['test async exports'] = function(){ + assert.ok('wahoo'); + }; + }, 100); diff --git a/node_modules/mongoose/support/expresso/docs/layout/foot.html b/node_modules/mongoose/support/expresso/docs/layout/foot.html new file mode 100644 index 0000000..44d85e9 --- /dev/null +++ b/node_modules/mongoose/support/expresso/docs/layout/foot.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/docs/layout/head.html b/node_modules/mongoose/support/expresso/docs/layout/head.html new file mode 100644 index 0000000..567f62e --- /dev/null +++ b/node_modules/mongoose/support/expresso/docs/layout/head.html @@ -0,0 +1,42 @@ + + + Expresso - TDD Framework For Node + + + + + Fork me on GitHub + +
    +

    Expresso

    diff --git a/node_modules/mongoose/support/expresso/lib/bar.js b/node_modules/mongoose/support/expresso/lib/bar.js new file mode 100644 index 0000000..e15aad4 --- /dev/null +++ b/node_modules/mongoose/support/expresso/lib/bar.js @@ -0,0 +1,4 @@ + +exports.bar = function(msg){ + return msg || 'bar'; +}; \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/lib/foo.js b/node_modules/mongoose/support/expresso/lib/foo.js new file mode 100644 index 0000000..15701a5 --- /dev/null +++ b/node_modules/mongoose/support/expresso/lib/foo.js @@ -0,0 +1,16 @@ + +exports.foo = function(msg){ + if (msg) { + return msg; + } else { + return generateFoo(); + } +}; + +function generateFoo() { + return 'foo'; +} + +function Foo(msg){ + this.msg = msg || 'foo'; +} diff --git a/node_modules/mongoose/support/expresso/package.json b/node_modules/mongoose/support/expresso/package.json new file mode 100644 index 0000000..569a54b --- /dev/null +++ b/node_modules/mongoose/support/expresso/package.json @@ -0,0 +1,12 @@ +{ "name": "expresso", + "version": "0.7.2", + "description": "TDD framework, light-weight, fast, CI-friendly", + "author": "TJ Holowaychuk ", + "bin": { + "expresso": "./bin/expresso", + "node-jscoverage": "./deps/jscoverage/node-jscoverage" + }, + "scripts": { + "preinstall": "make deps/jscoverage/node-jscoverage" + } +} \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/test/assert.test.js b/node_modules/mongoose/support/expresso/test/assert.test.js new file mode 100644 index 0000000..f822595 --- /dev/null +++ b/node_modules/mongoose/support/expresso/test/assert.test.js @@ -0,0 +1,91 @@ + +/** + * Module dependencies. + */ + +var assert = require('assert'); + +module.exports = { + 'assert.eql()': function(){ + assert.equal(assert.deepEqual, assert.eql); + }, + + 'assert.type()': function(){ + assert.type('foobar', 'string'); + assert.type(2, 'number'); + assert.throws(function(){ + assert.type([1,2,3], 'string'); + }); + }, + + 'assert.includes()': function(){ + assert.includes('some random string', 'dom'); + assert.throws(function(){ + assert.include('some random string', 'foobar'); + }); + + assert.includes(['foo', 'bar'], 'bar'); + assert.includes(['foo', 'bar'], 'foo'); + assert.includes([1,2,3], 3); + assert.includes([1,2,3], 2); + assert.includes([1,2,3], 1); + assert.throws(function(){ + assert.includes(['foo', 'bar'], 'baz'); + }); + + assert.throws(function(){ + assert.includes({ wrong: 'type' }, 'foo'); + }); + }, + + 'assert.isNull()': function(){ + assert.isNull(null); + assert.throws(function(){ + assert.isNull(undefined); + }); + assert.throws(function(){ + assert.isNull(false); + }); + }, + + 'assert.isUndefined()': function(){ + assert.isUndefined(undefined); + assert.throws(function(){ + assert.isUndefined(null); + }); + assert.throws(function(){ + assert.isUndefined(false); + }); + }, + + 'assert.isNotNull()': function(){ + assert.isNotNull(false); + assert.isNotNull(undefined); + assert.throws(function(){ + assert.isNotNull(null); + }); + }, + + 'assert.isDefined()': function(){ + assert.isDefined(false); + assert.isDefined(null); + assert.throws(function(){ + assert.isDefined(undefined); + }); + }, + + 'assert.match()': function(){ + assert.match('foobar', /foo(bar)?/); + assert.throws(function(){ + assert.match('something', /rawr/); + }); + }, + + 'assert.length()': function(){ + assert.length('test', 4); + assert.length([1,2,3,4], 4); + assert.throws(function(){ + assert.length([1,2,3], 4); + }); + } +}; \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/test/async.test.js b/node_modules/mongoose/support/expresso/test/async.test.js new file mode 100644 index 0000000..d1d2036 --- /dev/null +++ b/node_modules/mongoose/support/expresso/test/async.test.js @@ -0,0 +1,12 @@ + +/** + * Module dependencies. + */ + +var assert = require('assert'); + +setTimeout(function(){ + exports['test async exports'] = function(){ + assert.ok('wahoo'); + }; +}, 100); \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/test/bar.test.js b/node_modules/mongoose/support/expresso/test/bar.test.js new file mode 100644 index 0000000..cfc2e11 --- /dev/null +++ b/node_modules/mongoose/support/expresso/test/bar.test.js @@ -0,0 +1,13 @@ + +/** + * Module dependencies. + */ + +var assert = require('assert') + , bar = require('bar'); + +module.exports = { + 'bar()': function(){ + assert.equal('bar', bar.bar()); + } +}; \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/test/foo.test.js b/node_modules/mongoose/support/expresso/test/foo.test.js new file mode 100644 index 0000000..ee5cff3 --- /dev/null +++ b/node_modules/mongoose/support/expresso/test/foo.test.js @@ -0,0 +1,14 @@ + +/** + * Module dependencies. + */ + +var assert = require('assert') + , foo = require('foo'); + +module.exports = { + 'foo()': function(){ + assert.equal('foo', foo.foo()); + assert.equal('foo', foo.foo()); + } +}; \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/test/http.test.js b/node_modules/mongoose/support/expresso/test/http.test.js new file mode 100644 index 0000000..41648d2 --- /dev/null +++ b/node_modules/mongoose/support/expresso/test/http.test.js @@ -0,0 +1,109 @@ + +/** + * Module dependencies. + */ + +var assert = require('assert') + , http = require('http'); + +var server = http.createServer(function(req, res){ + if (req.method === 'GET') { + if (req.url === '/delay') { + setTimeout(function(){ + res.writeHead(200, {}); + res.end('delayed'); + }, 200); + } else { + var body = JSON.stringify({ name: 'tj' }); + res.writeHead(200, { + 'Content-Type': 'application/json; charset=utf8', + 'Content-Length': body.length + }); + res.end(body); + } + } else { + var body = ''; + req.setEncoding('utf8'); + req.addListener('data', function(chunk){ body += chunk }); + req.addListener('end', function(){ + res.writeHead(200, {}); + res.end(req.url + ' ' + body); + }); + } +}); + +var delayedServer = http.createServer(function(req, res){ + res.writeHead(200); + res.end('it worked'); +}); + +var oldListen = delayedServer.listen; +delayedServer.listen = function(){ + var args = arguments; + setTimeout(function(){ + oldListen.apply(delayedServer, args); + }, 100); +}; + +module.exports = { + 'test assert.response()': function(beforeExit){ + var called = 0; + + assert.response(server, { + url: '/', + method: 'GET' + },{ + body: '{"name":"tj"}', + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf8' + } + }); + + assert.response(server, { + url: '/foo', + method: 'POST', + data: 'bar baz' + },{ + body: '/foo bar baz', + status: 200 + }, function(res){ + ++called; + assert.ok(res); + }); + + assert.response(server, { + url: '/foo' + }, function(res){ + ++called; + assert.ok(res.body.indexOf('tj') >= 0, 'Test assert.response() callback'); + }); + + assert.response(server, + { url: '/delay', timeout: 300 }, + { body: 'delayed' }); + + beforeExit(function(){ + assert.equal(2, called); + }); + }, + + 'test assert.response() regexp': function(){ + assert.response(server, + { url: '/foo', method: 'POST', data: 'foobar' }, + { body: /^\/foo foo(bar)?/ }); + }, + + 'test assert.response() regexp headers': function(){ + assert.response(server, + { url: '/' }, + { body: '{"name":"tj"}', headers: { 'Content-Type': /^application\/json/ } }); + }, + + // [!] if this test doesn't pass, an uncaught ECONNREFUSED will display + 'test assert.response() with deferred listen()': function(){ + assert.response(delayedServer, + { url: '/' }, + { body: 'it worked' }); + } +}; diff --git a/node_modules/mongoose/support/expresso/test/serial/async.test.js b/node_modules/mongoose/support/expresso/test/serial/async.test.js new file mode 100644 index 0000000..c596d5c --- /dev/null +++ b/node_modules/mongoose/support/expresso/test/serial/async.test.js @@ -0,0 +1,39 @@ + +var assert = require('assert') + , setup = 0 + , order = []; + +module.exports = { + setup: function(done){ + ++setup; + done(); + }, + + a: function(done){ + assert.equal(1, setup); + order.push('a'); + setTimeout(function(){ + done(); + }, 500); + }, + + b: function(done){ + assert.equal(2, setup); + order.push('b'); + setTimeout(function(){ + done(); + }, 200); + }, + + c: function(done){ + assert.equal(3, setup); + order.push('c'); + setTimeout(function(){ + done(); + }, 1000); + }, + + d: function(){ + assert.eql(order, ['a', 'b', 'c']); + } +}; \ No newline at end of file diff --git a/node_modules/mongoose/support/expresso/test/serial/http.test.js b/node_modules/mongoose/support/expresso/test/serial/http.test.js new file mode 100644 index 0000000..7783eb5 --- /dev/null +++ b/node_modules/mongoose/support/expresso/test/serial/http.test.js @@ -0,0 +1,48 @@ + +/** + * Module dependencies. + */ + +var assert = require('assert') + , http = require('http'); + +var server = http.createServer(function(req, res){ + if (req.method === 'GET') { + if (req.url === '/delay') { + setTimeout(function(){ + res.writeHead(200, {}); + res.end('delayed'); + }, 200); + } else { + var body = JSON.stringify({ name: 'tj' }); + res.writeHead(200, { + 'Content-Type': 'application/json; charset=utf8', + 'Content-Length': body.length + }); + res.end(body); + } + } else { + var body = ''; + req.setEncoding('utf8'); + req.addListener('data', function(chunk){ body += chunk }); + req.addListener('end', function(){ + res.writeHead(200, {}); + res.end(req.url + ' ' + body); + }); + } +}); + +module.exports = { + 'test assert.response()': function(done){ + assert.response(server, { + url: '/', + method: 'GET' + },{ + body: '{"name":"tj"}', + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf8' + } + }, done); + } +}; \ No newline at end of file diff --git a/node_modules/mongoose/test/collection.test.js b/node_modules/mongoose/test/collection.test.js new file mode 100644 index 0000000..54c247c --- /dev/null +++ b/node_modules/mongoose/test/collection.test.js @@ -0,0 +1,116 @@ + +var start = require('./common') + , mongoose = start.mongoose + , Collection = require('../lib/collection'); + +module.exports = { + + 'test buffering of commands until connection is established': function(beforeExit){ + var db = mongoose.createConnection() + , collection = db.collection('test-buffering-collection') + , connected = false + , inserted = false; + + collection.insert({ }, function(){ + connected.should.be.true; + inserted = true; + db.close(); + }); + + var uri = 'mongodb://localhost/mongoose_test'; + db.open(process.env.MONGOOSE_TEST_URI || uri, function(err){ + connected = !err; + }); + + beforeExit(function(){ + connected.should.be.true; + inserted.should.be.true; + }); + }, + + 'test methods that should throw (unimplemented)': function () { + var collection = new Collection('test', mongoose.connection) + , thrown = false; + + try { + collection.getIndexes(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.update(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.save(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.insert(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.find(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.findOne(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.findAndModify(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.ensureIndex(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + } + +}; diff --git a/node_modules/mongoose/test/common.js b/node_modules/mongoose/test/common.js new file mode 100644 index 0000000..f1c6b4d --- /dev/null +++ b/node_modules/mongoose/test/common.js @@ -0,0 +1,138 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('../') + , should = require('should') + , Table = require('../support/cli-table') + , Mongoose = mongoose.Mongoose + , Collection = mongoose.Collection + , Assertion = should.Assertion + , startTime = Date.now() + , queryCount = 0 + , opened = 0 + , closed = 0; + +/** + * Override all Collection related queries to keep count + */ + +[ 'ensureIndex' + , 'findAndModify' + , 'findOne' + , 'find' + , 'insert' + , 'save' + , 'update' + , 'remove' + , 'count' + , 'distinct' +].forEach(function (method) { + + var oldMethod = Collection.prototype[method]; + + Collection.prototype[method] = function () { + queryCount++; + return oldMethod.apply(this, arguments); + }; + +}); + +/** + * Override Collection#onOpen to keep track of connections + */ + +var oldOnOpen = Collection.prototype.onOpen; + +Collection.prototype.onOpen = function(){ + opened++; + return oldOnOpen.apply(this, arguments); +}; + +/** + * Override Collection#onClose to keep track of disconnections + */ + +var oldOnClose = Collection.prototype.onClose; + +Collection.prototype.onClose = function(){ + closed++; + return oldOnClose.apply(this, arguments); +}; + +/** + * Assert that a connection is open or that mongoose connections are open. + * Examples: + * mongoose.should.be.connected; + * db.should.be.connected; + * + * @api public + */ + +Assertion.prototype.__defineGetter__('connected', function(){ + if (this.obj instanceof Mongoose) + this.obj.connections.forEach(function(connected){ + c.should.be.connected; + }); + else + this.obj.readyState.should.eql(1); +}); + +/** + * Assert that a connection is closed or that a mongoose connections are closed. + * Examples: + * mongoose.should.be.disconnected; + * db.should.be.disconnected; + * + * @api public + */ + +Assertion.prototype.__defineGetter__('disconnected', function(){ + if (this.obj instanceof Mongoose) + this.obj.connections.forEach(function(){ + c.should.be.disconnected; + }); + else + this.obj.readyState.should.eql(0); +}); + +/** + * Create a connection to the test database. + * You can set the environmental variable MONGOOSE_TEST_URI to override this. + * + * @api private + */ + +module.exports = function (options) { + return mongoose.createConnection( + process.env.MONGOOSE_TEST_URI || 'mongodb://localhost/mongoose_test' + , options + ); +}; + +/** + * Provide stats for tests + */ + +process.on('beforeExit', function(){ + var table = new Table({ + head: ['Stat', 'Time (ms)'] + , colWidths: [23, 15] + }); + + table.push( + ['Queries run', queryCount] + , ['Time ellapsed', Date.now() - startTime] + , ['Connections opened', opened] + , ['Connections closed', closed] + ); + + console.error(table.toString()); +}); + +/** + * Module exports. + */ + +module.exports.mongoose = mongoose; diff --git a/node_modules/mongoose/test/connection.test.js b/node_modules/mongoose/test/connection.test.js new file mode 100644 index 0000000..d8cf119 --- /dev/null +++ b/node_modules/mongoose/test/connection.test.js @@ -0,0 +1,256 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , Schema = mongoose.Schema + +/** + * Test. + */ + +module.exports = { + + 'test closing a connection that\'s already closed': function (beforeExit) { + var db = mongoose.createConnection() + , called = false; + + db.readyState.should.eql(0); + db.close(function (err) { + should.strictEqual(err, null); + called = true; + }); + + beforeExit(function () { + called.should.be.true; + }); + }, + + 'test connection args': function (beforeExit) { + var db = mongoose.createConnection('mongodb://localhost/fake'); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual(undefined, db.pass); + should.strictEqual(undefined, db.user); + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal(27017); + db.close(); + + db = mongoose.createConnection('mongodb://localhost:27000/fake'); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual(undefined, db.pass); + should.strictEqual(undefined, db.user); + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal('27000'); + db.close(); + + db = mongoose.createConnection('mongodb://aaron:psw@localhost:27000/fake'); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual('psw', db.pass); + should.strictEqual('aaron', db.user); + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal('27000'); + db.close(); + + db = mongoose.createConnection('mongodb://aaron:psw@localhost:27000/fake', { db: { forceServerObjectId: true }}); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.close(); + + db = mongoose.createConnection('mongodb://aaron:psw@localhost:27000/fake', { server: { auto_reconnect: false }}); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.false; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.close(); + + var called1 = false; + db = mongoose.createConnection('mongodb://aaron:psw@localhost:27000/fake', { server: { auto_reconnect: true }}, function () { + called1 = true; + }); + beforeExit(function () { + called1.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.close(); + + var called2 = false; + db = mongoose.createConnection('mongodb://localhost/fake', function () { + called2 = true; + }); + beforeExit(function () { + called2.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal(27017); + db.close(); + + db = mongoose.createConnection('mongodb:///fake', function (err) { + err.message.should.equal('Missing connection hostname.'); + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual(undefined, db.name); + should.strictEqual(undefined, db.host); + should.strictEqual(undefined, db.port); + db.close(); + + db = mongoose.createConnection('mongodb://localhost', function (err) { + err.message.should.equal('Missing connection database.'); + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual(undefined, db.name); + should.strictEqual(undefined, db.host); + should.strictEqual(undefined, db.port); + db.close(); + + var called3 = false; + db = mongoose.createConnection('127.0.0.1', 'faker', 28000, { server: { auto_reconnect: false }}, function () { + called3 = true; + }); + beforeExit(function () { + called3.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.false; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(28000); + db.close(); + + var called4 = false; + db = mongoose.createConnection('127.0.0.1', 'faker', 28000, function () { + called4 = true; + }); + beforeExit(function () { + called4.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(28000); + db.close(); + + db = mongoose.createConnection('127.0.0.1', 'faker', 28000, { server: { auto_reconnect: true }}); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(28000); + db.close(); + + db = mongoose.createConnection('127.0.0.1', 'faker', 28001); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(28001); + db.close(); + + db = mongoose.createConnection('127.0.0.1', 'faker', { blah: 1 }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.options.blah.should.equal(1); + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(27017); + db.close(); + + var called5 = false + db = mongoose.createConnection('127.0.0.1', 'faker', function () { + called5 = true; + }); + beforeExit(function () { + called5.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(27017); + db.close(); + + db = mongoose.createConnection('127.0.0.1', 'faker'); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(27017); + db.close(); + }, + + 'connection.model allows passing a schema': function () { + var db = start(); + var MyModel = db.model('MyModelasdf', new Schema({ + name: String + })); + + MyModel.schema.should.be.an.instanceof(Schema); + MyModel.prototype.schema.should.be.an.instanceof(Schema); + + var m = new MyModel({name:'aaron'}); + m.name.should.eql('aaron'); + db.close(); + } + +}; diff --git a/node_modules/mongoose/test/crash.test.js b/node_modules/mongoose/test/crash.test.js new file mode 100644 index 0000000..67e6c86 --- /dev/null +++ b/node_modules/mongoose/test/crash.test.js @@ -0,0 +1,36 @@ + +// GH-407 + +var start = require('./common') + , mongoose = start.mongoose + , should = require('should') + +exports['test mongodb crash with invalid objectid string'] = function () { + var db = mongoose.createConnection("mongodb://localhost/test-crash"); + + var IndexedGuy = new mongoose.Schema({ + name: { type: String } + }); + + var Guy = db.model('Guy', IndexedGuy); + Guy.find({ + _id: { + $in: [ + '4e0de2a6ee47bff98000e145', + '4e137bd81a6a8e00000007ac', + '', + '4e0e2ca0795666368603d974'] + } + }, function (err) { + db.close(); + + // should is acting strange + try { + should.strictEqual(err.message, "Invalid ObjectId"); + } catch (er) { + console.error(err); + throw er; + } + }); + +} diff --git a/node_modules/mongoose/test/document.test.js b/node_modules/mongoose/test/document.test.js new file mode 100644 index 0000000..06f2ef3 --- /dev/null +++ b/node_modules/mongoose/test/document.test.js @@ -0,0 +1,607 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , Schema = mongoose.Schema + , ObjectId = Schema.ObjectId + , Document = require('../lib/document') + , DocumentObjectId = mongoose.Types.ObjectId; + +/** + * Test Document constructor. + */ + +function TestDocument () { + Document.apply(this, arguments); +}; + +/** + * Inherits from Document. + */ + +TestDocument.prototype.__proto__ = Document.prototype; + +/** + * Set a dummy schema to simulate compilation. + */ + +var schema = TestDocument.prototype.schema = new Schema({ + test : String + , oids : [ObjectId] + , numbers : [Number] + , nested : { + age : Number + , cool : ObjectId + , deep : { x: String } + , path : String + , setr : String + } + , nested2 : { + nested: String + , yup : { + nested : Boolean + , yup : String + , age : Number + } + } +}); + +schema.virtual('nested.agePlus2').get(function (v) { + return this.nested.age + 2; +}); +schema.virtual('nested.setAge').set(function (v) { + this.nested.age = v; +}); +schema.path('nested.path').get(function (v) { + return this.nested.age + (v ? v : ''); +}); +schema.path('nested.setr').set(function (v) { + return v + ' setter'; +}); + +/** + * Method subject to hooks. Simply fires the callback once the hooks are + * executed. + */ + +TestDocument.prototype.hooksTest = function(fn){ + fn(null, arguments); +}; + +/** + * Test. + */ + +module.exports = { + 'test shortcut getters': function(){ + var doc = new TestDocument(); + doc.init({ + test : 'test' + , oids : [] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + } + }); + + doc.test.should.eql('test'); + doc.oids.should.be.an.instanceof(Array); + (doc.nested.age == 5).should.be.true; + DocumentObjectId.toString(doc.nested.cool).should.eql('4c6c2d6240ced95d0e00003c'); + doc.nested.agePlus2.should.eql(7); + doc.nested.path.should.eql('5my path'); + doc.nested.setAge = 10; + (doc.nested.age == 10).should.be.true; + doc.nested.setr = 'set it'; + doc.getValue('nested.setr').should.eql('set it setter'); + + var doc2 = new TestDocument(); + doc2.init({ + test : 'toop' + , oids : [] + , nested : { + age : 2 + , cool : DocumentObjectId.fromString('4cf70857337498f95900001c') + , deep : { x: 'yay' } + } + }); + + doc2.test.should.eql('toop'); + doc2.oids.should.be.an.instanceof(Array); + (doc2.nested.age == 2).should.be.true; + + // GH-366 + should.strictEqual(doc2.nested.bonk, undefined); + should.strictEqual(doc2.nested.nested, undefined); + should.strictEqual(doc2.nested.test, undefined); + should.strictEqual(doc2.nested.age.test, undefined); + should.strictEqual(doc2.nested.age.nested, undefined); + should.strictEqual(doc2.oids.nested, undefined); + should.strictEqual(doc2.nested.deep.x, 'yay'); + should.strictEqual(doc2.nested.deep.nested, undefined); + should.strictEqual(doc2.nested.deep.cool, undefined); + should.strictEqual(doc2.nested2.yup.nested, undefined); + should.strictEqual(doc2.nested2.yup.nested2, undefined); + should.strictEqual(doc2.nested2.yup.yup, undefined); + should.strictEqual(doc2.nested2.yup.age, undefined); + doc2.nested2.yup.should.be.a('object'); + + doc2.nested2.yup = { + age: 150 + , yup: "Yesiree" + , nested: true + }; + + should.strictEqual(doc2.nested2.nested, undefined); + should.strictEqual(doc2.nested2.yup.nested, true); + should.strictEqual(doc2.nested2.yup.yup, "Yesiree"); + (doc2.nested2.yup.age == 150).should.be.true; + doc2.nested2.nested = "y"; + should.strictEqual(doc2.nested2.nested, "y"); + should.strictEqual(doc2.nested2.yup.nested, true); + should.strictEqual(doc2.nested2.yup.yup, "Yesiree"); + (doc2.nested2.yup.age == 150).should.be.true; + + DocumentObjectId.toString(doc2.nested.cool).should.eql('4cf70857337498f95900001c'); + + doc.oids.should.not.equal(doc2.oids); + }, + + 'test shortcut setters': function () { + var doc = new TestDocument(); + + doc.init({ + test : 'Test' + , nested : { + age : 5 + } + }); + + doc.isModified('test').should.be.false; + doc.test = 'Woot'; + doc.test.should.eql('Woot'); + doc.isModified('test').should.be.true; + + doc.isModified('nested.age').should.be.false; + doc.nested.age = 2; + (doc.nested.age == 2).should.be.true; + doc.isModified('nested.age').should.be.true; + }, + + 'test accessor of id': function () { + var doc = new TestDocument(); + doc._id.should.be.an.instanceof(DocumentObjectId); + }, + + 'test shortcut of id hexString': function () { + var doc = new TestDocument() + , _id = doc._id.toString(); + doc.id.should.be.a('string'); + }, + + 'test toObject clone': function(){ + var doc = new TestDocument(); + doc.init({ + test : 'test' + , oids : [] + , nested : { + age : 5 + , cool : new DocumentObjectId + } + }); + + var copy = doc.toObject(); + + copy.test._marked = true; + copy.nested._marked = true; + copy.nested.age._marked = true; + copy.nested.cool._marked = true; + + should.strictEqual(doc._doc.test._marked, undefined); + should.strictEqual(doc._doc.nested._marked, undefined); + should.strictEqual(doc._doc.nested.age._marked, undefined); + should.strictEqual(doc._doc.nested.cool._marked, undefined); + }, + + 'test hooks system': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , awaiting = 0 + , called = false; + + // serial + doc.pre('hooksTest', function(next){ + steps++; + setTimeout(function(){ + // make sure next step hasn't executed yet + steps.should.eql(1); + next(); + }, 50); + }); + + doc.pre('hooksTest', function(next){ + steps++; + next(); + }); + + // parallel + doc.pre('hooksTest', true, function(next, done){ + steps++; + steps.should.eql(3); + setTimeout(function(){ + steps.should.eql(4); + }, 10); + setTimeout(function(){ + steps++; + done(); + }, 110); + next(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + setTimeout(function(){ + steps.should.eql(4); + }, 10); + setTimeout(function(){ + steps++; + done(); + }, 110); + next(); + }); + + doc.hooksTest(function(err){ + should.strictEqual(err, null); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(7); + called.should.be.true; + }); + }, + + 'test that calling next twice doesnt break': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', function(next){ + steps++; + next(); + next(); + }); + + doc.pre('hooksTest', function(next){ + steps++; + next(); + }); + + doc.hooksTest(function(err){ + should.strictEqual(err, null); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(3); + called.should.be.true; + }); + }, + + 'test that calling done twice doesnt break': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + done(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + done(); + }); + + doc.hooksTest(function(err){ + should.strictEqual(err, null); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(3); + called.should.be.true; + }); + }, + + 'test that calling done twice on the same doesnt mean completion': + function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + done(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + }); + + doc.hooksTest(function(err){ + should.strictEqual(err, null); + called = true; + }); + + beforeExit(function(){ + steps.should.eql(2); + called.should.be.false; + }); + }, + + 'test hooks system errors from a serial hook': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', function(next){ + steps++; + next(); + }); + + doc.pre('hooksTest', function(next){ + steps++; + next(new Error); + }); + + doc.pre('hooksTest', function(next){ + steps++; + }); + + doc.hooksTest(function(err){ + err.should.be.an.instanceof(Error); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(3); + called.should.be.true; + }); + }, + + 'test hooks system erros from last serial hook': function(beforeExit){ + var doc = new TestDocument() + , called = false; + + doc.pre('hooksTest', function(next){ + next(new Error()); + }); + + doc.hooksTest(function(err){ + err.should.be.an.instanceof(Error); + called = true; + }); + + beforeExit(function(){ + called.should.be.true; + }); + }, + + 'test mutating incoming args via middleware': function (beforeExit) { + var doc = new TestDocument(); + + doc.pre('set', function(next, path, val){ + next(path, 'altered-' + val); + }); + + doc.set('test', 'me'); + + beforeExit(function(){ + doc.test.should.equal('altered-me'); + }); + }, + + 'test hooks system errors from a parallel hook': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(new Error); + }); + + doc.hooksTest(function(err){ + err.should.be.an.instanceof(Error); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(4); + called.should.be.true; + }); + }, + + 'test that its not necessary to call the last next in the parallel chain': + function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', function(next, done){ + next(); + done(); + }); + + doc.pre('hooksTest', function(next, done){ + done(); + }); + + doc.hooksTest(function(){ + called = true; + }); + + beforeExit(function(){ + called.should.be.true; + }); + }, + + 'test passing two arguments to a method subject to hooks and return value': + function (beforeExit) { + var doc = new TestDocument() + , called = false; + + doc.pre('hooksTest', function (next) { + next(); + }); + + doc.hooksTest(function (err, args) { + args.should.have.length(2); + args[1].should.eql('test'); + called = true; + }, 'test') + + beforeExit(function () { + called.should.be.true; + }); + }, + + 'test jsonifying an object': function () { + var doc = new TestDocument({ test: 'woot' }) + , oidString = DocumentObjectId.toString(doc._id); + + // convert to json string + var json = JSON.stringify(doc); + + // parse again + var obj = JSON.parse(json); + + obj.test.should.eql('woot'); + obj._id.should.eql(oidString); + }, + + 'toObject should not set undefined values to null': function () { + var doc = new TestDocument() + , obj = doc.toObject(); + + delete obj._id; + obj.should.eql({ numbers: [], oids: [] }); + }, + + // GH-209 + 'MongooseErrors should be instances of Error': function () { + var MongooseError = require('../lib/error') + , err = new MongooseError("Some message"); + err.should.be.an.instanceof(Error); + }, + 'ValidationErrors should be instances of Error': function () { + var ValidationError = Document.ValidationError + , err = new ValidationError(new TestDocument); + err.should.be.an.instanceof(Error); + }, + + 'methods on embedded docs should work': function () { + var db = start() + , ESchema = new Schema({ name: String }) + + ESchema.methods.test = function () { + return this.name + ' butter'; + } + ESchema.statics.ten = function () { + return 10; + } + + var E = db.model('EmbeddedMethodsAndStaticsE', ESchema); + var PSchema = new Schema({ embed: [ESchema] }); + var P = db.model('EmbeddedMethodsAndStaticsP', PSchema); + + var p = new P({ embed: [{name: 'peanut'}] }); + should.equal('function', typeof p.embed[0].test); + should.equal('function', typeof E.ten); + p.embed[0].test().should.equal('peanut butter'); + E.ten().should.equal(10); + + // test push casting + p = new P; + p.embed.push({name: 'apple'}); + should.equal('function', typeof p.embed[0].test); + should.equal('function', typeof E.ten); + p.embed[0].test().should.equal('apple butter'); + + db.close(); + }, + + 'setting a positional path does not cast value to array': function () { + var doc = new TestDocument; + doc.init({ numbers: [1,3] }); + doc.numbers[0].should.eql(1); + doc.numbers[1].valueOf().should.eql(3); + doc.set('numbers.1', 2); + doc.numbers[0].should.eql(1); + doc.numbers[1].valueOf().should.eql(2); + }, + + 'no maxListeners warning should occur': function () { + var db = start(); + + var traced = false; + var trace = console.trace; + + console.trace = function () { + traced = true; + console.trace = trace; + } + + var schema = new Schema({ + title: String + , embed1: [new Schema({name:String})] + , embed2: [new Schema({name:String})] + , embed3: [new Schema({name:String})] + , embed4: [new Schema({name:String})] + , embed5: [new Schema({name:String})] + , embed6: [new Schema({name:String})] + , embed7: [new Schema({name:String})] + , embed8: [new Schema({name:String})] + , embed9: [new Schema({name:String})] + , embed10: [new Schema({name:String})] + , embed11: [new Schema({name:String})] + }); + + var S = db.model('noMaxListeners', schema); + + var s = new S({ title: "test" }); + db.close(); + traced.should.be.false + } +}; diff --git a/node_modules/mongoose/test/drivers/node-mongodb-native/collection.test.js b/node_modules/mongoose/test/drivers/node-mongodb-native/collection.test.js new file mode 100644 index 0000000..2ae5a4b --- /dev/null +++ b/node_modules/mongoose/test/drivers/node-mongodb-native/collection.test.js @@ -0,0 +1,64 @@ + +/** + * Module dependencies. + */ + +var start = require('../../common') + , mongoose = start.mongoose + , should = require('should') + , Schema = mongoose.Schema; + +/** + * Setup. + */ + +mongoose.model('NativeDriverTest', new Schema({ + title: String +})); + +/** + * Test. + */ + +module.exports = { + + 'test that trying to implement a sparse index works': function () { + var db = start() + , NativeTestCollection = db.model('NativeDriverTest'); + + NativeTestCollection.collection.ensureIndex({ title: 1 }, { sparse: true }, function (err) { + should.strictEqual(!!err, false); + NativeTestCollection.collection.getIndexes(function (err, indexes) { + db.close(); + should.strictEqual(!!err, false); + indexes.should.be.instanceof(Object); + indexes['title_1'].should.eql([['title', 1]]); + }); + }); + }, + + 'test that the -native traditional ensureIndex spec syntax for fields works': function () { + var db = start() + , NativeTestCollection = db.model('NativeDriverTest'); + + NativeTestCollection.collection.ensureIndex([['a', 1]], function () { + db.close(); + }); + }, + + 'unique index fails passes error': function () { + var db = start() + , schema = new Schema({ title: String }) + , NativeTestCollection = db.model('NativeDriverTestUnique', schema) + + NativeTestCollection.create({ title: 'x' }, {title:'x'}, function (err) { + should.strictEqual(!!err, false); + + NativeTestCollection.collection.ensureIndex({ title: 1 }, { unique: true }, function (err) { + ;/E11000 duplicate key error index/.test(err.message).should.equal(true); + + db.close(); + }); + }); + } +}; diff --git a/node_modules/mongoose/test/dropdb.js b/node_modules/mongoose/test/dropdb.js new file mode 100755 index 0000000..0b806e7 --- /dev/null +++ b/node_modules/mongoose/test/dropdb.js @@ -0,0 +1,7 @@ +var start = require('./common') +var db = start(); +db.on('open', function () { + db.db.dropDatabase(function () { + process.exit(); + }); +}); diff --git a/node_modules/mongoose/test/index.test.js b/node_modules/mongoose/test/index.test.js new file mode 100644 index 0000000..0cdba0d --- /dev/null +++ b/node_modules/mongoose/test/index.test.js @@ -0,0 +1,235 @@ + +var url = require('url') + , start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , Mongoose = mongoose.Mongoose + , Schema = mongoose.Schema; + +module.exports = { + + 'test connecting to the demo database': function(beforeExit){ + var db = start() + , connected = false; + + db.on('open', function(){ + connected = true; + db.close(); + }); + + beforeExit(function(){ + connected.should.be.true; + }); + }, + + 'test default connection': function(beforeExit){ + var db = mongoose.connection + , uri = 'mongodb://localhost/mongoose_test' + , connected = false; + + mongoose.connect(process.env.MONGOOSE_TEST_URI || uri); + db.on('open', function(){ + connected = true; + db.close(); + }); + + beforeExit(function(){ + connected.should.be.true; + }); + }, + + 'test setting options': function(){ + var mongoose = new Mongoose(); + + mongoose.set('a', 'b'); + mongoose.set('long option', 'c'); + + mongoose.get('a').should.eql('b'); + mongoose.set('a').should.eql('b'); + mongoose.get('long option').should.eql('c'); + }, + + 'test declaring global plugins': function (beforeExit) { + var mong = new Mongoose() + , schema = new Schema() + , called = 0; + + mong.plugin(function (s) { + s.should.equal(schema); + called++; + }); + + schema.plugin(function (s) { + s.should.equal(schema); + called++; + }); + + mong.model('GlobalPlugins', schema); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test disconnection of all connections': function (beforeExit) { + var mong = new Mongoose() + , uri = 'mongodb://localhost/mongoose_test' + , connections = 0 + , disconnections = 0; + + mong.connect(process.env.MONGOOSE_TEST_URI || uri); + var db = mong.connection; + + db.on('open', function(){ + connections++; + }); + + db.on('close', function () { + disconnections++; + }); + + var db2 = mong.createConnection(process.env.MONGOOSE_TEST_URI || uri); + + db2.on('open', function () { + connections++; + }); + + db2.on('close', function () { + disconnections++; + }); + + mong.disconnect(); + + beforeExit(function () { + connections.should.eql(2); + disconnections.should.eql(2); + }); + }, + + 'test disconnection of all connections callback': function (beforeExit) { + var mong = new Mongoose() + , uri = 'mongodb://localhost/mongoose_test' + , called = false; + + mong.connect(process.env.MONGOOSE_TEST_URI || uri); + + mong.connection.on('open', function () { + mong.disconnect(function () { + called = true; + }); + }); + + beforeExit(function () { + called.should.be.true; + }); + }, + + 'try accessing a model that hasn\'t been defined': function () { + var mong = new Mongoose() + , thrown = false; + + try { + mong.model('Test'); + } catch (e) { + /hasn't been registered/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + }, + + 'test connecting with a signature of host, database, function': function (){ + var mong = new Mongoose() + , uri = process.env.MONGOOSE_TEST_URI || 'mongodb://localhost/mongoose_test'; + + uri = url.parse(uri); + + mong.connect(uri.hostname, uri.pathname.substr(1), function (err) { + should.strictEqual(err, null); + mong.connection.close(); + }); + }, + + 'test connecting to a replica set': function () { + var uri = process.env.MONGOOSE_SET_TEST_URI; + + if (!uri) { + console.log('\033[31m', '\n', 'You\'re not testing for replica sets!' + , '\n', 'Please set the MONGOOSE_SET_TEST_URI env variable.', '\n' + , 'e.g: `mongodb://localhost:27017/db,mongodb://localhost…`', '\n' + , '\033[39m'); + return; + } + + var mong = new Mongoose(); + + mong.connectSet(uri, function (err) { + should.strictEqual(err, null); + + mong.model('Test', new mongoose.Schema({ + test: String + })); + + var Test = mong.model('Test') + , test = new Test(); + + test.test = 'aa'; + test.save(function (err) { + should.strictEqual(err, null); + + Test.findById(test._id, function (err, doc) { + should.strictEqual(err, null); + + doc.test.should.eql('aa'); + + mong.connection.close(); + }); + }); + }); + }, + + 'test initializing a new Connection to a replica set': function () { + var uri = process.env.MONGOOSE_SET_TEST_URI; + + if (!uri) return; + + var mong = new Mongoose(true); + + var conn = mong.createSetConnection(uri, function (err) { + should.strictEqual(err, null); + + mong.model('ReplSetTwo', new mongoose.Schema({ + test: String + })); + + var Test = conn.model('ReplSetTwo') + , test = new Test(); + + test.test = 'aa'; + test.save(function (err) { + should.strictEqual(err, null); + + Test.findById(test._id, function (err, doc) { + should.strictEqual(err, null); + + doc.test.should.eql('aa'); + + conn.close(); + }); + }); + }); + }, + + 'test public exports': function () { + mongoose.version.should.be.a('string'); + mongoose.Collection.should.be.a('function'); + mongoose.Connection.should.be.a('function'); + mongoose.Schema.should.be.a('function'); + mongoose.SchemaType.should.be.a('function'); + mongoose.Query.should.be.a('function'); + mongoose.Promise.should.be.a('function'); + mongoose.Model.should.be.a('function'); + mongoose.Document.should.be.a('function'); + } + +}; diff --git a/node_modules/mongoose/test/model.querying.test.js b/node_modules/mongoose/test/model.querying.test.js new file mode 100644 index 0000000..e1ccf8f --- /dev/null +++ b/node_modules/mongoose/test/model.querying.test.js @@ -0,0 +1,1986 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Query = require('../lib/query') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , CastError = SchemaType.CastError + , ObjectId = Schema.ObjectId + , MongooseBuffer = mongoose.Types.Buffer + , DocumentObjectId = mongoose.Types.ObjectId; + +/** + * Setup. + */ + +var Comments = new Schema(); + +Comments.add({ + title : String + , date : Date + , body : String + , comments : [Comments] +}); + +var BlogPostB = new Schema({ + title : String + , author : String + , slug : String + , date : Date + , meta : { + date : Date + , visitors : Number + } + , published : Boolean + , mixed : {} + , numbers : [Number] + , tags : [String] + , sigs : [Buffer] + , owners : [ObjectId] + , comments : [Comments] + , def : { type: String, default: 'kandinsky' } +}); + +mongoose.model('BlogPostB', BlogPostB); +var collection = 'blogposts_' + random(); + +var ModSchema = new Schema({ + num: Number +}); +mongoose.model('Mod', ModSchema); + +var geoSchema = new Schema({ loc: { type: [Number], index: '2d'}}); + +module.exports = { + + 'test that find returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + // query + BlogPostB.find({}).should.be.an.instanceof(Query); + + // query, fields + BlogPostB.find({}, {}).should.be.an.instanceof(Query); + + // query, fields (array) + BlogPostB.find({}, []).should.be.an.instanceof(Query); + + // query, fields, options + BlogPostB.find({}, {}, {}).should.be.an.instanceof(Query); + + // query, fields (array), options + BlogPostB.find({}, [], {}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that findOne returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + // query + BlogPostB.findOne({}).should.be.an.instanceof(Query); + + // query, fields + BlogPostB.findOne({}, {}).should.be.an.instanceof(Query); + + // query, fields (array) + BlogPostB.findOne({}, []).should.be.an.instanceof(Query); + + // query, fields, options + BlogPostB.findOne({}, {}, {}).should.be.an.instanceof(Query); + + // query, fields (array), options + BlogPostB.findOne({}, [], {}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that an empty find does not hang': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + + function fn () { + db.close(); + }; + + BlogPostB.find({}, fn); + }, + + 'test that a query is executed when a callback is passed': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , count = 5 + , q = { _id: new DocumentObjectId }; // make sure the query is fast + + function fn () { + --count || db.close(); + }; + + // query + BlogPostB.find(q, fn).should.be.an.instanceof(Query); + + // query, fields + BlogPostB.find(q, {}, fn).should.be.an.instanceof(Query); + + // query, fields (array) + BlogPostB.find(q, [], fn).should.be.an.instanceof(Query); + + // query, fields, options + BlogPostB.find(q, {}, {}, fn).should.be.an.instanceof(Query); + + // query, fields (array), options + BlogPostB.find(q, [], {}, fn).should.be.an.instanceof(Query); + }, + + 'test that query is executed where a callback for findOne': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , count = 5 + , q = { _id: new DocumentObjectId }; // make sure the query is fast + + function fn () { + --count || db.close(); + }; + + // query + BlogPostB.findOne(q, fn).should.be.an.instanceof(Query); + + // query, fields + BlogPostB.findOne(q, {}, fn).should.be.an.instanceof(Query); + + // query, fields (array) + BlogPostB.findOne(q, [], fn).should.be.an.instanceof(Query); + + // query, fields, options + BlogPostB.findOne(q, {}, {}, fn).should.be.an.instanceof(Query); + + // query, fields (array), options + BlogPostB.findOne(q, [], {}, fn).should.be.an.instanceof(Query); + }, + + 'test that count returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.count({}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that count Query executes when you pass a callback': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , pending = 2 + + function fn () { + if (--pending) return; + db.close(); + }; + + BlogPostB.count({}, fn).should.be.an.instanceof(Query); + BlogPostB.count(fn).should.be.an.instanceof(Query); + }, + + 'test that distinct returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.distinct('title', {}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that distinct Query executes when you pass a callback': function () { + var db = start(); + var Address = new Schema({ zip: String }); + Address = db.model('Address', Address, 'addresses_' + random()); + + Address.create({ zip: '10010'}, { zip: '10010'}, { zip: '99701'}, function (err, a1, a2, a3) { + should.strictEqual(null, err); + var query = Address.distinct('zip', {}, function (err, results) { + should.strictEqual(null, err); + results.should.eql(['10010', '99701']); + db.close(); + }); + query.should.be.an.instanceof(Query); + }); + }, + + + 'test that update returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.update({}, {}).should.be.an.instanceof(Query); + BlogPostB.update({}, {}, {}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that update Query executes when you pass a callback': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , count = 2; + + function fn () { + --count || db.close(); + }; + + BlogPostB.update({title: random()}, {}, fn).should.be.an.instanceof(Query); + + BlogPostB.update({title: random()}, {}, {}, fn).should.be.an.instanceof(Query); + }, + + 'test finding a document': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Wooooot ' + random(); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ title: title }, function (err, doc) { + should.strictEqual(err, null); + doc.get('title').should.eql(title); + doc.isNew.should.be.false; + + db.close(); + }); + }); + }, + + 'test finding a document byId': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Edwald ' + random(); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + var pending = 2; + + BlogPostB.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + doc.should.be.an.instanceof(BlogPostB); + doc.get('title').should.eql(title); + --pending || db.close(); + }); + + BlogPostB.findById(post.get('_id').toHexString(), function (err, doc) { + should.strictEqual(err, null); + doc.should.be.an.instanceof(BlogPostB); + doc.get('title').should.eql(title); + --pending || db.close(); + }); + }); + }, + + 'test finding documents': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Wooooot ' + random(); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.find({ title: title }, function (err, docs) { + should.strictEqual(err, null); + docs.should.have.length(2); + + docs[0].get('title').should.eql(title); + docs[0].isNew.should.be.false; + + docs[1].get('title').should.eql(title); + docs[1].isNew.should.be.false; + + db.close(); + }); + }); + }); + }, + + 'test finding documents where an array that contains one specific member': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + BlogPostB.create({numbers: [100, 101, 102]}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.find({numbers: 100}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(created._id); + db.close(); + }); + }); + }, + + 'test counting documents': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Wooooot ' + random(); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.count({ title: title }, function (err, count) { + should.strictEqual(err, null); + + count.should.be.a('number'); + count.should.eql(2); + + db.close(); + }); + }); + }); + }, + + 'test query casting': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Loki ' + random(); + + var post = new BlogPostB() + , id = DocumentObjectId.toString(post.get('_id')); + + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ _id: id }, function (err, doc) { + should.strictEqual(err, null); + + doc.get('title').should.equal(title); + db.close(); + }); + }); + }, + + 'test a query that includes a casting error': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.find({ date: 'invalid date' }, function (err) { + err.should.be.an.instanceof(Error); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test findOne queries that require casting for $modifiers': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB({ + meta: { + visitors: -10 + } + }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ 'meta.visitors': { $gt: '-20', $lt: -1 } }, + function (err, found) { + found.get('meta.visitors') + .valueOf().should.equal(post.get('meta.visitors').valueOf()); + found.id; + found.get('_id').should.eql(post.get('_id')); + db.close(); + }); + }); + }, + + 'test find queries that require casting for $modifiers': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB({ + meta: { + visitors: -75 + } + }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.find({ 'meta.visitors': { $gt: '-100', $lt: -50 } }, + function (err, found) { + should.strictEqual(err, null); + + found.should.have.length(1); + found[0].get('_id').should.eql(post.get('_id')); + found[0].get('meta.visitors').valueOf() + .should.equal(post.get('meta.visitors').valueOf()); + db.close(); + }); + }); + }, + + // GH-199 + 'test find queries where $in cast the values wherein the array': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + var post = new BlogPostB() + , id = DocumentObjectId.toString(post._id); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ _id: { $in: [id] } }, function (err, doc) { + should.strictEqual(err, null); + + DocumentObjectId.toString(doc._id).should.eql(id); + db.close(); + }); + }); + }, + + // GH-232 + 'test find queries where $nin cast the values wherein the array': function () { + var db = start() + , NinSchema = new Schema({ + num: Number + }); + mongoose.model('Nin', NinSchema); + var Nin = db.model('Nin', 'nins_' + random()); + Nin.create({ num: 1 }, function (err, one) { + should.strictEqual(err, null); + Nin.create({ num: 2 }, function (err, two) { + should.strictEqual(err, null); + Nin.create({num: 3}, function (err, three) { + should.strictEqual(err, null); + Nin.find({ num: {$nin: [2]}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + db.close(); + }); + }); + }); + }); + }, + + 'test find queries with $ne with single value against array': function () { + var db = start(); + var schema = new Schema({ + ids: [Schema.ObjectId] + , b: Schema.ObjectId + }); + + var NE = db.model('NE_Test', schema, 'nes__' + random()); + + var id1 = new DocumentObjectId; + var id2 = new DocumentObjectId; + var id3 = new DocumentObjectId; + var id4 = new DocumentObjectId; + + NE.create({ ids: [id1, id4], b: id3 }, function (err, ne1) { + should.strictEqual(err, null); + NE.create({ ids: [id2, id4], b: id3 },function (err, ne2) { + should.strictEqual(err, null); + + var query = NE.find({ 'b': id3.toString(), 'ids': { $ne: id1 }}); + query.run(function (err, nes1) { + should.strictEqual(err, null); + nes1.length.should.eql(1); + + NE.find({ b: { $ne: [1] }}, function (err, nes2) { + err.message.should.eql("Invalid ObjectId"); + + NE.find({ b: { $ne: 4 }}, function (err, nes3) { + err.message.should.eql("Invalid ObjectId"); + + NE.find({ b: id3, ids: { $ne: id4 }}, function (err, nes4) { + db.close(); + should.strictEqual(err, null); + nes4.length.should.eql(0); + }); + }); + }); + }); + + }); + }); + + }, + + 'test for findById where partial initialization': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , queries = 5; + + var post = new BlogPostB(); + + post.title = 'hahaha'; + post.slug = 'woot'; + post.meta.visitors = 53; + post.tags = ['humidity', 'soggy']; + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.true; + doc.isInit('slug').should.be.true; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.true; + doc.meta.visitors.valueOf().should.equal(53); + doc.tags.length.should.equal(2); + --queries || db.close(); + }); + + BlogPostB.findById(post.get('_id'), ['title'], function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.true; + doc.isInit('slug').should.be.false; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.false; + should.strictEqual(undefined, doc.meta.visitors); + should.strictEqual(undefined, doc.tags); + --queries || db.close(); + }); + + BlogPostB.findById(post.get('_id'), { slug: 0 }, function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.true; + doc.isInit('slug').should.be.false; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.true; + doc.meta.visitors.valueOf().should.equal(53); + doc.tags.length.should.equal(2); + --queries || db.close(); + }); + + BlogPostB.findById(post.get('_id'), { title:1 }, function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.true; + doc.isInit('slug').should.be.false; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.false; + should.strictEqual(undefined, doc.meta.visitors); + should.strictEqual(undefined, doc.tags); + --queries || db.close(); + }); + + BlogPostB.findById(post.get('_id'), ['slug'], function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.false; + doc.isInit('slug').should.be.true; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.false; + should.strictEqual(undefined, doc.meta.visitors); + should.strictEqual(undefined, doc.tags); + --queries || db.close(); + }); + }); + }, + + 'findOne where subset of fields excludes _id': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + BlogPostB.create({title: 'subset 1'}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findOne({title: 'subset 1'}, {title: 1, _id: 0}, function (err, found) { + should.strictEqual(err, null); + should.strictEqual(undefined, found._id); + found.title.should.equal('subset 1'); + db.close(); + }); + }); + }, + + 'test find where subset of fields, excluding _id': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + BlogPostB.create({title: 'subset 1', author: 'me'}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.find({title: 'subset 1'}, {title: 1, _id: 0}, function (err, found) { + should.strictEqual(err, null); + should.strictEqual(undefined, found[0]._id); + found[0].title.should.equal('subset 1'); + should.strictEqual(undefined, found[0].def); + should.strictEqual(undefined, found[0].author); + should.strictEqual(false, Array.isArray(found[0].comments)); + db.close(); + }); + }); + }, + + // gh-541 + 'find subset of fields excluding embedded doc _id': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: 'LOTR', comments: [{ title: ':)' }]}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.find({_id: created}, { _id: 0, 'comments._id': 0 }, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(undefined, found[0]._id); + found[0].title.should.equal('LOTR'); + should.strictEqual('kandinsky', found[0].def); + should.strictEqual(undefined, found[0].author); + should.strictEqual(true, Array.isArray(found[0].comments)); + found[0].comments.length.should.equal(1); + found[0].comments[0].title.should.equal(':)'); + should.strictEqual(undefined, found[0].comments[0]._id); + // gh-590 + should.strictEqual(null, found[0].comments[0].id); + }); + }); + }, + + + 'exluded fields should be undefined': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , date = new Date + + BlogPostB.create({title: 'subset 1', author: 'me', meta: { date: date }}, function (err, created) { + should.strictEqual(err, null); + var id = created.id; + BlogPostB.findById(created.id, {title: 0, 'meta.date': 0, owners: 0}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + should.strictEqual(undefined, found.title); + should.strictEqual('kandinsky', found.def); + should.strictEqual('me', found.author); + should.strictEqual(true, Array.isArray(found.comments)); + should.equal(undefined, found.meta.date); + found.comments.length.should.equal(0); + should.equal(undefined, found.owners); + }); + }); + }, + + 'exluded fields should be undefined and defaults applied to other fields': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , id = new DocumentObjectId + , date = new Date + + BlogPostB.collection.insert({ _id: id, title: 'hahaha1', meta: { date: date }}, function (err) { + should.strictEqual(err, null); + + BlogPostB.findById(id, {title: 0}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found._id.should.eql(id); + should.strictEqual(undefined, found.title); + should.strictEqual('kandinsky', found.def); + should.strictEqual(undefined, found.author); + should.strictEqual(true, Array.isArray(found.comments)); + should.equal(date.toString(), found.meta.date.toString()); + found.comments.length.should.equal(0); + }); + }); + }, + + 'test for find where partial initialization': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , queries = 4; + + var post = new BlogPostB(); + + post.title = 'hahaha'; + post.slug = 'woot'; + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.find({ _id: post.get('_id') }, function (err, docs) { + should.strictEqual(err, null); + docs[0].isInit('title').should.be.true; + docs[0].isInit('slug').should.be.true; + docs[0].isInit('date').should.be.false; + should.strictEqual('kandinsky', docs[0].def); + --queries || db.close(); + }); + + BlogPostB.find({ _id: post.get('_id') }, ['title'], function (err, docs) { + should.strictEqual(err, null); + docs[0].isInit('title').should.be.true; + docs[0].isInit('slug').should.be.false; + docs[0].isInit('date').should.be.false; + should.strictEqual(undefined, docs[0].def); + --queries || db.close(); + }); + + BlogPostB.find({ _id: post.get('_id') }, { slug: 0, def: 0 }, function (err, docs) { + should.strictEqual(err, null); + docs[0].isInit('title').should.be.true; + docs[0].isInit('slug').should.be.false; + docs[0].isInit('date').should.be.false; + should.strictEqual(undefined, docs[0].def); + --queries || db.close(); + }); + + BlogPostB.find({ _id: post.get('_id') }, ['slug'], function (err, docs) { + should.strictEqual(err, null); + docs[0].isInit('title').should.be.false; + docs[0].isInit('slug').should.be.true; + docs[0].isInit('date').should.be.false; + should.strictEqual(undefined, docs[0].def); + --queries || db.close(); + }); + }); + }, + + // GH-204 + 'test query casting when finding by Date': function () { + var db = start() + , P = db.model('BlogPostB', collection); + + var post = new P; + + post.meta.date = new Date(); + + post.save(function (err) { + should.strictEqual(err, null); + + P.findOne({ _id: post._id, 'meta.date': { $lte: Date.now() } }, function (err, doc) { + should.strictEqual(err, null); + + DocumentObjectId.toString(doc._id).should.eql(DocumentObjectId.toString(post._id)); + doc.meta.date = null; + doc.save(function (err) { + should.strictEqual(err, null); + P.findById(doc._id, function (err, doc) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(doc.meta.date, null); + }); + }); + }); + }); + }, + + // gh-523 + 'null boolean default is allowed': function () { + var db = start() + , s1 = new Schema({ b: { type: Boolean, default: null }}) + , M1 = db.model('NullDateDefaultIsAllowed1', s1) + , s2 = new Schema({ b: { type: Boolean, default: false }}) + , M2 = db.model('NullDateDefaultIsAllowed2', s2) + , s3 = new Schema({ b: { type: Boolean, default: true }}) + , M3 = db.model('NullDateDefaultIsAllowed3', s3) + + db.close(); + + var m1 = new M1; + should.strictEqual(null, m1.b); + var m2 = new M2; + should.strictEqual(false, m2.b); + var m3 = new M3; + should.strictEqual(true, m3.b); + }, + + // GH-220 + 'test querying if an array contains at least a certain single member': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + var post = new BlogPostB(); + + post.tags.push('cat'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({tags: 'cat'}, function (err, doc) { + should.strictEqual(err, null); + + doc.id; + doc._id.should.eql(post._id); + db.close(); + }); + }); + }, + + 'test querying if an array contains one of multiple members $in a set': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + var post = new BlogPostB(); + + post.tags.push('football'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({tags: {$in: ['football', 'baseball']}}, function (err, doc) { + should.strictEqual(err, null); + doc.id; + doc._id.should.eql(post._id); + + BlogPostB.findOne({ _id: post._id, tags: /otba/i }, function (err, doc) { + should.strictEqual(err, null); + doc.id; + doc._id.should.eql(post._id); + + db.close(); + }) + }); + }); + }, + + 'test querying if an array contains one of multiple members $in a set 2': function () { + var db = start() + , BlogPostA = db.model('BlogPostB', collection) + + var post = new BlogPostA({ tags: ['gooberOne'] }); + + post.save(function (err) { + should.strictEqual(err, null); + + var query = {tags: {$in:[ 'gooberOne' ]}}; + + BlogPostA.findOne(query, function (err, returned) { + done(); + should.strictEqual(err, null); + ;(!!~returned.tags.indexOf('gooberOne')).should.be.true; + returned.id; + returned._id.should.eql(post._id); + }); + }); + + post.collection.insert({ meta: { visitors: 9898, a: null } }, {}, function (err, b) { + should.strictEqual(err, null); + + BlogPostA.findOne({_id: b[0]._id}, function (err, found) { + done(); + should.strictEqual(err, null); + found.get('meta.visitors').valueOf().should.eql(9898); + }) + }); + + var pending = 2; + function done () { + if (--pending) return; + db.close(); + } + }, + + 'test querying via $where a string': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({ title: 'Steve Jobs', author: 'Steve Jobs'}, function (err, created) { + should.strictEqual(err, null); + + BlogPostB.findOne({ $where: "this.title && this.title === this.author" }, function (err, found) { + should.strictEqual(err, null); + + found.id; + found._id.should.eql(created._id); + db.close(); + }); + }); + }, + + 'test querying via $where a function': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({ author: 'Atari', slug: 'Atari'}, function (err, created) { + should.strictEqual(err, null); + + BlogPostB.findOne({ $where: function () { + return (this.author && this.slug && this.author === this.slug); + } }, function (err, found) { + should.strictEqual(err, null); + + found.id; + found._id.should.eql(created._id); + db.close(); + }); + }); + }, + + 'test find where $exists': function () { + var db = start() + , ExistsSchema = new Schema({ + a: Number + , b: String + }); + mongoose.model('Exists', ExistsSchema); + var Exists = db.model('Exists', 'exists_' + random()); + Exists.create({ a: 1}, function (err, aExisting) { + should.strictEqual(err, null); + Exists.create({b: 'hi'}, function (err, bExisting) { + should.strictEqual(err, null); + Exists.find({b: {$exists: true}}, function (err, docs) { + should.strictEqual(err, null); + db.close(); + docs.should.have.length(1); + }); + }); + }); + }, + + 'test finding based on nested fields': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB({ + meta: { + visitors: 5678 + } + }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ 'meta.visitors': 5678 }, function (err, found) { + should.strictEqual(err, null); + found.get('meta.visitors') + .valueOf().should.equal(post.get('meta.visitors').valueOf()); + found.id; + found.get('_id').should.eql(post.get('_id')); + db.close(); + }); + }); + }, + + // GH-242 + 'test finding based on embedded document fields': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({comments: [{title: 'i should be queryable'}], numbers: [1,2,33333], tags:['yes', 'no']}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findOne({'comments.title': 'i should be queryable'}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + BlogPostB.findOne({'comments.0.title': 'i should be queryable'}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + // GH-463 + BlogPostB.findOne({'numbers.2': 33333}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + BlogPostB.findOne({'tags.1': 'no'}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + db.close(); + }); + }); + }); + }); + }); + }, + + // GH-389 + 'find nested doc using string id': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({comments: [{title: 'i should be queryable by _id'}, {title:'me too me too!'}]}, function (err, created) { + should.strictEqual(err, null); + var id = created.comments[1]._id.toString(); + BlogPostB.findOne({'comments._id': id}, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(!! found, true, 'Find by nested doc id hex string fails'); + found.id; + found._id.should.eql(created._id); + }); + }); + }, + + 'test finding where $elemMatch': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , dateAnchor = +new Date; + + BlogPostB.create({comments: [{title: 'elemMatch', date: dateAnchor + 5}]}, function (err, createdAfter) { + should.strictEqual(err, null); + BlogPostB.create({comments: [{title: 'elemMatch', date: dateAnchor - 5}]}, function (err, createdBefore) { + should.strictEqual(err, null); + BlogPostB.find({'comments': {'$elemMatch': {title: 'elemMatch', date: {$gt: dateAnchor}}}}, + function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(createdAfter._id); + db.close(); + } + ); + }); + }); + }, + + 'test finding where $mod': function () { + var db = start() + , Mod = db.model('Mod', 'mods_' + random()); + Mod.create({num: 1}, function (err, one) { + should.strictEqual(err, null); + Mod.create({num: 2}, function (err, two) { + should.strictEqual(err, null); + Mod.find({num: {$mod: [2, 1]}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(one._id); + db.close(); + }); + }); + }); + }, + + 'test finding where $not': function () { + var db = start() + , Mod = db.model('Mod', 'mods_' + random()); + Mod.create({num: 1}, function (err, one) { + should.strictEqual(err, null); + Mod.create({num: 2}, function (err, two) { + should.strictEqual(err, null); + Mod.find({num: {$not: {$mod: [2, 1]}}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(two._id); + db.close(); + }); + }); + }); + }, + + 'test finding where $or': function () { + var db = start() + , Mod = db.model('Mod', 'mods_' + random()); + + Mod.create({num: 1}, {num: 2, str: 'two'}, function (err, one, two) { + should.strictEqual(err, null); + + var pending = 3; + test1(); + test2(); + test3(); + + function test1 () { + Mod.find({$or: [{num: 1}, {num: 2}]}, function (err, found) { + done(); + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(one._id); + found[1]._id.should.eql(two._id); + }); + } + + function test2 () { + Mod.find({ $or: [{ str: 'two'}, {str:'three'}] }, function (err, found) { + if (err) console.error(err); + done(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(two._id); + }); + } + + function test3 () { + Mod.find({$or: [{num: 1}]}).$or([{ str: 'two' }]).run(function (err, found) { + if (err) console.error(err); + done(); + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(one._id); + found[1]._id.should.eql(two._id); + }); + } + + function done () { + if (--pending) return; + db.close(); + } + }); + }, + + 'test finding where $ne': function () { + var db = start() + , Mod = db.model('Mod', 'mods_' + random()); + Mod.create({num: 1}, function (err, one) { + should.strictEqual(err, null); + Mod.create({num: 2}, function (err, two) { + should.strictEqual(err, null); + Mod.create({num: 3}, function (err, three) { + should.strictEqual(err, null); + Mod.find({num: {$ne: 1}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(two._id); + found[1]._id.should.eql(three._id); + db.close(); + }); + }); + }); + }); + }, + + 'test finding null matches null and undefined': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection + random()); + + BlogPostB.create( + { title: 'A', author: null } + , { title: 'B' }, function (err, createdA, createdB) { + should.strictEqual(err, null); + BlogPostB.find({author: null}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(2); + }); + }); + }, + + 'test finding STRICT null matches': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection + random()); + + BlogPostB.create( + { title: 'A', author: null} + , { title: 'B' }, function (err, createdA, createdB) { + should.strictEqual(err, null); + BlogPostB.find({author: {$in: [null], $exists: true}}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(createdA._id); + }); + }); + }, + + 'setting a path to undefined should retain the value as undefined': function () { + var db = start() + , B = db.model('BlogPostB', collection + random()) + + var doc = new B; + doc.title='css3'; + doc._delta().$set.title.should.equal('css3'); + doc.title = undefined; + doc._delta().$unset.title.should.equal(1); + should.strictEqual(undefined, doc._delta().$set); + + doc.title='css3'; + doc.author = 'aaron'; + doc.numbers = [3,4,5]; + doc.meta.date = new Date; + doc.meta.visitors = 89; + doc.comments = [{ title: 'thanksgiving', body: 'yuuuumm' }]; + doc.comments.push({ title: 'turkey', body: 'cranberries' }); + + doc.save(function (err) { + should.strictEqual(null, err); + B.findById(doc._id, function (err, b) { + should.strictEqual(null, err); + b.title.should.equal('css3'); + b.author.should.equal('aaron'); + should.equal(b.meta.date.toString(), doc.meta.date.toString()); + b.meta.visitors.valueOf().should.equal(doc.meta.visitors.valueOf()); + b.comments.length.should.equal(2); + b.comments[0].title.should.equal('thanksgiving'); + b.comments[0].body.should.equal('yuuuumm'); + b.comments[1].title.should.equal('turkey'); + b.comments[1].body.should.equal('cranberries'); + b.title = undefined; + b.author = null; + b.meta.date = undefined; + b.meta.visitors = null; + b.comments[0].title = null; + b.comments[0].body = undefined; + b.save(function (err) { + should.strictEqual(null, err); + B.findById(b._id, function (err, b) { + should.strictEqual(null, err); + should.strictEqual(undefined, b.title); + should.strictEqual(null, b.author); + + should.strictEqual(undefined, b.meta.date); + should.strictEqual(null, b.meta.visitors); + should.strictEqual(null, b.comments[0].title); + should.strictEqual(undefined, b.comments[0].body); + b.comments[1].title.should.equal('turkey'); + b.comments[1].body.should.equal('cranberries'); + + b.meta = undefined; + b.save(function (err) { + should.strictEqual(null, err); + B.collection.findOne({ _id: b._id}, function (err, b) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(undefined, b.meta); + }); + }); + }); + }); + }); + }); + }, + + 'test finding strings via regular expressions': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: 'Next to Normal'}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findOne({title: /^Next/}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + var reg = '^Next to Normal$'; + + BlogPostB.find({ title: { $regex: reg }}, function (err, found) { + should.strictEqual(err, null); + found.length.should.equal(1); + found[0].id; + found[0]._id.should.eql(created._id); + + BlogPostB.findOne({ title: { $regex: reg }}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + BlogPostB.where('title').$regex(reg).findOne(function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + BlogPostB.where('title').$regex(/^Next/).findOne(function (err, found) { + db.close(); + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + }); + }); + }); + }); + }); + }); + }, + + 'test finding a document whose arrays contain at least $all values': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create( + {numbers: [-1,-2,-3,-4], meta: { visitors: 4 }} + , {numbers: [0,-1,-2,-3,-4]} + , function (err, whereoutZero, whereZero) { + should.strictEqual(err, null); + + BlogPostB.find({numbers: {$all: [-1, -2, -3, -4]}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + BlogPostB.find({'meta.visitors': {$all: [4] }}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(whereoutZero._id); + BlogPostB.find({numbers: {$all: [0, -1]}}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(whereZero._id); + }); + }); + }); + }); + }, + + 'test finding a document whose arrays contain at least $all string values': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + var post = new BlogPostB({ title: "Aristocats" }); + + post.tags.push('onex'); + post.tags.push('twox'); + post.tags.push('threex'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findById(post._id, function (err, post) { + should.strictEqual(err, null); + + BlogPostB.find({ title: { '$all': ['Aristocats']}}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + + BlogPostB.find({ title: { '$all': [/^Aristocats/]}}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + + BlogPostB.find({tags: { '$all': ['onex','twox','threex']}}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + + BlogPostB.find({tags: { '$all': [/^onex/i]}}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + + BlogPostB.findOne({tags: { '$all': /^two/ }}, function (err, doc) { + db.close(); + should.strictEqual(err, null); + doc.id.should.eql(post.id); + }); + }); + }); + }); + }); + }); + + }); + }, + + 'test finding documents where an array of a certain $size': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({numbers: [1,2,3,4,5,6,7,8,9,10]}, function (err, whereoutZero) { + should.strictEqual(err, null); + BlogPostB.create({numbers: [11,12,13,14,15,16,17,18,19,20]}, function (err, whereZero) { + should.strictEqual(err, null); + BlogPostB.create({numbers: [1,2,3,4,5,6,7,8,9,10,11]}, function (err, found) { + BlogPostB.find({numbers: {$size: 10}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + BlogPostB.find({numbers: {$size: 11}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + db.close(); + }); + }); + }); + }); + }); + }, + + 'test finding documents where an array where the $slice operator': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({numbers: [500,600,700,800]}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findById(created._id, {numbers: {$slice: 2}}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + found.numbers.should.have.length(2); + found.numbers[0].should.equal(500); + found.numbers[1].should.equal(600); + BlogPostB.findById(created._id, {numbers: {$slice: -2}}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + found.numbers.should.have.length(2); + found.numbers[0].should.equal(700); + found.numbers[1].should.equal(800); + BlogPostB.findById(created._id, {numbers: {$slice: [1, 2]}}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + found.numbers.should.have.length(2); + found.numbers[0].should.equal(600); + found.numbers[1].should.equal(700); + db.close(); + }); + }); + }); + }); + }, + + 'test finding documents with a specifc Buffer in their array': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({sigs: [new Buffer([1, 2, 3]), + new Buffer([4, 5, 6]), + new Buffer([7, 8, 9])]}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findOne({sigs: new Buffer([1, 2, 3])}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + var query = { sigs: { "$in" : [new Buffer([3, 3, 3]), new Buffer([4, 5, 6])] } }; + BlogPostB.findOne(query, function (err, found) { + should.strictEqual(err, null); + db.close(); + }); + }); + }); + }, + + 'test limits': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: 'first limit'}, function (err, first) { + should.strictEqual(err, null); + BlogPostB.create({title: 'second limit'}, function (err, second) { + should.strictEqual(err, null); + BlogPostB.create({title: 'third limit'}, function (err, third) { + should.strictEqual(err, null); + BlogPostB.find({title: /limit$/}).limit(2).find( function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(first._id); + found[1]._id.should.eql(second._id); + db.close(); + }); + }); + }); + }); + }, + + 'test skips': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: 'first skip'}, function (err, first) { + should.strictEqual(err, null); + BlogPostB.create({title: 'second skip'}, function (err, second) { + should.strictEqual(err, null); + BlogPostB.create({title: 'third skip'}, function (err, third) { + should.strictEqual(err, null); + BlogPostB.find({title: /skip$/}).skip(1).limit(2).find( function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(second._id); + found[1]._id.should.eql(third._id); + db.close(); + }); + }); + }); + }); + }, + + 'test sorts': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({meta: {visitors: 100}}, function (err, least) { + should.strictEqual(err, null); + BlogPostB.create({meta: {visitors: 300}}, function (err, largest) { + should.strictEqual(err, null); + BlogPostB.create({meta: {visitors: 200}}, function (err, middle) { + should.strictEqual(err, null); + BlogPostB + .where('meta.visitors').gt(99).lt(301) + .sort('meta.visitors', -1) + .find( function (err, found) { + should.strictEqual(err, null); + found.should.have.length(3); + found[0]._id.should.eql(largest._id); + found[1]._id.should.eql(middle._id); + found[2]._id.should.eql(least._id); + db.close(); + }); + }); + }); + }); + }, + + 'test backwards compatibility with previously existing null values in db': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB(); + + post.collection.insert({ meta: { visitors: 9898, a: null } }, {}, function (err, b) { + should.strictEqual(err, null); + + BlogPostB.findOne({_id: b[0]._id}, function (err, found) { + should.strictEqual(err, null); + found.get('meta.visitors').valueOf().should.eql(9898); + db.close(); + }) + }) + }, + + 'test backwards compatibility with unused values in db': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB(); + + post.collection.insert({ meta: { visitors: 9898, color: 'blue'}}, {}, function (err, b) { + should.strictEqual(err, null); + + BlogPostB.findOne({_id: b[0]._id}, function (err, found) { + should.strictEqual(err, null); + found.get('meta.visitors').valueOf().should.eql(9898); + found.save(function (err) { + should.strictEqual(err, null); + db.close(); + }) + }) + }) + }, + + 'test streaming cursors with #each': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: "The Wrestler", tags: ["movie"]}, function (err, wrestler) { + should.strictEqual(err, null); + BlogPostB.create({title: "Black Swan", tags: ["movie"]}, function (err, blackswan) { + should.strictEqual(err, null); + BlogPostB.create({title: "Pi", tags: ["movie"]}, function (err, pi) { + should.strictEqual(err, null); + var found = {}; + BlogPostB + .find({tags: "movie"}) + .sort('title', -1) + .each(function (err, post) { + should.strictEqual(err, null); + if (post) found[post.title] = 1; + else { + found.should.have.property("The Wrestler", 1); + found.should.have.property("Black Swan", 1); + found.should.have.property("Pi", 1); + db.close(); + } + }); + }); + }); + }); + }, + + 'test streaming cursors with #each and manual iteration': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: "Bleu", tags: ["Krzysztof Kieślowski"]}, function (err, wrestler) { + should.strictEqual(err, null); + BlogPostB.create({title: "Blanc", tags: ["Krzysztof Kieślowski"]}, function (err, blackswan) { + should.strictEqual(err, null); + BlogPostB.create({title: "Rouge", tags: ["Krzysztof Kieślowski"]}, function (err, pi) { + should.strictEqual(err, null); + var found = {}; + BlogPostB + .find({tags: "Krzysztof Kieślowski"}) + .each(function (err, post, next) { + should.strictEqual(err, null); + if (post) { + found[post.title] = 1; + process.nextTick(next); + } else { + found.should.have.property("Bleu", 1); + found.should.have.property("Blanc", 1); + found.should.have.property("Rouge", 1); + db.close(); + } + }); + }); + }); + }); + }, + + '$gt, $lt, $lte, $gte work on strings': function () { + var db = start() + var D = db.model('D', new Schema({dt: String}), collection); + + D.create({ dt: '2011-03-30' }, done); + D.create({ dt: '2011-03-31' }, done); + D.create({ dt: '2011-04-01' }, done); + D.create({ dt: '2011-04-02' }, done); + + var pending = 3; + function done (err) { + if (err) db.close(); + should.strictEqual(err, null); + + if (--pending) return; + + pending = 2; + + D.find({ 'dt': { $gte: '2011-03-30', $lte: '2011-04-01' }}).sort('dt', 1).run(function (err, docs) { + if (--pending) db.close(); + should.strictEqual(err, null); + docs.length.should.eql(3); + docs[0].dt.should.eql('2011-03-30'); + docs[1].dt.should.eql('2011-03-31'); + docs[2].dt.should.eql('2011-04-01'); + docs.some(function (d) { return '2011-04-02' === d.dt }).should.be.false; + }); + + D.find({ 'dt': { $gt: '2011-03-30', $lt: '2011-04-02' }}).sort('dt', 1).run(function (err, docs) { + if (--pending) db.close(); + should.strictEqual(err, null); + docs.length.should.eql(2); + docs[0].dt.should.eql('2011-03-31'); + docs[1].dt.should.eql('2011-04-01'); + docs.some(function (d) { return '2011-03-30' === d.dt }).should.be.false; + docs.some(function (d) { return '2011-04-02' === d.dt }).should.be.false; + }); + } + }, + + 'nested mixed queries (x.y.z)': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.find({ 'mixed.nested.stuff': 'skynet' }, function (err, docs) { + db.close(); + should.strictEqual(err, null); + }); + }, + + // GH-336 + 'finding by Date field works': function () { + var db = start() + , Test = db.model('TestDateQuery', new Schema({ date: Date }), 'datetest_' + random()) + , now = new Date; + + Test.create({ date: now }, { date: new Date(now-10000) }, function (err, a, b) { + should.strictEqual(err, null); + Test.find({ date: now }, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(1); + }); + }); + }, + + // GH-309 + 'using $near with Arrays works (geo-spatial)': function () { + var db = start() + , Test = db.model('Geo1', geoSchema, 'geospatial'+random()); + + Test.create({ loc: [ 10, 20 ]}, { loc: [ 40, 90 ]}, function (err) { + should.strictEqual(err, null); + setTimeout(function () { + Test.find({ loc: { $near: [30, 40] }}, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(2); + }); + }, 700); + }); + }, + + // GH-586 + 'using $within with Arrays works (geo-spatial)': function () { + var db = start() + , Test = db.model('Geo2', geoSchema, collection + 'geospatial'); + + Test.create({ loc: [ 35, 50 ]}, { loc: [ -40, -90 ]}, function (err) { + should.strictEqual(err, null); + setTimeout(function () { + Test.find({ loc: { '$within': { '$box': [[30,40], [40,60]] }}}, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(1); + }); + }, 700); + }); + }, + + // GH-610 + 'using nearSphere with Arrays works (geo-spatial)': function () { + var db = start() + , Test = db.model('Geo3', geoSchema, "y"+random()); + + Test.create({ loc: [ 10, 20 ]}, { loc: [ 40, 90 ]}, function (err) { + should.strictEqual(err, null); + setTimeout(function () { + Test.find({ loc: { $nearSphere: [30, 40] }}, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(2); + }); + }, 700); + }); + }, + + 'using $maxDistance with Array works (geo-spatial)': function () { + var db = start() + , Test = db.model('Geo4', geoSchema, "x"+random()); + + Test.create({ loc: [ 20, 80 ]}, { loc: [ 25, 30 ]}, function (err, docs) { + should.strictEqual(!!err, false); + setTimeout(function () { + Test.find({ loc: { $near: [25, 31], $maxDistance: 1 }}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + Test.find({ loc: { $near: [25, 32], $maxDistance: 1 }}, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(0); + }); + }); + }, 500); + }); + }, + + '$type tests': function () { + var db = start() + , B = db.model('BlogPostB', collection); + + B.find({ title: { $type: "asd" }}, function (err, posts) { + err.message.should.eql("$type parameter must be Number"); + + B.find({ title: { $type: 2 }}, function (err, posts) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(Array.isArray(posts), true); + }); + }); + }, + + 'buffers find using available types': function () { + var db = start() + , BufSchema = new Schema({ name: String, block: Buffer }) + , Test = db.model('Buffer', BufSchema, "buffers"); + + var docA = { name: 'A', block: new Buffer('über') }; + var docB = { name: 'B', block: new Buffer("buffer shtuffs are neat") }; + var docC = { name: 'C', block: 'hello world' }; + + Test.create(docA, docB, docC, function (err, a, b, c) { + should.strictEqual(err, null); + b.block.toString('utf8').should.equal('buffer shtuffs are neat'); + a.block.toString('utf8').should.equal('über'); + c.block.toString('utf8').should.equal('hello world'); + + Test.findById(a._id, function (err, a) { + should.strictEqual(err, null); + a.block.toString('utf8').should.equal('über'); + + Test.findOne({ block: 'buffer shtuffs are neat' }, function (err, rb) { + should.strictEqual(err, null); + rb.block.toString('utf8').should.equal('buffer shtuffs are neat'); + + Test.findOne({ block: /buffer/i }, function (err, rb) { + err.message.should.eql('Cast to buffer failed for value "/buffer/i"') + Test.findOne({ block: [195, 188, 98, 101, 114] }, function (err, rb) { + should.strictEqual(err, null); + rb.block.toString('utf8').should.equal('über'); + + Test.findOne({ block: 'aGVsbG8gd29ybGQ=' }, function (err, rb) { + should.strictEqual(err, null); + should.strictEqual(rb, null); + + Test.findOne({ block: new Buffer('aGVsbG8gd29ybGQ=', 'base64') }, function (err, rb) { + should.strictEqual(err, null); + rb.block.toString('utf8').should.equal('hello world'); + + Test.findOne({ block: new MongooseBuffer('aGVsbG8gd29ybGQ=', 'base64') }, function (err, rb) { + should.strictEqual(err, null); + rb.block.toString('utf8').should.equal('hello world'); + + Test.remove({}, function (err) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + }); + }); + }); + }); + + }); + }, + + 'buffer tests using conditionals': function () { + // $in $nin etc + var db = start() + , BufSchema = new Schema({ name: String, block: Buffer }) + , Test = db.model('Buffer2', BufSchema, "buffer_"+random()); + + var docA = { name: 'A', block: new MongooseBuffer([195, 188, 98, 101, 114]) }; //über + var docB = { name: 'B', block: new MongooseBuffer("buffer shtuffs are neat") }; + var docC = { name: 'C', block: new MongooseBuffer('aGVsbG8gd29ybGQ=', 'base64') }; + + Test.create(docA, docB, docC, function (err, a, b, c) { + should.strictEqual(err, null); + a.block.toString('utf8').should.equal('über'); + b.block.toString('utf8').should.equal('buffer shtuffs are neat'); + c.block.toString('utf8').should.equal('hello world'); + + Test.find({ block: { $in: [[195, 188, 98, 101, 114], "buffer shtuffs are neat", new Buffer('aGVsbG8gd29ybGQ=', 'base64')] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(3); + }); + + Test.find({ block: { $in: ['über', 'hello world'] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + }); + + Test.find({ block: { $in: ['über'] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(1); + tests[0].block.toString('utf8').should.equal('über'); + }); + + Test.find({ block: { $nin: ['über'] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + }); + + Test.find({ block: { $nin: [[195, 188, 98, 101, 114], new Buffer('aGVsbG8gd29ybGQ=', 'base64')] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(1); + tests[0].block.toString('utf8').should.equal('buffer shtuffs are neat'); + }); + + Test.find({ block: { $ne: 'über' }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + }); + + Test.find({ block: { $gt: 'über' }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + }); + + Test.find({ block: { $gte: 'über' }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(3); + }); + + Test.find({ block: { $lt: new Buffer('buffer shtuffs are neat') }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + tests[0].block.toString('utf8').should.equal('über'); + }); + + Test.find({ block: { $lte: 'buffer shtuffs are neat' }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(3); + }); + + var pending = 9; + function done () { + if (--pending) return; + Test.remove({}, function (err) { + db.close(); + should.strictEqual(err, null); + }); + } + }); + }, + + // gh-591 + 'querying Mixed types with elemMatch': function () { + var db = start() + , S = new Schema({ a: [{}], b: Number }) + , M = db.model('QueryingMixedArrays', S, random()) + + var m = new M; + m.a = [1,2,{ name: 'Frodo' },'IDK', {name: 100}]; + m.b = 10; + + m.save(function (err) { + should.strictEqual(null, err); + + M.find({ a: { name: 'Frodo' }, b: '10' }, function (err, docs) { + should.strictEqual(null, err); + docs[0].a.length.should.equal(5); + docs[0].b.valueOf().should.equal(10); + + var query = { + a: { + $elemMatch: { name: 100 } + } + } + + M.find(query, function (err, docs) { + db.close(); + should.strictEqual(null, err); + docs[0].a.length.should.equal(5); + }); + }); + }); + }, + + // gh-599 + 'regex with Array should work': function () { + var db = start() + , B = db.model('BlogPostB', random()) + + B.create({ tags: 'wooof baaaark meeeeow'.split(' ') }, function (err, b) { + should.strictEqual(null, err); + B.findOne({ tags: /ooof$/ }, function (err, doc) { + should.strictEqual(null, err); + should.strictEqual(true, !! doc); + ;(!! ~doc.tags.indexOf('meeeeow')).should.be.true; + + B.findOne({ tags: {$regex: 'eow$' } }, function (err, doc) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(true, !! doc); + ;(!! ~doc.tags.indexOf('meeeeow')).should.be.true; + }); + }); + }); + }, + + // gh-640 + 'updating a number to null': function () { + var db = start() + var B = db.model('BlogPostB') + var b = new B({ meta: { visitors: null }}); + b.save(function (err) { + should.strictEqual(null, err); + B.findById(b, function (err, b) { + should.strictEqual(null, err); + should.strictEqual(b.meta.visitors, null); + + B.update({ _id: b._id }, { meta: { visitors: null }}, function (err, docs) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + } +}; diff --git a/node_modules/mongoose/test/model.ref.test.js b/node_modules/mongoose/test/model.ref.test.js new file mode 100644 index 0000000..247770b --- /dev/null +++ b/node_modules/mongoose/test/model.ref.test.js @@ -0,0 +1,1444 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Schema = mongoose.Schema + , ObjectId = Schema.ObjectId + , DocObjectId = mongoose.Types.ObjectId + +/** + * Setup. + */ + +/** + * User schema. + */ + +var User = new Schema({ + name : String + , email : String + , gender : { type: String, enum: ['male', 'female'], default: 'male' } + , age : { type: Number, default: 21 } + , blogposts : [{ type: ObjectId, ref: 'RefBlogPost' }] +}); + +/** + * Comment subdocument schema. + */ + +var Comment = new Schema({ + _creator : { type: ObjectId, ref: 'RefUser' } + , content : String +}); + +/** + * Blog post schema. + */ + +var BlogPost = new Schema({ + _creator : { type: ObjectId, ref: 'RefUser' } + , title : String + , comments : [Comment] + , fans : [{ type: ObjectId, ref: 'RefUser' }] +}); + +var posts = 'blogposts_' + random() + , users = 'users_' + random(); + +mongoose.model('RefBlogPost', BlogPost); +mongoose.model('RefUser', User); + +/** + * Tests. + */ + +module.exports = { + + 'test populating a single reference': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users) + + User.create({ + name : 'Guillermo' + , email : 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.should.be.an.instanceof(User); + post._creator.name.should.equal('Guillermo'); + post._creator.email.should.equal('rauchg@gmail.com'); + }); + }); + }); + }, + + 'test an error in single reference population propagates': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts + '1') + , User = db.model('RefUser', users + '1'); + + User.create({ + name: 'Guillermo' + , email: 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + var origFind = User.findOne; + + // mock an error + User.findOne = function () { + var args = Array.prototype.map.call(arguments, function (arg) { + return 'function' == typeof arg ? function () { + arg(new Error('woot')); + } : arg; + }); + return origFind.apply(this, args); + }; + + BlogPost + .findById(post._id) + .populate('_creator') + .run(function (err, post) { + db.close(); + err.should.be.an.instanceof(Error); + err.message.should.equal('woot'); + }); + }); + }); + }, + + 'test populating with partial fields selection': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Guillermo' + , email : 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator', ['email']) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.should.be.an.instanceof(User); + post._creator.isInit('name').should.be.false; + post._creator.email.should.equal('rauchg@gmail.com'); + }); + }); + }); + }, + + 'populating single oid with partial field selection and filter': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Banana' + , email : 'cats@example.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator', 'email', { name: 'Peanut' }) + .run(function (err, post) { + should.strictEqual(err, null); + should.strictEqual(post._creator, null); + + BlogPost + .findById(post._id) + .populate('_creator', 'email', { name: 'Banana' }) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + post._creator.should.be.an.instanceof(User); + post._creator.isInit('name').should.be.false; + post._creator.email.should.equal('cats@example.com'); + }); + }); + }); + }); + }, + + 'test populating and changing a reference': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Guillermo' + , email : 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator') + .run(function (err, post) { + should.strictEqual(err, null); + + post._creator.should.be.an.instanceof(User); + post._creator.name.should.equal('Guillermo'); + post._creator.email.should.equal('rauchg@gmail.com'); + + User.create({ + name : 'Aaron' + , email : 'aaron@learnboost.com' + }, function (err, newCreator) { + should.strictEqual(err, null); + + post._creator = newCreator._id; + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.name.should.equal('Aaron'); + post._creator.email.should.equal('aaron@learnboost.com'); + }); + }); + }); + }); + }); + }); + }, + + 'test populating with partial fields selection and changing ref': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Guillermo' + , email : 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator', {'name': 1}) + .run(function (err, post) { + should.strictEqual(err, null); + + post._creator.should.be.an.instanceof(User); + post._creator.name.should.equal('Guillermo'); + + User.create({ + name : 'Aaron' + , email : 'aaron@learnboost.com' + }, function (err, newCreator) { + should.strictEqual(err, null); + + post._creator = newCreator._id; + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator', {email: 0}) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.name.should.equal('Aaron'); + should.not.exist(post._creator.email); + }); + }); + }); + }); + }); + }); + }, + + 'test populating an array of references and fetching many': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + }, function (err, fan2) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans') + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + blogposts[0].fans[0].name.should.equal('Fan 1'); + blogposts[0].fans[0].email.should.equal('fan1@learnboost.com'); + blogposts[0].fans[1].name.should.equal('Fan 2'); + blogposts[0].fans[1].email.should.equal('fan2@learnboost.com'); + + blogposts[1].fans[0].name.should.equal('Fan 2'); + blogposts[1].fans[0].email.should.equal('fan2@learnboost.com'); + blogposts[1].fans[1].name.should.equal('Fan 1'); + blogposts[1].fans[1].email.should.equal('fan1@learnboost.com'); + + }); + }); + }); + }); + }); + }, + + 'test an error in array reference population propagates': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts + '2') + , User = db.model('RefUser', users + '2'); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + }, function (err, fan2) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + // mock an error + var origFind = User.find; + User.find = function () { + var args = Array.prototype.map.call(arguments, function (arg) { + return 'function' == typeof arg ? function () { + arg(new Error('woot 2')); + } : arg; + }); + return origFind.apply(this, args); + }; + + BlogPost + .find({ $or: [{ _id: post1._id }, { _id: post2._id }] }) + .populate('fans') + .run(function (err, blogposts) { + db.close(); + err.should.be.an.instanceof(Error); + err.message.should.equal('woot 2'); + }); + }); + }); + }); + }); + }, + + 'test populating an array of references with fields selection': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + }, function (err, fan2) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', 'name') + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + blogposts[0].fans[0].name.should.equal('Fan 1'); + blogposts[0].fans[0].isInit('email').should.be.false; + blogposts[0].fans[1].name.should.equal('Fan 2'); + blogposts[0].fans[1].isInit('email').should.be.false; + should.strictEqual(blogposts[0].fans[1].email, undefined); + + blogposts[1].fans[0].name.should.equal('Fan 2'); + blogposts[1].fans[0].isInit('email').should.be.false; + blogposts[1].fans[1].name.should.equal('Fan 1'); + blogposts[1].fans[1].isInit('email').should.be.false; + }); + }); + }); + }); + }); + }, + + 'test populating an array of references and filtering': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, function (err, fan2) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 3' + , email : 'fan3@learnboost.com' + , gender : 'female' + }, function (err, fan3) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2, fan3] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan3, fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', '', { gender: 'female', _id: { $in: [fan2] }}) + .run(function (err, blogposts) { + should.strictEqual(err, null); + + blogposts[0].fans.length.should.equal(1); + blogposts[0].fans[0].gender.should.equal('female'); + blogposts[0].fans[0].name.should.equal('Fan 2'); + blogposts[0].fans[0].email.should.equal('fan2@learnboost.com'); + + blogposts[1].fans.length.should.equal(1); + blogposts[1].fans[0].gender.should.equal('female'); + blogposts[1].fans[0].name.should.equal('Fan 2'); + blogposts[1].fans[0].email.should.equal('fan2@learnboost.com'); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', false, { gender: 'female' }) + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + should.strictEqual(blogposts[0].fans.length, 2); + blogposts[0].fans[0].gender.should.equal('female'); + blogposts[0].fans[0].name.should.equal('Fan 2'); + blogposts[0].fans[0].email.should.equal('fan2@learnboost.com'); + blogposts[0].fans[1].gender.should.equal('female'); + blogposts[0].fans[1].name.should.equal('Fan 3'); + blogposts[0].fans[1].email.should.equal('fan3@learnboost.com'); + + should.strictEqual(blogposts[1].fans.length, 2); + blogposts[1].fans[0].gender.should.equal('female'); + blogposts[1].fans[0].name.should.equal('Fan 3'); + blogposts[1].fans[0].email.should.equal('fan3@learnboost.com'); + blogposts[1].fans[1].gender.should.equal('female'); + blogposts[1].fans[1].name.should.equal('Fan 2'); + blogposts[1].fans[1].email.should.equal('fan2@learnboost.com'); + }); + + }); + }); + }); + }); + }); + }); + }, + + 'test populating an array of references and multi-filtering': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, function (err, fan2) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 3' + , email : 'fan3@learnboost.com' + , gender : 'female' + , age : 25 + }, function (err, fan3) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2, fan3] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan3, fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', undefined, { _id: fan3 }) + .run(function (err, blogposts) { + should.strictEqual(err, null); + + blogposts[0].fans.length.should.equal(1); + blogposts[0].fans[0].gender.should.equal('female'); + blogposts[0].fans[0].name.should.equal('Fan 3'); + blogposts[0].fans[0].email.should.equal('fan3@learnboost.com'); + should.equal(blogposts[0].fans[0].age, 25); + + blogposts[1].fans.length.should.equal(1); + blogposts[1].fans[0].gender.should.equal('female'); + blogposts[1].fans[0].name.should.equal('Fan 3'); + blogposts[1].fans[0].email.should.equal('fan3@learnboost.com'); + should.equal(blogposts[1].fans[0].age, 25); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', 0, { gender: 'female' }) + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + blogposts[0].fans.length.should.equal(2); + blogposts[0].fans[0].gender.should.equal('female'); + blogposts[0].fans[0].name.should.equal('Fan 2'); + blogposts[0].fans[0].email.should.equal('fan2@learnboost.com'); + blogposts[0].fans[1].gender.should.equal('female'); + blogposts[0].fans[1].name.should.equal('Fan 3'); + blogposts[0].fans[1].email.should.equal('fan3@learnboost.com'); + should.equal(blogposts[0].fans[1].age, 25); + + blogposts[1].fans.length.should.equal(2); + blogposts[1].fans[0].gender.should.equal('female'); + blogposts[1].fans[0].name.should.equal('Fan 3'); + blogposts[1].fans[0].email.should.equal('fan3@learnboost.com'); + should.equal(blogposts[1].fans[0].age, 25); + blogposts[1].fans[1].gender.should.equal('female'); + blogposts[1].fans[1].name.should.equal('Fan 2'); + blogposts[1].fans[1].email.should.equal('fan2@learnboost.com'); + }); + }); + }); + }); + }); + }); + }); + }, + + 'test populating an array of references and multi-filtering with field selection': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, function (err, fan2) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 3' + , email : 'fan3@learnboost.com' + , gender : 'female' + , age : 25 + }, function (err, fan3) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2, fan3] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan3, fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', 'name email', { gender: 'female', age: 25 }) + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + should.strictEqual(blogposts[0].fans.length, 1); + blogposts[0].fans[0].name.should.equal('Fan 3'); + blogposts[0].fans[0].email.should.equal('fan3@learnboost.com'); + blogposts[0].fans[0].isInit('email').should.be.true; + blogposts[0].fans[0].isInit('gender').should.be.false; + blogposts[0].fans[0].isInit('age').should.be.false; + + should.strictEqual(blogposts[1].fans.length, 1); + blogposts[1].fans[0].name.should.equal('Fan 3'); + blogposts[1].fans[0].email.should.equal('fan3@learnboost.com'); + blogposts[1].fans[0].isInit('email').should.be.true; + blogposts[1].fans[0].isInit('gender').should.be.false; + blogposts[1].fans[0].isInit('age').should.be.false; + }); + }); + }); + }); + }); + }); + }, + + 'test populating an array of refs, changing one, and removing one': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, { + name : 'Fan 2' + , email : 'fan2@learnboost.com' + }, { + name : 'Fan 3' + , email : 'fan3@learnboost.com' + }, { + name : 'Fan 4' + , email : 'fan4@learnboost.com' + }, function (err, fan1, fan2, fan3, fan4) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2] + }, { + title : 'Woot' + , fans : [fan2, fan1] + }, function (err, post1, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', ['name']) + .run(function (err, blogposts) { + should.strictEqual(err, null); + + blogposts[0].fans[0].name.should.equal('Fan 1'); + blogposts[0].fans[0].isInit('email').should.be.false; + blogposts[0].fans[1].name.should.equal('Fan 2'); + blogposts[0].fans[1].isInit('email').should.be.false; + + blogposts[1].fans[0].name.should.equal('Fan 2'); + blogposts[1].fans[0].isInit('email').should.be.false; + blogposts[1].fans[1].name.should.equal('Fan 1'); + blogposts[1].fans[1].isInit('email').should.be.false; + + blogposts[1].fans = [fan3, fan4]; + + blogposts[1].save(function (err) { + should.strictEqual(err, null); + + BlogPost + .findById(blogposts[1]._id, [], { populate: ['fans'] }) + .run(function (err, post) { + should.strictEqual(err, null); + + post.fans[0].name.should.equal('Fan 3'); + post.fans[1].name.should.equal('Fan 4'); + + post.fans.splice(0, 1); + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('fans') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + post.fans.length.should.equal(1); + post.fans[0].name.should.equal('Fan 4'); + }); + }); + }); + }); + }); + }); + }); + }, + + 'test populating subdocuments': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ name: 'User 1' }, function (err, user1) { + should.strictEqual(err, null); + + User.create({ name: 'User 2' }, function (err, user2) { + should.strictEqual(err, null); + + BlogPost.create({ + title: 'Woot' + , _creator: user1._id + , comments: [ + { _creator: user1._id, content: 'Woot woot' } + , { _creator: user2._id, content: 'Wha wha' } + ] + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator') + .populate('comments._creator') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.name.should.equal('User 1'); + post.comments[0]._creator.name.should.equal('User 1'); + post.comments[1]._creator.name.should.equal('User 2'); + }); + }); + }); + }); + }, + + 'test populating subdocuments partially': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'User 1' + , email : 'user1@learnboost.com' + }, function (err, user1) { + should.strictEqual(err, null); + + User.create({ + name : 'User 2' + , email : 'user2@learnboost.com' + }, function (err, user2) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , comments: [ + { _creator: user1, content: 'Woot woot' } + , { _creator: user2, content: 'Wha wha' } + ] + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('comments._creator', ['email']) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post.comments[0]._creator.email.should.equal('user1@learnboost.com'); + post.comments[0]._creator.isInit('name').should.be.false; + post.comments[1]._creator.email.should.equal('user2@learnboost.com'); + post.comments[1]._creator.isInit('name').should.be.false; + }); + }); + }); + }); + }, + + 'test populating subdocuments partially with conditions': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'User 1' + , email : 'user1@learnboost.com' + }, function (err, user1) { + should.strictEqual(err, null); + + User.create({ + name : 'User 2' + , email : 'user2@learnboost.com' + }, function (err, user2) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , comments: [ + { _creator: user1, content: 'Woot woot' } + , { _creator: user2, content: 'Wha wha' } + ] + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('comments._creator', {'email': 1}, { name: /User/ }) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post.comments[0]._creator.email.should.equal('user1@learnboost.com'); + post.comments[0]._creator.isInit('name').should.be.false; + post.comments[1]._creator.email.should.equal('user2@learnboost.com'); + post.comments[1]._creator.isInit('name').should.be.false; + }); + }); + }); + }); + }, + + 'populating subdocs with invalid/missing subproperties': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'T-100' + , email : 'terminator100@learnboost.com' + }, function (err, user1) { + should.strictEqual(err, null); + + User.create({ + name : 'T-1000' + , email : 'terminator1000@learnboost.com' + }, function (err, user2) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , comments: [ + { _creator: null, content: 'Woot woot' } + , { _creator: user2, content: 'Wha wha' } + ] + }, function (err, post) { + should.strictEqual(err, null); + + // invalid subprop + BlogPost + .findById(post._id) + .populate('comments._idontexist', 'email') + .run(function (err, post) { + should.strictEqual(err, null); + should.exist(post); + post.comments.length.should.equal(2); + should.strictEqual(post.comments[0]._creator, null); + post.comments[1]._creator.toString().should.equal(user2.id); + + // subprop is null in a doc + BlogPost + .findById(post._id) + .populate('comments._creator', 'email') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + should.exist(post); + post.comments.length.should.equal(2); + should.strictEqual(post.comments[0]._creator, null); + should.strictEqual(post.comments[0].content, 'Woot woot'); + post.comments[1]._creator.email.should.equal('terminator1000@learnboost.com'); + post.comments[1]._creator.isInit('name').should.be.false; + post.comments[1].content.should.equal('Wha wha'); + }); + }); + }); + }); + }); + }, + + // gh-481 + 'test populating subdocuments partially with empty array': function (beforeExit) { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , worked = false; + + var post = BlogPost.create({ + title: 'Woot' + , comments: [] // EMPTY ARRAY + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('comments._creator', ['email']) + .run(function (err, returned) { + db.close(); + worked = true; + should.strictEqual(err, null); + returned.id.should.equal(post.id); + }); + }); + + beforeExit(function () { + worked.should.be.true; + }); + }, + + 'populating subdocuments with array including nulls': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users) + + var user = new User({ name: 'hans zimmer' }); + user.save(function (err) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , fans: [] + }, function (err, post) { + should.strictEqual(err, null); + + // shove some uncasted vals + BlogPost.collection.update({ _id: post._id }, { $set: { fans: [null, undefined, user.id, null] } }, function (err) { + should.strictEqual(err, undefined); + + BlogPost + .findById(post._id) + .populate('fans', ['name']) + .run(function (err, returned) { + db.close(); + should.strictEqual(err, null); + returned.id.should.equal(post.id); + returned.fans.length.should.equal(1); + }); + }) + }); + }); + }, + + 'populating more than one array at a time': function () { + var db = start() + , User = db.model('RefUser', users) + , M = db.model('PopMultiSubDocs', new Schema({ + users: [{ type: ObjectId, ref: 'RefUser' }] + , fans: [{ type: ObjectId, ref: 'RefUser' }] + , comments: [Comment] + })) + + User.create({ + email : 'fan1@learnboost.com' + }, { + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, { + name: 'Fan 3' + }, function (err, fan1, fan2, fan3) { + should.strictEqual(err, null); + + M.create({ + users: [fan3] + , fans: [fan1] + , comments: [ + { _creator: fan1, content: 'bejeah!' } + , { _creator: fan2, content: 'chickfila' } + ] + }, { + users: [fan1] + , fans: [fan2] + , comments: [ + { _creator: fan3, content: 'hello' } + , { _creator: fan1, content: 'world' } + ] + }, function (err, post1, post2) { + should.strictEqual(err, null); + + M.where('_id').in([post1, post2]) + .populate('fans', 'name', { gender: 'female' }) + .populate('users', 'name', { gender: 'male' }) + .populate('comments._creator', ['email'], { name: null }) + .run(function (err, posts) { + db.close(); + should.strictEqual(err, null); + + should.exist(posts); + posts.length.should.equal(2); + var p1 = posts[0]; + var p2 = posts[1]; + should.strictEqual(p1.fans.length, 0); + should.strictEqual(p2.fans.length, 1); + p2.fans[0].name.should.equal('Fan 2'); + p2.fans[0].isInit('email').should.be.false; + p2.fans[0].isInit('gender').should.be.false; + p1.comments.length.should.equal(2); + p2.comments.length.should.equal(2); + should.exist(p1.comments[0]._creator.email); + should.not.exist(p2.comments[0]._creator); + p1.comments[0]._creator.email.should.equal('fan1@learnboost.com'); + p2.comments[1]._creator.email.should.equal('fan1@learnboost.com'); + p1.comments[0]._creator.isInit('name').should.be.false; + p2.comments[1]._creator.isInit('name').should.be.false; + p1.comments[0].content.should.equal('bejeah!'); + p2.comments[1].content.should.equal('world'); + should.not.exist(p1.comments[1]._creator); + should.not.exist(p2.comments[0]._creator); + p1.comments[1].content.should.equal('chickfila'); + p2.comments[0].content.should.equal('hello'); + }); + }); + }); + }, + + 'populating multiple children of a sub-array at a time': function () { + var db = start() + , User = db.model('RefUser', users) + , BlogPost = db.model('RefBlogPost', posts) + , Inner = new Schema({ + user: { type: ObjectId, ref: 'RefUser' } + , post: { type: ObjectId, ref: 'RefBlogPost' } + }) + , I = db.model('PopMultiChildrenOfSubDocInner', Inner) + var M = db.model('PopMultiChildrenOfSubDoc', new Schema({ + kids: [Inner] + })) + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + , gender : 'male' + }, { + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, function (err, fan1, fan2) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + }, { + title : 'yay' + }, function (err, post1, post2) { + should.strictEqual(err, null); + M.create({ + kids: [ + { user: fan1, post: post1, y: 5 } + , { user: fan2, post: post2, y: 8 } + ] + , x: 4 + }, function (err, m1) { + should.strictEqual(err, null); + + M.findById(m1) + .populate('kids.user', ["name"]) + .populate('kids.post', ["title"], { title: "woot" }) + .run(function (err, o) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(o.kids.length, 2); + var k1 = o.kids[0]; + var k2 = o.kids[1]; + should.strictEqual(true, !k2.post); + should.strictEqual(k1.user.name, "Fan 1"); + should.strictEqual(k1.user.email, undefined); + should.strictEqual(k1.post.title, "woot"); + should.strictEqual(k2.user.name, "Fan 2"); + }); + }); + }); + }); + }, + + 'passing sort options to the populate method': function () { + var db = start() + , P = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ name: 'aaron', age: 10 }, { name: 'fan2', age: 8 }, { name: 'someone else', age: 3 }, + function (err, fan1, fan2, fan3) { + should.strictEqual(err, null); + + P.create({ fans: [fan2, fan3, fan1] }, function (err, post) { + should.strictEqual(err, null); + + P.findById(post) + .populate('fans', null, null, { sort: 'name' }) + .run(function (err, post) { + should.strictEqual(err, null); + + post.fans.length.should.equal(3); + post.fans[0].name.should.equal('aaron'); + post.fans[1].name.should.equal('fan2'); + post.fans[2].name.should.equal('someone else'); + + P.findById(post) + .populate('fans', 'name', null, { sort: [['name', -1]] }) + .run(function (err, post) { + should.strictEqual(err, null); + + post.fans.length.should.equal(3); + post.fans[2].name.should.equal('aaron'); + should.strictEqual(undefined, post.fans[2].age) + post.fans[1].name.should.equal('fan2'); + should.strictEqual(undefined, post.fans[1].age) + post.fans[0].name.should.equal('someone else'); + should.strictEqual(undefined, post.fans[0].age) + + P.findById(post) + .populate('fans', 'age', { age: { $gt: 3 }}, { sort: [['name', 'desc']] }) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post.fans.length.should.equal(2); + post.fans[1].age.valueOf().should.equal(10); + post.fans[0].age.valueOf().should.equal(8); + }); + }); + }); + }); + }); + }, + + 'refs should cast to ObjectId from hexstrings': function () { + var BP = mongoose.model('RefBlogPost', BlogPost); + var bp = new BP; + bp._creator = new DocObjectId().toString(); + bp._creator.should.be.an.instanceof(DocObjectId); + bp.set('_creator', new DocObjectId().toString()); + bp._creator.should.be.an.instanceof(DocObjectId); + }, + + 'populate should work on String _ids': function () { + var db = start(); + + var UserSchema = new Schema({ + _id: String + , name: String + }) + + var NoteSchema = new Schema({ + author: { type: String, ref: 'UserWithStringId' } + , body: String + }) + + var User = db.model('UserWithStringId', UserSchema, random()) + var Note = db.model('NoteWithStringId', NoteSchema, random()) + + var alice = new User({_id: 'alice', name: "Alice"}) + + alice.save(function (err) { + should.strictEqual(err, null); + + var note = new Note({author: 'alice', body: "Buy Milk"}); + note.save(function (err) { + should.strictEqual(err, null); + + Note.findById(note.id).populate('author').run(function (err, note) { + db.close(); + should.strictEqual(err, null); + note.body.should.equal('Buy Milk'); + should.exist(note.author); + note.author.name.should.equal('Alice'); + }); + }); + }) + }, + + 'populate should work on Number _ids': function () { + var db = start(); + + var UserSchema = new Schema({ + _id: Number + , name: String + }) + + var NoteSchema = new Schema({ + author: { type: Number, ref: 'UserWithNumberId' } + , body: String + }) + + var User = db.model('UserWithNumberId', UserSchema, random()) + var Note = db.model('NoteWithNumberId', NoteSchema, random()) + + var alice = new User({_id: 2359, name: "Alice"}) + + alice.save(function (err) { + should.strictEqual(err, null); + + var note = new Note({author: 2359, body: "Buy Milk"}); + note.save(function (err) { + should.strictEqual(err, null); + + Note.findById(note.id).populate('author').run(function (err, note) { + db.close(); + should.strictEqual(err, null); + note.body.should.equal('Buy Milk'); + should.exist(note.author); + note.author.name.should.equal('Alice'); + }); + }); + }) + }, + + // gh-577 + 'required works on ref fields': function () { + var db = start(); + + var userSchema = new Schema({ + email: {type: String, required: true} + }); + var User = db.model('ObjectIdRefRequiredField', userSchema, random()); + + var numSchema = new Schema({ _id: Number, val: Number }); + var Num = db.model('NumberRefRequired', numSchema, random()); + + var strSchema = new Schema({ _id: String, val: String }); + var Str = db.model('StringRefRequired', strSchema, random()); + + var commentSchema = new Schema({ + user: {type: ObjectId, ref: 'ObjectIdRefRequiredField', required: true} + , num: {type: Number, ref: 'NumberRefRequired', required: true} + , str: {type: String, ref: 'StringRefRequired', required: true} + , text: String + }); + var Comment = db.model('CommentWithRequiredField', commentSchema); + + var pending = 3; + + var string = new Str({ _id: 'my string', val: 'hello' }); + var number = new Num({ _id: 1995, val: 234 }); + var user = new User({ email: 'test' }); + + string.save(next); + number.save(next); + user.save(next); + + function next (err) { + should.strictEqual(null, err); + if (--pending) return; + + var comment = new Comment({ + text: 'test' + }); + + comment.save(function (err) { + should.equal('Validation failed', err && err.message); + err.errors.should.have.property('num'); + err.errors.should.have.property('str'); + err.errors.should.have.property('user'); + err.errors.num.type.should.equal('required'); + err.errors.str.type.should.equal('required'); + err.errors.user.type.should.equal('required'); + + comment.user = user; + comment.num = 1995; + comment.str = 'my string'; + + comment.save(function (err, comment) { + should.strictEqual(null, err); + + Comment + .findById(comment.id) + .populate('user') + .populate('num') + .populate('str') + .run(function (err, comment) { + should.strictEqual(err, null); + + comment.set({text: 'test2'}); + + comment.save(function (err, comment) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + } + }, + + 'populate works with schemas with both id and _id defined': function () { + var db =start() + , S1 = new Schema({ id: String }) + , S2 = new Schema({ things: [{ type: ObjectId, ref: '_idAndid' }]}) + + var M1 = db.model('_idAndid', S1); + var M2 = db.model('populateWorksWith_idAndidSchemas', S2); + + M1.create( + { id: "The Tiger That Isn't" } + , { id: "Users Guide To The Universe" } + , function (err, a, b) { + should.strictEqual(null, err); + + var m2 = new M2({ things: [a, b]}); + m2.save(function (err) { + should.strictEqual(null, err); + M2.findById(m2).populate('things').run(function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.things.length.should.equal(2); + doc.things[0].id.should.equal("The Tiger That Isn't"); + doc.things[1].id.should.equal("Users Guide To The Universe"); + }) + }); + }) + }, + + // gh-602 + 'Update works with populated arrays': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users) + + var user1 = new User({ name: 'aphex' }); + var user2 = new User({ name: 'twin' }); + + User.create({name:'aphex'},{name:'twin'}, function (err, u1, u2) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , fans: [] + }, function (err, post) { + should.strictEqual(err, null); + + var update = { fans: [u1, u2] }; + BlogPost.update({ _id: post }, update, function (err) { + should.strictEqual(err, null); + + // the original update doc should not be modified + ;('fans' in update).should.be.true; + ;('$set' in update).should.be.false; + update.fans[0].should.be.instanceof(mongoose.Document); + update.fans[1].should.be.instanceof(mongoose.Document); + + BlogPost.findById(post, function (err, post) { + db.close(); + should.strictEqual(err, null); + post.fans.length.should.equal(2); + post.fans[0].should.be.instanceof(DocObjectId); + post.fans[1].should.be.instanceof(DocObjectId); + }); + }); + }); + }); + } +}; diff --git a/node_modules/mongoose/test/model.stream.test.js b/node_modules/mongoose/test/model.stream.test.js new file mode 100644 index 0000000..a010b0e --- /dev/null +++ b/node_modules/mongoose/test/model.stream.test.js @@ -0,0 +1,232 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , utils = require('../lib/utils') + , random = utils.random + , Query = require('../lib/query') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , CastError = SchemaType.CastError + , ObjectId = Schema.ObjectId + , MongooseBuffer = mongoose.Types.Buffer + , DocumentObjectId = mongoose.Types.ObjectId + , fs = require('fs') + +var names = ('Aaden Aaron Adrian Aditya Agustin Jim Bob Jonah Frank Sally Lucy').split(' '); + +/** + * Setup. + */ + +var Person = new Schema({ + name: String +}); + +mongoose.model('PersonForStream', Person); +var collection = 'personforstream_' + random(); + +;(function setup () { + var db = start() + , P = db.model('PersonForStream', collection) + + var people = names.map(function (name) { + return { name: name }; + }); + + P.create(people, function (err) { + should.strictEqual(null, err); + db.close(); + assignExports(); + }); +})() + +function assignExports () { var o = { + + 'cursor stream': function () { + var db = start() + , P = db.model('PersonForStream', collection) + , i = 0 + , closed = 0 + , paused = 0 + , resumed = 0 + , err + + var stream = P.find({}).stream(); + + stream.on('data', function (doc) { + should.strictEqual(true, !! doc.name); + should.strictEqual(true, !! doc._id); + + if (paused > 0 && 0 === resumed) { + err = new Error('data emitted during pause'); + return done(); + } + + if (++i === 3) { + stream.paused.should.be.false; + stream.pause(); + stream.paused.should.be.true; + paused++; + + setTimeout(function () { + stream.paused.should.be.true; + resumed++; + stream.resume(); + stream.paused.should.be.false; + }, 20); + } + }); + + stream.on('error', function (er) { + err = er; + done(); + }); + + stream.on('close', function () { + closed++; + done(); + }); + + function done () { + db.close(); + should.strictEqual(undefined, err); + should.equal(i, names.length); + closed.should.equal(1); + paused.should.equal(1); + resumed.should.equal(1); + stream._cursor.isClosed().should.be.true; + } + } + +, 'immediately destroying a stream prevents the query from executing': function () { + var db = start() + , P = db.model('PersonForStream', collection) + , i = 0 + + var stream = P.where('name', 'Jonah').select('name').findOne().stream(); + + stream.on('data', function () { + i++; + }) + stream.on('close', done); + stream.on('error', done); + + stream.destroy(); + + function done (err) { + should.strictEqual(undefined, err); + i.should.equal(0); + process.nextTick(function () { + db.close(); + should.strictEqual(null, stream._fields); + }) + } + } + +, 'destroying a stream stops it': function () { + var db = start() + , P = db.model('PersonForStream', collection) + , finished = 0 + , i = 0 + + var stream = P.where('name').$exists().limit(10).only('_id').stream(); + + should.strictEqual(null, stream._destroyed); + stream.readable.should.be.true; + + stream.on('data', function (doc) { + should.strictEqual(undefined, doc.name); + if (++i === 5) { + stream.destroy(); + stream.readable.should.be.false; + } + }); + + stream.on('close', done); + stream.on('error', done); + + function done (err) { + ++finished; + setTimeout(function () { + db.close(); + should.strictEqual(undefined, err); + i.should.equal(5); + finished.should.equal(1); + stream._destroyed.should.equal(true); + stream.readable.should.be.false; + stream._cursor.isClosed().should.be.true; + }, 150) + } + } + +, 'cursor stream errors': function () { + var db = start({ server: { auto_reconnect: false }}) + , P = db.model('PersonForStream', collection) + , finished = 0 + , closed = 0 + , i = 0 + + var stream = P.find().batchSize(5).stream(); + + stream.on('data', function (doc) { + if (++i === 5) { + db.close(); + } + }); + + stream.on('close', function () { + closed++; + }); + + stream.on('error', done); + + function done (err) { + ++finished; + setTimeout(function () { + should.equal('no open connections', err.message); + i.should.equal(5); + closed.should.equal(1); + finished.should.equal(1); + stream._destroyed.should.equal(true); + stream.readable.should.be.false; + stream._cursor.isClosed().should.be.true; + }, 150) + } + } + +, 'cursor stream pipe': function () { + var db = start() + , P = db.model('PersonForStream', collection) + , filename = '/tmp/_mongoose_stream_out.txt' + , out = fs.createWriteStream(filename) + + var stream = P.find().sort('name', 1).limit(20).stream(); + stream.pipe(out); + + stream.on('error', done); + stream.on('close', done); + + function done (err) { + db.close(); + should.strictEqual(undefined, err); + var contents = fs.readFileSync(filename, 'utf8'); + ;/Aaden/.test(contents).should.be.true; + ;/Aaron/.test(contents).should.be.true; + ;/Adrian/.test(contents).should.be.true; + ;/Aditya/.test(contents).should.be.true; + ;/Agustin/.test(contents).should.be.true; + fs.unlink(filename); + } + } +} + +// end exports + +utils.merge(exports, o); + +} diff --git a/node_modules/mongoose/test/model.test.js b/node_modules/mongoose/test/model.test.js new file mode 100644 index 0000000..4ad8aa6 --- /dev/null +++ b/node_modules/mongoose/test/model.test.js @@ -0,0 +1,4873 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Query = require('../lib/query') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , CastError = SchemaType.CastError + , ValidatorError = SchemaType.ValidatorError + , ValidationError = mongoose.Document.ValidationError + , ObjectId = Schema.ObjectId + , DocumentObjectId = mongoose.Types.ObjectId + , DocumentArray = mongoose.Types.DocumentArray + , EmbeddedDocument = mongoose.Types.Embedded + , MongooseNumber = mongoose.Types.Number + , MongooseArray = mongoose.Types.Array + , MongooseError = mongoose.Error; + +/** + * Setup. + */ + +var Comments = new Schema(); + +Comments.add({ + title : String + , date : Date + , body : String + , comments : [Comments] +}); + +var BlogPost = new Schema({ + title : String + , author : String + , slug : String + , date : Date + , meta : { + date : Date + , visitors : Number + } + , published : Boolean + , mixed : {} + , numbers : [Number] + , owners : [ObjectId] + , comments : [Comments] +}); + +BlogPost.virtual('titleWithAuthor') + .get(function () { + return this.get('title') + ' by ' + this.get('author'); + }) + .set(function (val) { + var split = val.split(' by '); + this.set('title', split[0]); + this.set('author', split[1]); + }); + +BlogPost.method('cool', function(){ + return this; +}); + +BlogPost.static('woot', function(){ + return this; +}); + +mongoose.model('BlogPost', BlogPost); + +var collection = 'blogposts_' + random(); + +module.exports = { + + 'test a model isNew flag when instantiating': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.isNew.should.be.true; + db.close(); + }, + + 'modified getter should not throw': function () { + var db = start(); + var BlogPost = db.model('BlogPost', collection); + var post = new BlogPost; + db.close(); + + var threw = false; + try { + post.modified; + } catch (err) { + threw = true; + } + + threw.should.be.false; + }, + + 'test presence of model schema': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.schema.should.be.an.instanceof(Schema); + BlogPost.prototype.schema.should.be.an.instanceof(Schema); + db.close(); + }, + + 'test a model default structure when instantiated': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.isNew.should.be.true; + post.db.model('BlogPost').modelName.should.equal('BlogPost'); + post.constructor.modelName.should.equal('BlogPost'); + + post.get('_id').should.be.an.instanceof(DocumentObjectId); + + should.equal(undefined, post.get('title')); + should.equal(undefined, post.get('slug')); + should.equal(undefined, post.get('date')); + + post.get('meta').should.be.a('object'); + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + should.equal(undefined, post.get('published')); + + post.get('numbers').should.be.an.instanceof(MongooseArray); + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + db.close(); + }, + + 'mongoose.model returns the model at creation': function () { + var Named = mongoose.model('Named', new Schema({ name: String })); + var n1 = new Named(); + should.equal(n1.name, null); + var n2 = new Named({ name: 'Peter Bjorn' }); + should.equal(n2.name, 'Peter Bjorn'); + + var schema = new Schema({ number: Number }); + var Numbered = mongoose.model('Numbered', schema, collection); + var n3 = new Numbered({ number: 1234 }); + n3.number.valueOf().should.equal(1234); + }, + + 'collection name can be specified through schema': function () { + var schema = new Schema({ name: String }, { collection: 'users1' }); + var Named = mongoose.model('CollectionNamedInSchema1', schema); + Named.prototype.collection.name.should.equal('users1'); + + var db = start(); + var users2schema = new Schema({ name: String }, { collection: 'users2' }); + var Named2 = db.model('CollectionNamedInSchema2', users2schema); + db.close(); + Named2.prototype.collection.name.should.equal('users2'); + }, + + 'test default Array type casts to Mixed': function () { + var db = start() + , DefaultArraySchema = new Schema({ + num1: Array + , num2: [] + }) + + mongoose.model('DefaultArraySchema', DefaultArraySchema); + var DefaultArray = db.model('DefaultArraySchema', collection); + var arr = new DefaultArray(); + + arr.get('num1').should.have.length(0); + arr.get('num2').should.have.length(0); + + var threw1 = false + , threw2 = false; + + try { + arr.num1.push({ x: 1 }) + arr.num1.push(9) + arr.num1.push("woah") + } catch (err) { + threw1 = true; + } + + threw1.should.equal(false); + + try { + arr.num2.push({ x: 1 }) + arr.num2.push(9) + arr.num2.push("woah") + } catch (err) { + threw2 = true; + } + + threw2.should.equal(false); + + db.close(); + + }, + + 'test a model default structure that has a nested Array when instantiated': function () { + var db = start() + , NestedSchema = new Schema({ + nested: { + array: [Number] + } + }); + mongoose.model('NestedNumbers', NestedSchema); + var NestedNumbers = db.model('NestedNumbers', collection); + + var nested = new NestedNumbers(); + nested.get('nested.array').should.be.an.instanceof(MongooseArray); + db.close(); + }, + + 'test a model that defaults an Array attribute to a default non-empty array': function () { + var db = start() + , DefaultArraySchema = new Schema({ + arr: {type: Array, cast: String, default: ['a', 'b', 'c']} + }); + mongoose.model('DefaultArray', DefaultArraySchema); + var DefaultArray = db.model('DefaultArray', collection); + var arr = new DefaultArray(); + arr.get('arr').should.have.length(3); + arr.get('arr')[0].should.equal('a'); + arr.get('arr')[1].should.equal('b'); + arr.get('arr')[2].should.equal('c'); + db.close(); + }, + + 'test a model that defaults an Array attribute to a default single member array': function () { + var db = start() + , DefaultOneCardArraySchema = new Schema({ + arr: {type: Array, cast: String, default: ['a']} + }); + mongoose.model('DefaultOneCardArray', DefaultOneCardArraySchema); + var DefaultOneCardArray = db.model('DefaultOneCardArray', collection); + var arr = new DefaultOneCardArray(); + arr.get('arr').should.have.length(1); + arr.get('arr')[0].should.equal('a'); + db.close(); + }, + + 'test a model that defaults an Array attributes to an empty array': function () { + var db = start() + , DefaultZeroCardArraySchema = new Schema({ + arr: {type: Array, cast: String, default: []} + }); + mongoose.model('DefaultZeroCardArray', DefaultZeroCardArraySchema); + var DefaultZeroCardArray = db.model('DefaultZeroCardArray', collection); + var arr = new DefaultZeroCardArray(); + arr.get('arr').should.have.length(0); + arr.arr.should.have.length(0); + db.close(); + }, + + 'test that arrays auto-default to the empty array': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.numbers.should.have.length(0); + db.close(); + }, + + 'test instantiating a model with a hash that maps to at least 1 null value': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + title: null + }); + should.strictEqual(null, post.title); + db.close(); + }, + + 'saving a model with a null value should perpetuate that null value to the db': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + title: null + }); + should.strictEqual(null, post.title); + post.save( function (err) { + should.strictEqual(err, null); + BlogPost.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(found.title, null); + }); + }); + }, + + 'test instantiating a model with a hash that maps to at least 1 undefined value': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + title: undefined + }); + should.strictEqual(undefined, post.title); + post.save( function (err) { + should.strictEqual(null, err); + BlogPost.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(found.title, undefined); + }); + }); + }, + + 'test a model structure when saved': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , pending = 2; + + function done () { + if (!--pending) db.close(); + } + + var post = new BlogPost(); + post.on('save', function (post) { + post.get('_id').should.be.an.instanceof(DocumentObjectId); + + should.equal(undefined, post.get('title')); + should.equal(undefined, post.get('slug')); + should.equal(undefined, post.get('date')); + should.equal(undefined, post.get('published')); + + post.get('meta').should.be.a('object'); + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + done(); + }); + + post.save(function(err, post){ + should.strictEqual(err, null); + post.get('_id').should.be.an.instanceof(DocumentObjectId); + + should.equal(undefined, post.get('title')); + should.equal(undefined, post.get('slug')); + should.equal(undefined, post.get('date')); + should.equal(undefined, post.get('published')); + + post.get('meta').should.be.a('object'); + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + done(); + }); + }, + + 'test a model structures when created': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({ title: 'hi there'}, function (err, post) { + should.strictEqual(err, null); + post.get('_id').should.be.an.instanceof(DocumentObjectId); + + should.strictEqual(post.get('title'), 'hi there'); + should.equal(undefined, post.get('slug')); + should.equal(undefined, post.get('date')); + + post.get('meta').should.be.a('object'); + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + should.strictEqual(undefined, post.get('published')); + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + db.close(); + }); + }, + + 'test a model structure when initd': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , date : new Date + , meta : { + date : new Date + , visitors : 5 + } + , published : true + , owners : [new DocumentObjectId, new DocumentObjectId] + , comments : [ + { title: 'Test', date: new Date, body: 'Test' } + , { title: 'Super', date: new Date, body: 'Cool' } + ] + }); + + post.get('title').should.eql('Test'); + post.get('slug').should.eql('test'); + post.get('date').should.be.an.instanceof(Date); + post.get('meta').should.be.a('object'); + post.get('meta').date.should.be.an.instanceof(Date); + post.get('meta').visitors.should.be.an.instanceof(MongooseNumber); + post.get('published').should.be.true; + + post.title.should.eql('Test'); + post.slug.should.eql('test'); + post.date.should.be.an.instanceof(Date); + post.meta.should.be.a('object'); + post.meta.date.should.be.an.instanceof(Date); + post.meta.visitors.should.be.an.instanceof(MongooseNumber); + post.published.should.be.true; + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('owners')[0].should.be.an.instanceof(DocumentObjectId); + post.get('owners')[1].should.be.an.instanceof(DocumentObjectId); + + post.owners.should.be.an.instanceof(MongooseArray); + post.owners[0].should.be.an.instanceof(DocumentObjectId); + post.owners[1].should.be.an.instanceof(DocumentObjectId); + + post.get('comments').should.be.an.instanceof(DocumentArray); + post.get('comments')[0].should.be.an.instanceof(EmbeddedDocument); + post.get('comments')[1].should.be.an.instanceof(EmbeddedDocument); + + post.comments.should.be.an.instanceof(DocumentArray); + post.comments[0].should.be.an.instanceof(EmbeddedDocument); + post.comments[1].should.be.an.instanceof(EmbeddedDocument); + + db.close(); + }, + + 'test a model structure when partially initd': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , date : new Date + }); + + post.get('title').should.eql('Test'); + post.get('slug').should.eql('test'); + post.get('date').should.be.an.instanceof(Date); + post.get('meta').should.be.a('object'); + + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + should.equal(undefined, post.get('published')); + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + db.close(); + }, + + 'test initializing with a nested hash': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + meta: { + date : new Date + , visitors : 5 + } + }); + + post.get('meta.visitors').valueOf().should.equal(5); + db.close(); + }, + + 'test isNew on embedded documents after initing': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , comments : [ { title: 'Test', date: new Date, body: 'Test' } ] + }); + + post.get('comments')[0].isNew.should.be.false; + db.close(); + }, + + 'test isNew on embedded documents after saving': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ title: 'hocus pocus' }) + post.comments.push({ title: 'Humpty Dumpty', comments: [{title: 'nested'}] }); + post.get('comments')[0].isNew.should.be.true; + post.get('comments')[0].comments[0].isNew.should.be.true; + post.invalidate('title'); // force error + post.save(function (err) { + post.isNew.should.be.true; + post.get('comments')[0].isNew.should.be.true; + post.get('comments')[0].comments[0].isNew.should.be.true; + post.save(function (err) { + db.close(); + should.strictEqual(null, err); + post.isNew.should.be.false; + post.get('comments')[0].isNew.should.be.false; + post.get('comments')[0].comments[0].isNew.should.be.false; + }); + }); + }, + + 'modified states are reset after save runs': function () { + var db = start() + , B = db.model('BlogPost', collection) + , pending = 2; + + var b = new B; + + b.numbers.push(3); + b.save(function (err) { + should.strictEqual(null, err); + --pending || find(); + }); + + b.numbers.push(3); + b.save(function (err) { + should.strictEqual(null, err); + --pending || find(); + }); + + function find () { + B.findById(b, function (err, b) { + db.close(); + should.strictEqual(null, err); + b.numbers.length.should.equal(2); + }); + } + }, + + 'modified states in emb-doc are reset after save runs': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ title: 'hocus pocus' }); + post.comments.push({ title: 'Humpty Dumpty', comments: [{title: 'nested'}] }); + post.save(function(err){ + db.close(); + should.strictEqual(null, err); + var mFlag = post.comments[0].isModified('title'); + mFlag.should.equal(false); + post.isModified('title').should.equal(false); + }); + + }, + + 'test isModified when modifying keys': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , date : new Date + }); + + post.isModified('title').should.be.false; + post.set('title', 'test'); + post.isModified('title').should.be.true; + + post.isModified('date').should.be.false; + post.set('date', new Date(post.date + 1)); + post.isModified('date').should.be.true; + + post.isModified('meta.date').should.be.false; + db.close(); + }, + + 'setting a key identically to its current value should not dirty the key': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , date : new Date + }); + + post.isModified('title').should.be.false; + post.set('title', 'Test'); + post.isModified('title').should.be.false; + db.close(); + }, + + 'test isModified and isDirectModified on a DocumentArray': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , comments : [ { title: 'Test', date: new Date, body: 'Test' } ] + }); + + post.isModified('comments.0.title').should.be.false; + post.get('comments')[0].set('title', 'Woot'); + post.isModified('comments').should.be.true; + post.isDirectModified('comments').should.be.false; + post.isModified('comments.0.title').should.be.true; + post.isDirectModified('comments.0.title').should.be.true; + + db.close(); + }, + + 'test isModified on a DocumentArray with accessors': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , comments : [ { title: 'Test', date: new Date, body: 'Test' } ] + }); + + post.isModified('comments.0.body').should.be.false; + post.get('comments')[0].body = 'Woot'; + post.isModified('comments').should.be.true; + post.isDirectModified('comments').should.be.false; + post.isModified('comments.0.body').should.be.true; + post.isDirectModified('comments.0.body').should.be.true; + + db.close(); + }, + + 'test isModified on a MongooseArray with atomics methods': function(){ + // COMPLETEME + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + + post.isModified('owners').should.be.false; + post.get('owners').$push(new DocumentObjectId); + post.isModified('owners').should.be.true; + + db.close(); + }, + + 'test isModified on a MongooseArray with native methods': function(){ + // COMPLETEME + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + + post.isModified('owners').should.be.false; + post.get('owners').push(new DocumentObjectId); + + db.close(); + }, + + 'test isModified on a Mongoose Document - Modifying an existing record' : function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + + var doc = { + title : 'Test' + , slug : 'test' + , date : new Date + , meta : { + date : new Date + , visitors : 5 + } + , published : true + , mixed : { x: [ { y: [1,'yes', 2] } ] } + , numbers : [] + , owners : [new DocumentObjectId, new DocumentObjectId] + , comments : [ + { title: 'Test', date: new Date, body: 'Test' } + , { title: 'Super', date: new Date, body: 'Cool' } + ] + }; + + BlogPost.create(doc, function (err, post) { + BlogPost.findById(post.id, function (err, postRead) { + db.close(); + should.strictEqual(null, err); + //set the same data again back to the document. + //expected result, nothing should be set to modified + postRead.isModified('comments').should.be.false; + postRead.isNew.should.be.false; + postRead.set(postRead.toObject()); + + postRead.isModified('title').should.be.false; + postRead.isModified('slug').should.be.false; + postRead.isModified('date').should.be.false; + postRead.isModified('meta.date').should.be.false; + postRead.isModified('meta.visitors').should.be.false; + postRead.isModified('published').should.be.false; + postRead.isModified('mixed').should.be.false; + postRead.isModified('numbers').should.be.false; + postRead.isModified('owners').should.be.false; + postRead.isModified('comments').should.be.false; + postRead.comments[2] = { title: 'index' }; + postRead.comments = postRead.comments; + postRead.isModified('comments').should.be.true; + }); + }); + }, + + // GH-342 + 'over-writing a number should persist to the db': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + meta: { + date : new Date + , visitors : 10 + } + }); + + post.save( function (err) { + should.strictEqual(null, err); + post.set('meta.visitors', 20); + post.save( function (err) { + should.strictEqual(null, err); + BlogPost.findById(post.id, function (err, found) { + should.strictEqual(null, err); + found.get('meta.visitors').valueOf().should.equal(20); + db.close(); + }); + }); + }); + }, + + 'test defining a new method': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.cool().should.eql(post); + db.close(); + }, + + 'test defining a static': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.woot().should.eql(BlogPost); + db.close(); + }, + + 'test casting error': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , threw = false; + + var post = new BlogPost(); + + try { + post.init({ + date: 'Test' + }); + } catch(e){ + threw = true; + } + + threw.should.be.false; + + try { + post.set('title', 'Test'); + } catch(e){ + threw = true; + } + + threw.should.be.false; + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test nested casting error': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , threw = false; + + var post = new BlogPost(); + + try { + post.init({ + meta: { + date: 'Test' + } + }); + } catch(e){ + threw = true; + } + + threw.should.be.false; + + try { + post.set('meta.date', 'Test'); + } catch(e){ + threw = true; + } + + threw.should.be.false; + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test casting error in subdocuments': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , threw = false; + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , comments : [ { title: 'Test', date: new Date, body: 'Test' } ] + }); + + post.get('comments')[0].set('date', 'invalid'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test casting error when adding a subdocument': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , threw = false; + + var post = new BlogPost() + + try { + post.get('comments').push({ + date: 'Bad date' + }); + } catch (e) { + threw = true; + } + + threw.should.be.false; + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test validation': function(){ + function dovalidate (val) { + this.asyncScope.should.equal('correct'); + return true; + } + + function dovalidateAsync (val, callback) { + this.scope.should.equal('correct'); + process.nextTick(function () { + callback(true); + }); + } + + mongoose.model('TestValidation', new Schema({ + simple: { type: String, required: true } + , scope: { type: String, validate: [dovalidate, 'scope failed'], required: true } + , asyncScope: { type: String, validate: [dovalidateAsync, 'async scope failed'], required: true } + })); + + var db = start() + , TestValidation = db.model('TestValidation'); + + var post = new TestValidation(); + post.set('simple', ''); + post.set('scope', 'correct'); + post.set('asyncScope', 'correct'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + post.set('simple', 'here'); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test validation with custom message': function () { + function validate (val) { + return val === 'abc'; + } + mongoose.model('TestValidationMessage', new Schema({ + simple: { type: String, validate: [validate, 'must be abc'] } + })); + + var db = start() + , TestValidationMessage = db.model('TestValidationMessage'); + + var post = new TestValidationMessage(); + post.set('simple', ''); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.simple.should.be.an.instanceof(ValidatorError); + err.errors.simple.message.should.equal('Validator "must be abc" failed for path simple'); + post.errors.simple.message.should.equal('Validator "must be abc" failed for path simple'); + + post.set('simple', 'abc'); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + // GH-272 + 'test validation with Model.schema.path introspection': function () { + var db = start(); + var IntrospectionValidationSchema = new Schema({ + name: String + }); + var IntrospectionValidation = db.model('IntrospectionValidation', IntrospectionValidationSchema, 'introspections_' + random()); + IntrospectionValidation.schema.path('name').validate(function (value) { + return value.length < 2; + }, 'Name cannot be greater than 1 character'); + var doc = new IntrospectionValidation({name: 'hi'}); + doc.save( function (err) { + err.errors.name.message.should.equal("Validator \"Name cannot be greater than 1 character\" failed for path name"); + err.name.should.equal("ValidationError"); + err.message.should.equal("Validation failed"); + db.close(); + }); + }, + + 'test required validation for undefined values': function () { + mongoose.model('TestUndefinedValidation', new Schema({ + simple: { type: String, required: true } + })); + + var db = start() + , TestUndefinedValidation = db.model('TestUndefinedValidation'); + + var post = new TestUndefinedValidation(); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + post.set('simple', 'here'); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + // GH-319 + 'save callback should only execute once regardless of number of failed validations': function () { + mongoose.model('CallbackFiresOnceValidation', new Schema({ + username: { type: String, validate: /^[a-z]{6}$/i } + , email: { type: String, validate: /^[a-z]{6}$/i } + , password: { type: String, validate: /^[a-z]{6}$/i } + })); + + var db = start() + , CallbackFiresOnceValidation = db.model('CallbackFiresOnceValidation'); + + var post = new CallbackFiresOnceValidation({ + username: "nope" + , email: "too" + , password: "short" + }); + + var timesCalled = 0; + + post.save(function (err) { + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + (++timesCalled).should.eql(1); + + (Object.keys(err.errors).length).should.eql(3); + err.errors.password.should.be.an.instanceof(ValidatorError); + err.errors.email.should.be.an.instanceof(ValidatorError); + err.errors.username.should.be.an.instanceof(ValidatorError); + err.errors.password.message.should.eql('Validator failed for path password'); + err.errors.email.message.should.eql('Validator failed for path email'); + err.errors.username.message.should.eql('Validator failed for path username'); + + (Object.keys(post.errors).length).should.eql(3); + post.errors.password.should.be.an.instanceof(ValidatorError); + post.errors.email.should.be.an.instanceof(ValidatorError); + post.errors.username.should.be.an.instanceof(ValidatorError); + post.errors.password.message.should.eql('Validator failed for path password'); + post.errors.email.message.should.eql('Validator failed for path email'); + post.errors.username.message.should.eql('Validator failed for path username'); + + db.close(); + }); + }, + + 'test validation on a query result': function () { + mongoose.model('TestValidationOnResult', new Schema({ + resultv: { type: String, required: true } + })); + + var db = start() + , TestV = db.model('TestValidationOnResult'); + + var post = new TestV; + + post.validate(function (err) { + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + post.resultv = 'yeah'; + post.save(function (err) { + should.strictEqual(err, null); + TestV.findOne({ _id: post.id }, function (err, found) { + should.strictEqual(err, null); + found.resultv.should.eql('yeah'); + found.save(function(err){ + should.strictEqual(err, null); + db.close(); + }) + }); + }); + }) + }, + + 'test required validation for previously existing null values': function () { + mongoose.model('TestPreviousNullValidation', new Schema({ + previous: { type: String, required: true } + , a: String + })); + + var db = start() + , TestP = db.model('TestPreviousNullValidation') + + TestP.collection.insert({ a: null, previous: null}, {}, function (err, f) { + should.strictEqual(err, null); + + TestP.findOne({_id: f[0]._id}, function (err, found) { + should.strictEqual(err, null); + found.isNew.should.be.false; + should.strictEqual(found.get('previous'), null); + + found.validate(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + found.set('previous', 'yoyo'); + found.save(function (err) { + should.strictEqual(err, null); + db.close(); + }); + }) + }) + }); + }, + + 'test nested validation': function(){ + mongoose.model('TestNestedValidation', new Schema({ + nested: { + required: { type: String, required: true } + } + })); + + var db = start() + , TestNestedValidation = db.model('TestNestedValidation'); + + var post = new TestNestedValidation(); + post.set('nested.required', null); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + post.set('nested.required', 'here'); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test validation in subdocuments': function(){ + var Subdocs = new Schema({ + required: { type: String, required: true } + }); + + mongoose.model('TestSubdocumentsValidation', new Schema({ + items: [Subdocs] + })); + + var db = start() + , TestSubdocumentsValidation = db.model('TestSubdocumentsValidation'); + + var post = new TestSubdocumentsValidation(); + + post.get('items').push({ required: '' }); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.required.should.be.an.instanceof(ValidatorError); + err.errors.required.message.should.eql('Validator "required" failed for path required'); + + post.get('items')[0].set('required', true); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test async validation': function(){ + var executed = false; + + function validator(v, fn){ + setTimeout(function () { + executed = true; + fn(v !== 'test'); + }, 50); + }; + mongoose.model('TestAsyncValidation', new Schema({ + async: { type: String, validate: [validator, 'async validator'] } + })); + + var db = start() + , TestAsyncValidation = db.model('TestAsyncValidation'); + + var post = new TestAsyncValidation(); + post.set('async', 'test'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.async.should.be.an.instanceof(ValidatorError); + err.errors.async.message.should.eql('Validator "async validator" failed for path async'); + executed.should.be.true; + executed = false; + + post.set('async', 'woot'); + post.save(function(err){ + executed.should.be.true; + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test nested async validation': function(){ + var executed = false; + + function validator(v, fn){ + setTimeout(function () { + executed = true; + fn(v !== 'test'); + }, 50); + }; + + mongoose.model('TestNestedAsyncValidation', new Schema({ + nested: { + async: { type: String, validate: [validator, 'async validator'] } + } + })); + + var db = start() + , TestNestedAsyncValidation = db.model('TestNestedAsyncValidation'); + + var post = new TestNestedAsyncValidation(); + post.set('nested.async', 'test'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + executed.should.be.true; + executed = false; + + post.validate(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + executed.should.be.true; + executed = false; + + post.set('nested.async', 'woot'); + post.validate(function(err){ + executed.should.be.true; + should.equal(err, null); + executed = false; + + post.save(function(err){ + executed.should.be.true; + should.strictEqual(err, null); + db.close(); + }); + }); + }) + + }); + }, + + 'test async validation in subdocuments': function(){ + var executed = false; + + function validator (v, fn) { + setTimeout(function(){ + executed = true; + fn(v !== ''); + }, 50); + }; + + var Subdocs = new Schema({ + required: { type: String, validate: [validator, 'async in subdocs'] } + }); + + mongoose.model('TestSubdocumentsAsyncValidation', new Schema({ + items: [Subdocs] + })); + + var db = start() + , Test = db.model('TestSubdocumentsAsyncValidation'); + + var post = new Test(); + + post.get('items').push({ required: '' }); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + executed.should.be.true; + executed = false; + + post.get('items')[0].set({ required: 'here' }); + post.save(function(err){ + executed.should.be.true; + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test validation without saving': function(){ + + mongoose.model('TestCallingValidation', new Schema({ + item: { type: String, required: true } + })); + + var db = start() + , TestCallingValidation = db.model('TestCallingValidation'); + + var post = new TestCallingValidation; + + post.schema.path('item').isRequired.should.be.true; + + should.strictEqual(post.isNew, true); + + post.validate(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + should.strictEqual(post.isNew, true); + + post.item = 'yo'; + post.validate(function(err){ + should.equal(err, null); + should.strictEqual(post.isNew, true); + db.close(); + }); + }); + + }, + + 'test setting required to false': function () { + function validator () { + return true; + } + + mongoose.model('TestRequiredFalse', new Schema({ + result: { type: String, validate: [validator, 'chump validator'], required: false } + })); + + var db = start() + , TestV = db.model('TestRequiredFalse'); + + var post = new TestV; + + post.schema.path('result').isRequired.should.be.false; + + db.close(); + }, + + 'test defaults application': function(){ + var now = Date.now(); + + mongoose.model('TestDefaults', new Schema({ + date: { type: Date, default: now } + })); + + var db = start() + , TestDefaults = db.model('TestDefaults'); + + var post = new TestDefaults(); + post.get('date').should.be.an.instanceof(Date); + (+post.get('date')).should.eql(now); + db.close(); + }, + + 'test "type" is allowed as a key': function(){ + mongoose.model('TestTypeDefaults', new Schema({ + type: { type: String, default: 'YES!' } + })); + + var db = start() + , TestDefaults = db.model('TestTypeDefaults'); + + var post = new TestDefaults(); + post.get('type').should.be.a('string'); + post.get('type').should.eql('YES!'); + + // GH-402 + var TestDefaults2 = db.model('TestTypeDefaults2', new Schema({ + x: { y: { type: { type: String }, owner: String } } + })); + + var post = new TestDefaults2; + post.x.y.type = "#402"; + post.x.y.owner= "me"; + post.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + + }, + + 'test nested defaults application': function(){ + var now = Date.now(); + + mongoose.model('TestNestedDefaults', new Schema({ + nested: { + date: { type: Date, default: now } + } + })); + + var db = start() + , TestDefaults = db.model('TestNestedDefaults'); + + var post = new TestDefaults(); + post.get('nested.date').should.be.an.instanceof(Date); + (+post.get('nested.date')).should.eql(now); + db.close(); + }, + + 'test defaults application in subdocuments': function(){ + var now = Date.now(); + + var Items = new Schema({ + date: { type: Date, default: now } + }); + + mongoose.model('TestSubdocumentsDefaults', new Schema({ + items: [Items] + })); + + var db = start() + , TestSubdocumentsDefaults = db.model('TestSubdocumentsDefaults'); + + var post = new TestSubdocumentsDefaults(); + post.get('items').push({}); + post.get('items')[0].get('date').should.be.an.instanceof(Date); + (+post.get('items')[0].get('date')).should.eql(now); + db.close(); + }, + + // TODO: adapt this text to handle a getIndexes callback that's not unique to + // the mongodb-native driver. + 'test that indexes are ensured when the model is compiled': function(){ + var Indexed = new Schema({ + name : { type: String, index: true } + , last : String + , email : String + }); + + Indexed.index({ last: 1, email: 1 }, { unique: true }); + + var db = start() + , IndexedModel = db.model('IndexedModel', Indexed, 'indexedmodel' + random()) + , assertions = 0; + + IndexedModel.on('index', function(){ + IndexedModel.collection.getIndexes(function(err, indexes){ + should.strictEqual(err, null); + + for (var i in indexes) + indexes[i].forEach(function(index){ + if (index[0] == 'name') + assertions++; + if (index[0] == 'last') + assertions++; + if (index[0] == 'email') + assertions++; + }); + + assertions.should.eql(3); + db.close(); + }); + }); + }, + + 'test indexes on embedded documents': function () { + var BlogPosts = new Schema({ + _id : { type: ObjectId, index: true } + , title : { type: String, index: true } + , desc : String + }); + + var User = new Schema({ + name : { type: String, index: true } + , blogposts : [BlogPosts] + }); + + var db = start() + , UserModel = db.model('DeepIndexedModel', User, 'deepindexedmodel' + random()) + , assertions = 0; + + UserModel.on('index', function () { + UserModel.collection.getIndexes(function (err, indexes) { + should.strictEqual(err, null); + + for (var i in indexes) + indexes[i].forEach(function(index){ + if (index[0] == 'name') + assertions++; + if (index[0] == 'blogposts._id') + assertions++; + if (index[0] == 'blogposts.title') + assertions++; + }); + + assertions.should.eql(3); + db.close(); + }); + }); + }, + + 'compound indexes on embedded documents should be created': function () { + var BlogPosts = new Schema({ + title : String + , desc : String + }); + + BlogPosts.index({ title: 1, desc: 1 }); + + var User = new Schema({ + name : { type: String, index: true } + , blogposts : [BlogPosts] + }); + + var db = start() + , UserModel = db.model('DeepCompoundIndexModel', User, 'deepcompoundindexmodel' + random()) + , found = 0; + + UserModel.on('index', function () { + UserModel.collection.getIndexes(function (err, indexes) { + should.strictEqual(err, null); + + for (var index in indexes) { + switch (index) { + case 'name_1': + case 'blogposts.title_1_blogposts.desc_1': + ++found; + break; + } + } + + db.close(); + found.should.eql(2); + }); + }); + }, + + 'test getters with same name on embedded documents not clashing': function() { + var Post = new Schema({ + title : String + , author : { name : String } + , subject : { name : String } + }); + + mongoose.model('PostWithClashGetters', Post); + + var db = start() + , PostModel = db.model('PostWithClashGetters', 'postwithclash' + random()); + + var post = new PostModel({ + title: 'Test' + , author: { name: 'A' } + , subject: { name: 'B' } + }); + + post.author.name.should.eql('A'); + post.subject.name.should.eql('B'); + post.author.name.should.eql('A'); + + db.close(); + }, + + 'test post save middleware': function () { + var schema = new Schema({ + title: String + }); + + var called = 0; + + schema.post('save', function (obj) { + obj.title.should.eql('Little Green Running Hood'); + called.should.equal(0); + called++; + }); + + schema.post('save', function (obj) { + obj.title.should.eql('Little Green Running Hood'); + called.should.equal(1); + called++; + }); + + schema.post('save', function (obj) { + obj.title.should.eql('Little Green Running Hood'); + called.should.equal(2); + db.close(); + }); + + var db = start() + , TestMiddleware = db.model('TestPostSaveMiddleware', schema); + + var test = new TestMiddleware({ title: 'Little Green Running Hood'}); + + test.save(function(err){ + should.strictEqual(err, null); + }); + }, + + 'test middleware': function () { + var schema = new Schema({ + title: String + }); + + var called = 0; + + schema.pre('init', function (next) { + called++; + next(); + }); + + schema.pre('save', function (next) { + called++; + next(new Error('Error 101')); + }); + + schema.pre('remove', function (next) { + called++; + next(); + }); + + mongoose.model('TestMiddleware', schema); + + var db = start() + , TestMiddleware = db.model('TestMiddleware'); + + var test = new TestMiddleware(); + + test.init({ + title: 'Test' + }); + + called.should.eql(1); + + test.save(function(err){ + err.should.be.an.instanceof(Error); + err.message.should.eql('Error 101'); + called.should.eql(2); + + test.remove(function(err){ + should.strictEqual(err, null); + called.should.eql(3); + db.close(); + }); + }); + }, + + 'test post init middleware': function () { + var schema = new Schema({ + title: String + }); + + var preinit = 0 + , postinit = 0 + + schema.pre('init', function (next) { + ++preinit; + next(); + }); + + schema.post('init', function () { + ++postinit; + }); + + mongoose.model('TestPostInitMiddleware', schema); + + var db = start() + , Test = db.model('TestPostInitMiddleware'); + + var test = new Test({ title: "banana" }); + + test.save(function(err){ + should.strictEqual(err, null); + + Test.findById(test._id, function (err, test) { + should.strictEqual(err, null); + preinit.should.eql(1); + postinit.should.eql(1); + test.remove(function(err){ + db.close(); + }); + }); + }); + }, + + 'test updating documents': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , title = 'Tobi ' + random() + , author = 'Brian ' + random() + , newTitle = 'Woot ' + random() + , id0 = new DocumentObjectId + , id1 = new DocumentObjectId + + var post = new BlogPost(); + post.set('title', title); + post.author = author; + post.meta.visitors = 0; + post.date = new Date; + post.published = true; + post.mixed = { x: 'ex' }; + post.numbers = [4,5,6,7]; + post.owners = [id0, id1]; + post.comments = [{ body: 'been there' }, { body: 'done that' }]; + + post.save(function (err) { + should.strictEqual(err, null); + BlogPost.findById(post._id, function (err, cf) { + should.strictEqual(err, null); + cf.title.should.equal(title); + cf.author.should.equal(author); + cf.meta.visitors.valueOf().should.eql(0); + cf.date.should.eql(post.date); + cf.published.should.be.true; + cf.mixed.x.should.equal('ex'); + cf.numbers.toObject().should.eql([4,5,6,7]); + cf.owners.length.should.equal(2); + cf.owners[0].toString().should.equal(id0.toString()); + cf.owners[1].toString().should.equal(id1.toString()); + cf.comments.length.should.equal(2); + cf.comments[0].body.should.eql('been there'); + cf.comments[1].body.should.eql('done that'); + should.exist(cf.comments[0]._id); + should.exist(cf.comments[1]._id); + cf.comments[0]._id.should.be.an.instanceof(DocumentObjectId) + cf.comments[1]._id.should.be.an.instanceof(DocumentObjectId); + + var update = { + title: newTitle // becomes $set + , $inc: { 'meta.visitors': 2 } + , $set: { date: new Date } + , published: false // becomes $set + , 'mixed': { x: 'ECKS', y: 'why' } // $set + , $pullAll: { 'numbers': [4, 6] } + , $pull: { 'owners': id0 } + , 'comments.1.body': 8 // $set + } + + BlogPost.update({ title: title }, update, function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, up) { + should.strictEqual(err, null); + up.title.should.equal(newTitle); + up.author.should.equal(author); + up.meta.visitors.valueOf().should.equal(2); + up.date.toString().should.equal(update.$set.date.toString()); + up.published.should.eql(false); + up.mixed.x.should.equal('ECKS'); + up.mixed.y.should.equal('why'); + up.numbers.toObject().should.eql([5,7]); + up.owners.length.should.equal(1); + up.owners[0].toString().should.eql(id1.toString()); + up.comments[0].body.should.equal('been there'); + up.comments[1].body.should.equal('8'); + should.exist(up.comments[0]._id); + should.exist(up.comments[1]._id); + up.comments[0]._id.should.be.an.instanceof(DocumentObjectId) + up.comments[1]._id.should.be.an.instanceof(DocumentObjectId); + + var update2 = { + 'comments.body': 'fail' + } + + BlogPost.update({ _id: post._id }, update2, function (err) { + should.strictEqual(!!err, true); + ;/^can't append to array using string field name \[body\]/.test(err.message).should.be.true; + BlogPost.findById(post, function (err, p) { + should.strictEqual(null, err); + + var update3 = { + $pull: 'fail' + } + + BlogPost.update({ _id: post._id }, update3, function (err) { + should.strictEqual(!!err, true); + ;/Invalid atomic update value/.test(err.message).should.be.true; + + var update4 = { + $inc: { idontexist: 1 } + } + + // should not overwrite doc when no valid paths are submitted + BlogPost.update({ _id: post._id }, update4, function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, up) { + should.strictEqual(err, null); + + up.title.should.equal(newTitle); + up.author.should.equal(author); + up.meta.visitors.valueOf().should.equal(2); + up.date.toString().should.equal(update.$set.date.toString()); + up.published.should.eql(false); + up.mixed.x.should.equal('ECKS'); + up.mixed.y.should.equal('why'); + up.numbers.toObject().should.eql([5,7]); + up.owners.length.should.equal(1); + up.owners[0].toString().should.eql(id1.toString()); + up.comments[0].body.should.equal('been there'); + up.comments[1].body.should.equal('8'); + // non-schema data was still stored in mongodb + should.strictEqual(1, up._doc.idontexist); + + update5(post); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + function update5 (post) { + var update = { + comments: [{ body: 'worked great' }] + , $set: {'numbers.1': 100} + , $inc: { idontexist: 1 } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(err, null); + + // get the underlying doc + BlogPost.collection.findOne({ _id: post._id }, function (err, doc) { + should.strictEqual(err, null); + + var up = new BlogPost; + up.init(doc); + up.comments.length.should.equal(1); + up.comments[0].body.should.equal('worked great'); + should.strictEqual(true, !! doc.comments[0]._id); + up.meta.visitors.valueOf().should.equal(2); + up.mixed.x.should.equal('ECKS'); + up.numbers.toObject().should.eql([5,100]); + up.numbers[1].valueOf().should.eql(100); + + doc.idontexist.should.equal(2); + doc.numbers[1].should.eql(100); + + update6(post); + }); + }); + } + + function update6 (post) { + var update = { + $pushAll: { comments: [{ body: 'i am number 2' }, { body: 'i am number 3' }] } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(3); + ret.comments[1].body.should.equal('i am number 2'); + should.strictEqual(true, !! ret.comments[1]._id); + ret.comments[1]._id.should.be.an.instanceof(DocumentObjectId) + ret.comments[2].body.should.equal('i am number 3'); + should.strictEqual(true, !! ret.comments[2]._id); + ret.comments[2]._id.should.be.an.instanceof(DocumentObjectId) + + update7(post); + }) + }); + } + + // gh-542 + function update7 (post) { + var update = { + $pull: { comments: { body: 'i am number 2' } } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(2); + ret.comments[0].body.should.equal('worked great'); + ret.comments[0]._id.should.be.an.instanceof(DocumentObjectId) + ret.comments[1].body.should.equal('i am number 3'); + ret.comments[1]._id.should.be.an.instanceof(DocumentObjectId) + + update8(post); + }) + }); + } + + // gh-479 + function update8 (post) { + function a () {}; + a.prototype.toString = function () { return 'MongoDB++' } + var crazy = new a; + + var update = { + $addToSet: { 'comments.$.comments': { body: 'The Ring Of Power' } } + , $set: { 'comments.$.title': crazy } + } + + BlogPost.update({ _id: post._id, 'comments.body': 'worked great' }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(2); + ret.comments[0].body.should.equal('worked great'); + ret.comments[0].title.should.equal('MongoDB++'); + should.strictEqual(true, !! ret.comments[0].comments); + ret.comments[0].comments.length.should.equal(1); + should.strictEqual(ret.comments[0].comments[0].body, 'The Ring Of Power'); + ret.comments[0]._id.should.be.an.instanceof(DocumentObjectId) + ret.comments[0].comments[0]._id.should.be.an.instanceof(DocumentObjectId) + ret.comments[1].body.should.equal('i am number 3'); + should.strictEqual(undefined, ret.comments[1].title); + ret.comments[1]._id.should.be.an.instanceof(DocumentObjectId) + + update9(post); + }) + }); + } + + // gh-479 + function update9 (post) { + var update = { + $inc: { 'comments.$.newprop': '1' } + , $set: { date: (new Date).getTime() } // check for single val casting + } + + BlogPost.update({ _id: post._id, 'comments.body': 'worked great' }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret._doc.comments[0]._doc.newprop.should.equal(1); + should.strictEqual(undefined, ret._doc.comments[1]._doc.newprop); + ret.date.should.be.an.instanceof(Date); + ret.date.toString().should.equal(update.$set.date.toString()); + + update10(post, ret); + }) + }); + } + + // gh-545 + function update10 (post, last) { + var owner = last.owners[0]; + + var update = { + $addToSet: { 'owners': owner } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.owners.length.should.equal(1); + ret.owners[0].toString().should.eql(owner.toString()); + + update11(post, ret); + }) + }); + } + + // gh-545 + function update11 (post, last) { + var owner = last.owners[0] + , newowner = new DocumentObjectId + + var update = { + $addToSet: { 'owners': { $each: [owner, newowner] }} + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.owners.length.should.equal(2); + ret.owners[0].toString().should.eql(owner.toString()); + ret.owners[1].toString().should.eql(newowner.toString()); + + update12(post, newowner); + }) + }); + } + + // gh-574 + function update12 (post, newowner) { + var update = { + $pop: { 'owners': -1 } + , $unset: { title: 1 } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.owners.length.should.equal(1); + ret.owners[0].toString().should.eql(newowner.toString()); + should.strictEqual(undefined, ret.title); + + update13(post, ret); + }) + }); + } + + function update13 (post, ret) { + var update = { + $set: { + 'comments.0.comments.0.date': '11/5/2011' + , 'comments.1.body': 9000 + } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(2); + ret.comments[0].body.should.equal('worked great'); + ret.comments[1].body.should.equal('9000'); + ret.comments[0].comments[0].date.toString().should.equal(new Date('11/5/2011').toString()) + ret.comments[1].comments.length.should.equal(0); + + update14(post, ret); + }) + }); + } + + // gh-542 + function update14 (post, ret) { + var update = { + $pull: { comments: { _id: ret.comments[0].id } } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + db.close(); + should.strictEqual(null, err); + ret.comments.length.should.equal(1); + ret.comments[0].body.should.equal('9000'); + }) + }); + } + + }, + + 'test update doc casting': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.set('title', '1'); + + var id = post.get('_id'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.update({ title: 1, _id: id }, { title: 2 }, function (err) { + should.strictEqual(err, null); + + BlogPost.findOne({ _id: post.get('_id') }, function (err, doc) { + should.strictEqual(err, null); + + doc.get('title').should.eql('2'); + db.close(); + }); + }); + }); + }, + + 'test $push casting': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.get('numbers').push('3'); + post.get('numbers')[0].should.equal(3); + db.close(); + }, + + 'test $pull casting': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.get('numbers').push(1, 2, 3, 4); + post.save( function (err) { + BlogPost.findById( post.get('_id'), function (err, found) { + found.get('numbers').length.should.equal(4); + found.get('numbers').$pull('3'); + found.save( function (err) { + BlogPost.findById( found.get('_id'), function (err, found2) { + found2.get('numbers').length.should.equal(3); + db.close(); + }); + }); + }); + }); + }, + + 'test updating numbers atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , totalDocs = 4 + , saveQueue = []; + + var post = new BlogPost(); + post.set('meta.visitors', 5); + + post.save(function(err){ + if (err) throw err; + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('meta.visitors').increment(); + doc.get('meta.visitors').valueOf().should.be.equal(6); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('meta.visitors').increment(); + doc.get('meta.visitors').valueOf().should.be.equal(6); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('meta.visitors').increment(); + doc.get('meta.visitors').valueOf().should.be.equal(6); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('meta.visitors').increment(); + doc.get('meta.visitors').valueOf().should.be.equal(6); + save(doc); + }); + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == 4) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + if (err) throw err; + --totalDocs || complete(); + }); + }); + }; + + function complete () { + BlogPost.findOne({ _id: post.get('_id') }, function (err, doc) { + if (err) throw err; + doc.get('meta.visitors').valueOf().should.be.equal(9); + db.close(); + }); + }; + }); + }, + + 'test incrementing a number atomically with an arbitrary value': function () { + var db = start() + , BlogPost = db.model('BlogPost'); + + var post = new BlogPost(); + + post.meta.visitors = 0; + + post.save(function (err) { + should.strictEqual(err, null); + + post.meta.visitors.increment(50); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + (+doc.meta.visitors).should.eql(50); + db.close(); + }); + }); + }); + }, + + // GH-203 + 'test changing a number non-atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + + post.meta.visitors = 5; + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + doc.meta.visitors -= 2; + + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + (+doc.meta.visitors).should.eql(3); + db.close(); + }); + }); + }); + }); + }, + + 'test saving subdocuments atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , totalDocs = 4 + , saveQueue = []; + + var post = new BlogPost(); + + post.save(function(err){ + if (err) throw err; + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('comments').push({ title: '1' }); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('comments').push({ title: '2' }); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('comments').push({ title: '3' }); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('comments').push({ title: '4' }, { title: '5' }); + save(doc); + }); + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == 4) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + if (err) throw err; + --totalDocs || complete(); + }); + }); + }; + + function complete () { + BlogPost.findOne({ _id: post.get('_id') }, function (err, doc) { + if (err) throw err; + + doc.get('comments').length.should.eql(5); + + doc.get('comments').some(function(comment){ + return comment.get('title') == '1'; + }).should.be.true; + + doc.get('comments').some(function(comment){ + return comment.get('title') == '2'; + }).should.be.true; + + doc.get('comments').some(function(comment){ + return comment.get('title') == '3'; + }).should.be.true; + + doc.get('comments').some(function(comment){ + return comment.get('title') == '4'; + }).should.be.true; + + doc.get('comments').some(function(comment){ + return comment.get('title') == '5'; + }).should.be.true; + + db.close(); + }); + }; + }); + }, + + // GH-310 + 'test setting a subdocument atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + + BlogPost.create({ + comments: [{ title: 'first-title', body: 'first-body'}] + }, function (err, blog) { + should.strictEqual(null, err); + BlogPost.findById(blog.id, function (err, agent1blog) { + should.strictEqual(null, err); + BlogPost.findById(blog.id, function (err, agent2blog) { + should.strictEqual(null, err); + agent1blog.get('comments')[0].title = 'second-title'; + agent1blog.save( function (err) { + should.strictEqual(null, err); + agent2blog.get('comments')[0].body = 'second-body'; + agent2blog.save( function (err) { + should.strictEqual(null, err); + BlogPost.findById(blog.id, function (err, foundBlog) { + should.strictEqual(null, err); + db.close(); + var comment = foundBlog.get('comments')[0]; + comment.title.should.eql('second-title'); + comment.body.should.eql('second-body'); + }); + }); + }); + }); + }); + }); + }, + + 'test doubly nested array saving and loading': function(){ + var Inner = new Schema({ + arr: [Number] + }); + + var Outer = new Schema({ + inner: [Inner] + }); + mongoose.model('Outer', Outer); + + var db = start(); + var Outer = db.model('Outer', 'arr_test_' + random()); + + var outer = new Outer(); + outer.inner.push({}); + outer.save(function(err) { + should.strictEqual(err, null); + outer.get('_id').should.be.an.instanceof(DocumentObjectId); + + Outer.findById(outer.get('_id'), function(err, found) { + should.strictEqual(err, null); + should.equal(1, found.inner.length); + found.inner[0].arr.push(5); + found.save(function(err) { + should.strictEqual(err, null); + found.get('_id').should.be.an.instanceof(DocumentObjectId); + Outer.findById(found.get('_id'), function(err, found2) { + db.close(); + should.strictEqual(err, null); + should.equal(1, found2.inner.length); + should.equal(1, found2.inner[0].arr.length); + should.equal(5, found2.inner[0].arr[0]); + }); + }); + }); + }); + }, + + 'test updating multiple Number $pushes as a single $pushAll': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({}, function (err, t) { + t.nested.nums.push(1); + t.nested.nums.push(2); + + t.nested.nums.should.have.length(2); + + t.save( function (err) { + should.strictEqual(null, err); + t.nested.nums.should.have.length(2); + Temp.findById(t._id, function (err, found) { + found.nested.nums.should.have.length(2); + db.close(); + }); + }); + }); + }, + + 'test updating at least a single $push and $pushAll as a single $pushAll': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({}, function (err, t) { + t.nested.nums.push(1); + t.nested.nums.$pushAll([2, 3]); + + t.nested.nums.should.have.length(3); + + t.save( function (err) { + should.strictEqual(null, err); + t.nested.nums.should.have.length(3); + Temp.findById(t._id, function (err, found) { + found.nested.nums.should.have.length(3); + db.close(); + }); + }); + }); + }, + + 'test activePaths should be updated for nested modifieds': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pull(1); + t.nested.nums.$pull(2); + + t._activePaths.stateOf('nested.nums').should.equal('modify'); + db.close(); + + }); + }, + + '$pull should affect what you see in an array before a save': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pull(1); + + t.nested.nums.should.have.length(4); + + db.close(); + }); + }, + + '$pullAll should affect what you see in an array before a save': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pullAll([1, 2, 3]); + + t.nested.nums.should.have.length(2); + + db.close(); + }); + }, + + 'test updating multiple Number $pulls as a single $pullAll': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pull(1); + t.nested.nums.$pull(2); + + t.nested.nums.should.have.length(3); + + t.save( function (err) { + should.strictEqual(null, err); + t.nested.nums.should.have.length(3); + Temp.findById(t._id, function (err, found) { + found.nested.nums.should.have.length(3); + db.close(); + }); + }); + }); + }, + + 'having both a pull and pullAll should default to pullAll': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pull(1); + t.nested.nums.$pullAll([2, 3]); + + t.nested.nums.should.have.length(2); + + t.save( function (err) { + should.strictEqual(null, err); + t.nested.nums.should.have.length(2); + Temp.findById(t._id, function (err, found) { + found.nested.nums.should.have.length(2); + db.close(); + }); + }); + }); + }, + + '$shift': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('TestingShift', schema); + var Temp = db.model('TestingShift', collection); + + Temp.create({ nested: { nums: [1,2,3] }}, function (err, t) { + should.strictEqual(null, err); + + Temp.findById(t._id, function (err, found) { + should.strictEqual(null, err); + found.nested.nums.should.have.length(3); + found.nested.nums.$pop(); + found.nested.nums.should.have.length(2); + found.nested.nums[0].should.eql(1); + found.nested.nums[1].should.eql(2); + + found.save(function (err) { + should.strictEqual(null, err); + Temp.findById(t._id, function (err, found) { + should.strictEqual(null, err); + found.nested.nums.should.have.length(2); + found.nested.nums[0].should.eql(1); + found.nested.nums[1].should.eql(2); + found.nested.nums.$shift(); + found.nested.nums.should.have.length(1); + found.nested.nums[0].should.eql(2); + + found.save(function (err) { + should.strictEqual(null, err); + Temp.findById(t._id, function (err, found) { + db.close(); + should.strictEqual(null, err); + found.nested.nums.should.have.length(1); + found.nested.nums[0].should.eql(2); + }); + }); + }); + }); + }); + }); + }, + + 'test saving embedded arrays of Numbers atomically': function () { + var db = start() + , TempSchema = new Schema({ + nums: [Number] + }) + , totalDocs = 2 + , saveQueue = []; + + mongoose.model('Temp', TempSchema); + var Temp = db.model('Temp', collection); + + var t = new Temp(); + + t.save(function(err){ + if (err) throw err; + + Temp.findOne({ _id: t.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('nums').push(1); + save(doc); + }); + + Temp.findOne({ _id: t.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('nums').push(2, 3); + save(doc); + }); + + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == totalDocs) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + if (err) throw err; + --totalDocs || complete(); + }); + }); + }; + + function complete () { + Temp.findOne({ _id: t.get('_id') }, function (err, doc) { + if (err) throw err; + + doc.get('nums').length.should.eql(3); + + doc.get('nums').some(function(num){ + return num.valueOf() == '1'; + }).should.be.true; + + doc.get('nums').some(function(num){ + return num.valueOf() == '2'; + }).should.be.true; + + doc.get('nums').some(function(num){ + return num.valueOf() == '3'; + }).should.be.true; + + + db.close(); + }); + }; + }); + }, + + 'test saving embedded arrays of Strings atomically': function () { + var db = start() + , StrListSchema = new Schema({ + strings: [String] + }) + , totalDocs = 2 + , saveQueue = []; + + mongoose.model('StrList', StrListSchema); + var StrList = db.model('StrList'); + + var t = new StrList(); + + t.save(function(err){ + if (err) throw err; + + StrList.findOne({ _id: t.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('strings').push('a'); + save(doc); + }); + + StrList.findOne({ _id: t.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('strings').push('b', 'c'); + save(doc); + }); + + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == totalDocs) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + if (err) throw err; + --totalDocs || complete(); + }); + }); + }; + + function complete () { + StrList.findOne({ _id: t.get('_id') }, function (err, doc) { + if (err) throw err; + + doc.get('strings').length.should.eql(3); + + doc.get('strings').some(function(str){ + return str == 'a'; + }).should.be.true; + + doc.get('strings').some(function(str){ + return str == 'b'; + }).should.be.true; + + doc.get('strings').some(function(str){ + return str == 'c'; + }).should.be.true; + + db.close(); + }); + }; + }); + }, + + 'test saving embedded arrays of Buffers atomically': function () { + var db = start() + , BufListSchema = new Schema({ + buffers: [Buffer] + }) + , totalDocs = 2 + , saveQueue = []; + + mongoose.model('BufList', BufListSchema); + var BufList = db.model('BufList'); + + var t = new BufList(); + + t.save(function(err){ + should.strictEqual(null, err); + + BufList.findOne({ _id: t.get('_id') }, function(err, doc){ + should.strictEqual(null, err); + doc.get('buffers').push(new Buffer([140])); + save(doc); + }); + + BufList.findOne({ _id: t.get('_id') }, function(err, doc){ + should.strictEqual(null, err); + doc.get('buffers').push(new Buffer([141]), new Buffer([142])); + save(doc); + }); + + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == totalDocs) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + should.strictEqual(null, err); + --totalDocs || complete(); + }); + }); + }; + + function complete () { + BufList.findOne({ _id: t.get('_id') }, function (err, doc) { + db.close(); + should.strictEqual(null, err); + + doc.get('buffers').length.should.eql(3); + + doc.get('buffers').some(function(buf){ + return buf[0] == 140; + }).should.be.true; + + doc.get('buffers').some(function(buf){ + return buf[0] == 141; + }).should.be.true; + + doc.get('buffers').some(function(buf){ + return buf[0] == 142; + }).should.be.true; + + }); + }; + }); + }, + + // GH-255 + 'test updating an embedded document in an embedded array': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({comments: [{title: 'woot'}]}, function (err, post) { + should.strictEqual(err, null); + BlogPost.findById(post._id, function (err, found) { + should.strictEqual(err, null); + found.comments[0].title.should.equal('woot'); + found.comments[0].title = 'notwoot'; + found.save( function (err) { + should.strictEqual(err, null); + BlogPost.findById(found._id, function (err, updated) { + db.close(); + should.strictEqual(err, null); + updated.comments[0].title.should.equal('notwoot'); + }); + }); + }); + }); + }, + + // GH-334 + 'test updating an embedded array document to an Object value': function () { + var db = start() + , SubSchema = new Schema({ + name : String , + subObj : { subName : String } + }); + var GH334Schema = new Schema ({ name : String , arrData : [ SubSchema] }); + + mongoose.model('GH334' , GH334Schema); + var AModel = db.model('GH334'); + var instance = new AModel(); + + instance.set( { name : 'name-value' , arrData : [ { name : 'arrName1' , subObj : { subName : 'subName1' } } ] }); + instance.save(function(err) { + AModel.findById(instance.id, function(err, doc) { + doc.arrData[0].set('subObj' , { subName : 'modified subName' }); + doc.save(function(err) { + should.strictEqual(null, err); + AModel.findById(instance.id, function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.arrData[0].subObj.subName.should.eql('modified subName'); + }); + }); + }); + }); + }, + + // GH-267 + 'saving an embedded document twice should not push that doc onto the parent doc twice': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.comments.push({title: 'woot'}); + post.save( function (err) { + should.strictEqual(err, null); + post.comments.should.have.length(1); + BlogPost.findById(post.id, function (err, found) { + should.strictEqual(err, null); + found.comments.should.have.length(1); + post.save( function (err) { + should.strictEqual(err, null); + post.comments.should.have.length(1); + BlogPost.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.comments.should.have.length(1); + }); + }); + }); + }); + }, + + 'test filtering an embedded array by the id shortcut function': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + + post.comments.push({ title: 'woot' }); + post.comments.push({ title: 'aaaa' }); + + var subdoc1 = post.comments[0]; + var subdoc2 = post.comments[1]; + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + // test with an objectid + doc.comments.id(subdoc1.get('_id')).title.should.eql('woot'); + + // test with a string + var id = DocumentObjectId.toString(subdoc2._id); + doc.comments.id(id).title.should.eql('aaaa'); + + db.close(); + }); + }); + }, + + 'test filtering an embedded array by the id with cast error': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + should.strictEqual(doc.comments.id(null), null); + + db.close(); + }); + }); + }, + + 'test filtering an embedded array by the id shortcut with no match': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + should.strictEqual(doc.comments.id(new DocumentObjectId), null); + + db.close(); + }); + }); + }, + + 'test for removing a subdocument atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.title = 'hahaha'; + post.comments.push({ title: 'woot' }); + post.comments.push({ title: 'aaaa' }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.comments[0].remove(); + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.comments.should.have.length(1); + doc.comments[0].title.should.eql('aaaa'); + + db.close(); + }); + }); + }); + }); + }, + + 'test for single pull embedded doc' : function() { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.title = 'hahaha'; + post.comments.push({ title: 'woot' }); + post.comments.push({ title: 'aaaa' }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.comments.pull(doc.comments[0]); + doc.comments.pull(doc.comments[0]); + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.comments.should.have.length(0); + db.close(); + }); + }); + }); + }); + }, + + 'try saving mixed data': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , count = 3; + + // string + var post = new BlogPost(); + post.mixed = 'woot'; + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err) { + should.strictEqual(err, null); + + --count || db.close(); + }); + }); + + // array + var post2 = new BlogPost(); + post2.mixed = { name: "mr bungle", arr: [] }; + post2.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post2._id, function (err, doc){ + should.strictEqual(err, null); + + Array.isArray(doc.mixed.arr).should.be.true; + + doc.mixed = [{foo: 'bar'}]; + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(doc._id, function (err, doc){ + should.strictEqual(err, null); + + Array.isArray(doc.mixed).should.be.true; + doc.mixed.push({ hello: 'world' }); + doc.mixed.push([ 'foo', 'bar' ]); + doc.commit('mixed'); + + doc.save(function (err, doc) { + should.strictEqual(err, null); + + BlogPost.findById(post2._id, function (err, doc) { + should.strictEqual(err, null); + + doc.mixed[0].should.eql({ foo: 'bar' }); + doc.mixed[1].should.eql({ hello: 'world' }); + doc.mixed[2].should.eql(['foo','bar']); + --count || db.close(); + }); + }); + }); + + // date + var post3 = new BlogPost(); + post3.mixed = new Date; + post3.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post3._id, function (err, doc) { + should.strictEqual(err, null); + + doc.mixed.should.be.an.instanceof(Date); + --count || db.close(); + }); + }); + }); + + }); + }); + + }, + + // GH-200 + 'try populating mixed data from the constructor': function () { + var db = start() + , BlogPost = db.model('BlogPost'); + + var post = new BlogPost({ + mixed: { + type: 'test' + , github: 'rules' + , nested: { + number: 3 + } + } + }); + + post.mixed.type.should.eql('test'); + post.mixed.github.should.eql('rules'); + post.mixed.nested.number.should.eql(3); + + db.close(); + }, + + 'test that we instantiate MongooseNumber in arrays': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.numbers.push(1, '2', 3); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + (~doc.numbers.indexOf(1)).should.not.eql(0); + (~doc.numbers.indexOf(2)).should.not.eql(0); + (~doc.numbers.indexOf(3)).should.not.eql(0); + + db.close(); + }); + }); + }, + + 'test removing from an array atomically using MongooseArray#remove': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.numbers.push(1, 2, 3); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + doc.numbers.remove('1'); + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.numbers.should.have.length(2); + doc.numbers.remove('2', '3'); + + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + doc.numbers.should.have.length(0); + db.close(); + }); + }); + }); + }); + }); + }); + }, + + 'test getting a virtual property via get(...)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost({ + title: 'Letters from Earth' + , author: 'Mark Twain' + }); + + post.get('titleWithAuthor').should.equal('Letters from Earth by Mark Twain'); + + db.close(); + }, + + 'test setting a virtual property': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.set('titleWithAuthor', 'Huckleberry Finn by Mark Twain') + post.get('title').should.equal('Huckleberry Finn'); + post.get('author').should.equal('Mark Twain'); + + db.close(); + }, + + 'test getting a virtual property via shortcut getter': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost({ + title: 'Letters from Earth' + , author: 'Mark Twain' + }); + + post.titleWithAuthor.should.equal('Letters from Earth by Mark Twain'); + + db.close(); + }, + + 'saving a doc with a set virtual property should persist the real properties but not the virtual property': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.set('titleWithAuthor', 'Huckleberry Finn by Mark Twain') + post.get('title').should.equal('Huckleberry Finn'); + post.get('author').should.equal('Mark Twain'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, found) { + should.strictEqual(err, null); + + found.get('title').should.equal('Huckleberry Finn'); + found.get('author').should.equal('Mark Twain'); + found.toObject().should.not.have.property('titleWithAuthor'); + db.close(); + }); + }); + }, + + 'test setting a pseudo-nested virtual property': function () { + var db = start() + , PersonSchema = new Schema({ + name: { + first: String + , last: String + } + }); + + PersonSchema.virtual('name.full') + .get( function () { + return this.get('name.first') + ' ' + this.get('name.last'); + }) + .set( function (fullName) { + var split = fullName.split(' '); + this.set('name.first', split[0]); + this.set('name.last', split[1]); + }); + + mongoose.model('Person', PersonSchema); + + var Person = db.model('Person') + , person = new Person({ + name: { + first: 'Michael' + , last: 'Sorrentino' + } + }); + + person.get('name.full').should.equal('Michael Sorrentino'); + person.set('name.full', 'The Situation'); + person.get('name.first').should.equal('The'); + person.get('name.last').should.equal('Situation'); + + person.name.full.should.equal('The Situation'); + person.name.full = 'Michael Sorrentino'; + person.name.first.should.equal('Michael'); + person.name.last.should.equal('Sorrentino'); + + db.close(); + }, + + 'test removing all documents from a collection via Model.remove': function () { + var db = start() + , collection = 'blogposts_' + random() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.find({}, function (err, found) { + should.strictEqual(err, null); + + found.length.should.equal(1); + + BlogPost.remove({}, function (err) { + should.strictEqual(!!err, false); + + BlogPost.find({}, function (err, found2) { + should.strictEqual(err, null); + + found2.should.have.length(0); + db.close(); + }); + }); + }); + }); + }, + + // GH-190 + 'test shorcut getter for a type defined with { type: Native }': function () { + var schema = new Schema({ + date: { type: Date } + }); + + mongoose.model('ShortcutGetterObject', schema); + + var db = start() + , ShortcutGetter = db.model('ShortcutGetterObject', 'shortcut' + random()) + , post = new ShortcutGetter(); + + post.set('date', Date.now()); + post.date.should.be.an.instanceof(Date); + + db.close(); + }, + + 'test shortcut getter for a nested path': function () { + var schema = new Schema({ + first: { + second: [Number] + } + }); + mongoose.model('ShortcutGetterNested', schema); + + var db = start() + , ShortcutGetterNested = db.model('ShortcutGetterNested', collection) + , doc = new ShortcutGetterNested(); + + doc.first.should.be.a('object'); + doc.first.second.should.be.an.instanceof(MongooseArray); + + db.close(); + }, + + // GH-195 + 'test that save on an unaltered model doesn\'t clear the document': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.title = 'woot'; + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + // we deliberately make no alterations + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(doc._id, function (err, doc) { + should.strictEqual(err, null); + + doc.title.should.eql('woot'); + db.close(); + }); + }); + }); + }); + }, + + 'test that safe mode is the default and it works': function () { + var Human = new Schema({ + name : String + , email : { type: String, unique: true } + }); + + mongoose.model('SafeHuman', Human, true); + + var db = start() + , Human = db.model('SafeHuman', 'safehuman' + random()); + + var me = new Human({ + name : 'Guillermo Rauch' + , email : 'rauchg@gmail.com' + }); + + me.save(function (err) { + should.strictEqual(err, null); + + Human.findById(me._id, function (err, doc){ + should.strictEqual(err, null); + doc.email.should.eql('rauchg@gmail.com'); + + var copycat = new Human({ + name : 'Lionel Messi' + , email : 'rauchg@gmail.com' + }); + + copycat.save(function (err) { + /duplicate/.test(err.message).should.be.true; + err.should.be.an.instanceof(Error); + db.close(); + }); + }); + }); + }, + + 'test that safe mode can be turned off': function () { + var Human = new Schema({ + name : String + , email : { type: String, unique: true } + }); + + // turn it off + Human.set('safe', false); + + mongoose.model('UnsafeHuman', Human, true); + + var db = start() + , Human = db.model('UnsafeHuman', 'unsafehuman' + random()); + + var me = new Human({ + name : 'Guillermo Rauch' + , email : 'rauchg@gmail.com' + }); + + me.save(function (err) { + should.strictEqual(err, null); + + Human.findById(me._id, function (err, doc){ + should.strictEqual(err, null); + doc.email.should.eql('rauchg@gmail.com'); + + var copycat = new Human({ + name : 'Lionel Messi' + , email : 'rauchg@gmail.com' + }); + + copycat.save(function (err) { + should.strictEqual(err, null); + db.close(); + }); + }); + }); + }, + + 'passing null in pre hook works': function () { + var db = start(); + var schema = new Schema({ name: String }); + + schema.pre('save', function (next) { + next(null); // <<----- + }); + + var S = db.model('S', schema, collection); + var s = new S({name: 'zupa'}); + + s.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + + }, + + 'pre hooks called on all sub levels': function () { + var db = start(); + + var grandSchema = new Schema({ name : String }); + grandSchema.pre('save', function (next) { + this.name = 'grand'; + next(); + }); + + var childSchema = new Schema({ name : String, grand : [grandSchema]}); + childSchema.pre('save', function (next) { + this.name = 'child'; + next(); + }); + + var schema = new Schema({ name: String, child : [childSchema] }); + + schema.pre('save', function (next) { + this.name = 'parent'; + next(); + }); + + var S = db.model('presave_hook', schema, 'presave_hook'); + var s = new S({ name : 'a' , child : [ { name : 'b', grand : [{ name : 'c'}] } ]}); + + s.save(function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.name.should.eql('parent'); + doc.child[0].name.should.eql('child'); + doc.child[0].grand[0].name.should.eql('grand'); + }); + }, + + 'pre hooks error on all sub levels': function () { + var db = start(); + + var grandSchema = new Schema({ name : String }); + grandSchema.pre('save', function (next) { + next(new Error('Error 101')); + }); + + var childSchema = new Schema({ name : String, grand : [grandSchema]}); + childSchema.pre('save', function (next) { + this.name = 'child'; + next(); + }); + + var schema = new Schema({ name: String, child : [childSchema] }); + schema.pre('save', function (next) { + this.name = 'parent'; + next(); + }); + + var S = db.model('presave_hook_error', schema, 'presave_hook_error'); + var s = new S({ name : 'a' , child : [ { name : 'b', grand : [{ name : 'c'}] } ]}); + + s.save(function (err, doc) { + db.close(); + err.should.be.an.instanceof(Error); + err.message.should.eql('Error 101'); + }); + }, + + 'test post hooks': function () { + var schema = new Schema({ + title: String + }) + , save = false + , remove = false + , init = false; + + schema.post('save', function () { + save = true; + }); + + schema.post('init', function () { + init = true; + }); + + schema.post('remove', function () { + remove = true; + }); + + mongoose.model('PostHookTest', schema); + + var db = start() + , BlogPost = db.model('PostHookTest'); + + var post = new BlogPost(); + + post.save(function (err) { + process.nextTick(function () { + should.strictEqual(err, null); + save.should.be.true; + + BlogPost.findById(post._id, function (err, doc) { + process.nextTick(function () { + should.strictEqual(err, null); + init.should.be.true; + + doc.remove(function (err) { + process.nextTick(function () { + should.strictEqual(err, null); + remove.should.be.true; + db.close(); + }); + }); + }); + }); + }); + }); + }, + + 'test count querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable count as promise'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.count({title: 'interoperable count as promise'}); + query.exec(function (err, count) { + should.strictEqual(err, null); + count.should.equal(1); + db.close(); + }); + }); + }, + + 'test update querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable update as promise'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.update({title: 'interoperable update as promise'}, {title: 'interoperable update as promise delta'}); + query.exec(function (err) { + should.strictEqual(err, null); + BlogPost.count({title: 'interoperable update as promise delta'}, function (err, count) { + should.strictEqual(err, null); + count.should.equal(1); + db.close(); + }); + }); + }); + }, + + 'test findOne querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable findOne as promise'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.findOne({title: 'interoperable findOne as promise'}); + query.exec(function (err, found) { + should.strictEqual(err, null); + found.id.should.eql(created.id); + db.close(); + }); + }); + }, + + 'test find querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create( + {title: 'interoperable find as promise'} + , {title: 'interoperable find as promise'} + , function (err, createdOne, createdTwo) { + should.strictEqual(err, null); + var query = BlogPost.find({title: 'interoperable find as promise'}); + query.exec(function (err, found) { + should.strictEqual(err, null); + found.length.should.equal(2); + found[0]._id.id.should.eql(createdOne._id.id); + found[1]._id.id.should.eql(createdTwo._id.id); + db.close(); + }); + }); + }, + + 'test remove querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create( + {title: 'interoperable remove as promise'} + , function (err, createdOne, createdTwo) { + should.strictEqual(err, null); + var query = BlogPost.remove({title: 'interoperable remove as promise'}); + query.exec(function (err) { + should.strictEqual(err, null); + BlogPost.count({title: 'interoperable remove as promise'}, function (err, count) { + db.close(); + count.should.equal(0); + }); + }); + }); + }, + + 'test changing query at the last minute via #run(op, callback)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable ad-hoc as promise'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.count({title: 'interoperable ad-hoc as promise'}); + query.exec('findOne', function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + db.close(); + }); + }); + }, + + 'test count querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable count as promise 2'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.count({title: 'interoperable count as promise 2'}); + var promise = query.exec(); + promise.addBack(function (err, count) { + should.strictEqual(err, null); + count.should.equal(1); + db.close(); + }); + }); + }, + + 'test update querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable update as promise 2'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.update({title: 'interoperable update as promise 2'}, {title: 'interoperable update as promise delta 2'}); + var promise = query.run(); + promise.addBack(function (err) { + should.strictEqual(err, null); + BlogPost.count({title: 'interoperable update as promise delta 2'}, function (err, count) { + should.strictEqual(err, null); + count.should.equal(1); + db.close(); + }); + }); + }); + }, + + 'test findOne querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable findOne as promise 2'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.findOne({title: 'interoperable findOne as promise 2'}); + var promise = query.exec(); + promise.addBack(function (err, found) { + should.strictEqual(err, null); + found.id.should.eql(created.id); + db.close(); + }); + }); + }, + + 'test find querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create( + {title: 'interoperable find as promise 2'} + , {title: 'interoperable find as promise 2'} + , function (err, createdOne, createdTwo) { + should.strictEqual(err, null); + var query = BlogPost.find({title: 'interoperable find as promise 2'}); + var promise = query.run(); + promise.addBack(function (err, found) { + should.strictEqual(err, null); + found.length.should.equal(2); + found[0].id; + found[1].id; + found[0]._id.should.eql(createdOne._id); + found[1]._id.should.eql(createdTwo._id); + db.close(); + }); + }); + }, + + 'test remove querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create( + {title: 'interoperable remove as promise 2'} + , function (err, createdOne, createdTwo) { + should.strictEqual(err, null); + var query = BlogPost.remove({title: 'interoperable remove as promise 2'}); + var promise = query.exec(); + promise.addBack(function (err) { + should.strictEqual(err, null); + BlogPost.count({title: 'interoperable remove as promise 2'}, function (err, count) { + count.should.equal(0); + db.close(); + }); + }); + }); + }, + + 'test changing query at the last minute via #run(op) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable ad-hoc as promise 2'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.count({title: 'interoperable ad-hoc as promise 2'}); + var promise = query.exec('findOne'); + promise.addBack(function (err, found) { + should.strictEqual(err, null); + found._id.id.should.eql(created._id.id); + db.close(); + }); + }); + }, + + 'test nested obj literal getter/setters': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var date = new Date; + + var meta = { + date: date + , visitors: 5 + }; + + var post = new BlogPost() + post.init({ + meta: meta + }); + + post.get('meta').date.should.be.an.instanceof(Date); + post.meta.date.should.be.an.instanceof(Date); + + var threw = false; + var getter1; + var getter2; + var strmet; + try { + strmet = JSON.stringify(meta); + getter1 = JSON.stringify(post.get('meta')); + getter2 = JSON.stringify(post.meta); + } catch (err) { + threw = true; + } + + threw.should.be.false; + getter1 = JSON.parse(getter1); + getter2 = JSON.parse(getter2); + getter1.visitors.should.eql(getter2.visitors); + getter1.date.should.eql(getter2.date); + + post.meta.date = new Date - 1000; + post.meta.date.should.be.an.instanceof(Date); + post.get('meta').date.should.be.an.instanceof(Date); + + post.meta.visitors = 2; + post.get('meta').visitors.should.be.an.instanceof(MongooseNumber); + post.meta.visitors.should.be.an.instanceof(MongooseNumber); + + var newmeta = { + date: date - 2000 + , visitors: 234 + }; + + post.set(newmeta, 'meta'); + + post.meta.date.should.be.an.instanceof(Date); + post.get('meta').date.should.be.an.instanceof(Date); + post.meta.visitors.should.be.an.instanceof(MongooseNumber); + post.get('meta').visitors.should.be.an.instanceof(MongooseNumber); + (+post.meta.date).should.eql(date - 2000); + (+post.get('meta').date).should.eql(date - 2000); + (+post.meta.visitors).should.eql(234); + (+post.get('meta').visitors).should.eql(234); + + // set object directly + post.meta = { + date: date - 3000 + , visitors: 4815162342 + }; + + post.meta.date.should.be.an.instanceof(Date); + post.get('meta').date.should.be.an.instanceof(Date); + post.meta.visitors.should.be.an.instanceof(MongooseNumber); + post.get('meta').visitors.should.be.an.instanceof(MongooseNumber); + (+post.meta.date).should.eql(date - 3000); + (+post.get('meta').date).should.eql(date - 3000); + (+post.meta.visitors).should.eql(4815162342); + (+post.get('meta').visitors).should.eql(4815162342); + + db.close(); + }, + + 'nested object property access works when root initd with null': function () { + var db = start() + + var schema = new Schema({ + nest: { + st: String + } + }); + + mongoose.model('NestedStringA', schema); + var T = db.model('NestedStringA', collection); + + var t = new T({ nest: null }); + + should.strictEqual(t.nest.st, null); + t.nest = { st: "jsconf rules" }; + t.nest.toObject().should.eql({ st: "jsconf rules" }); + t.nest.st.should.eql("jsconf rules"); + + t.save(function (err) { + should.strictEqual(err, null); + db.close(); + }) + + }, + + 'nested object property access works when root initd with undefined': function () { + var db = start() + + var schema = new Schema({ + nest: { + st: String + } + }); + + mongoose.model('NestedStringB', schema); + var T = db.model('NestedStringB', collection); + + var t = new T({ nest: undefined }); + + should.strictEqual(t.nest.st, undefined); + t.nest = { st: "jsconf rules" }; + t.nest.toObject().should.eql({ st: "jsconf rules" }); + t.nest.st.should.eql("jsconf rules"); + + t.save(function (err) { + should.strictEqual(err, null); + db.close(); + }) + }, + + 're-saving object with pre-existing null nested object': function(){ + var db = start() + + var schema = new Schema({ + nest: { + st: String + , yep: String + } + }); + + mongoose.model('NestedStringC', schema); + var T = db.model('NestedStringC', collection); + + var t = new T({ nest: null }); + + t.save(function (err) { + should.strictEqual(err, null); + + t.nest = { st: "jsconf rules", yep: "it does" }; + t.save(function (err) { + should.strictEqual(err, null); + + T.findById(t.id, function (err, t) { + should.strictEqual(err, null); + t.nest.st.should.eql("jsconf rules"); + t.nest.yep.should.eql("it does"); + + t.nest = null; + t.save(function (err) { + should.strictEqual(err, null); + should.strictEqual(t._doc.nest, null); + db.close(); + }); + }); + }); + }); + }, + + 'pushing to a nested array of Mixed works on existing doc': function () { + var db = start(); + + mongoose.model('MySchema', new Schema({ + nested: { + arrays: [] + } + })); + + var DooDad = db.model('MySchema') + , doodad = new DooDad({ nested: { arrays: [] } }) + , date = 1234567890; + + doodad.nested.arrays.push(["+10", "yup", date]); + + doodad.save(function (err) { + should.strictEqual(err, null); + + DooDad.findById(doodad._id, function (err, doodad) { + should.strictEqual(err, null); + + doodad.nested.arrays.toObject().should.eql([['+10','yup',date]]); + + doodad.nested.arrays.push(["another", 1]); + + doodad.save(function (err) { + should.strictEqual(err, null); + + DooDad.findById(doodad._id, function (err, doodad) { + should.strictEqual(err, null); + + doodad + .nested + .arrays + .toObject() + .should.eql([['+10','yup',date], ["another", 1]]); + + db.close(); + }); + }); + }) + }); + + }, + + 'directly setting nested props works when property is named "type"': function () { + var db = start(); + + function def () { + return [{ x: 1 }, { x: 2 }, { x:3 }] + } + + mongoose.model('MySchema2', new Schema({ + nested: { + type: { type: String, default: 'yep' } + , array: { + type: Array, default: def + } + } + })); + + var DooDad = db.model('MySchema2', collection) + , doodad = new DooDad() + + doodad.save(function (err) { + should.strictEqual(err, null); + + DooDad.findById(doodad._id, function (err, doodad) { + should.strictEqual(err, null); + + doodad.nested.type.should.eql("yep"); + doodad.nested.array.toObject().should.eql([{x:1},{x:2},{x:3}]); + + doodad.nested.type = "nope"; + doodad.nested.array = ["some", "new", "stuff"]; + + doodad.save(function (err) { + should.strictEqual(err, null); + + DooDad.findById(doodad._id, function (err, doodad) { + should.strictEqual(err, null); + db.close(); + + doodad.nested.type.should.eql("nope"); + + doodad + .nested + .array + .toObject() + .should.eql(["some", "new", "stuff"]); + + }); + }); + }) + }); + }, + + 'system.profile should be a default model': function () { + var Profile = mongoose.model('system.profile'); + Profile.schema.paths.ts.should.be.a('object'); + Profile.schema.paths.info.should.be.a('object'); + Profile.schema.paths.millis.should.be.a('object'); + + Profile.schema.paths.op.should.be.a('object'); + Profile.schema.paths.ns.should.be.a('object'); + Profile.schema.paths.query.should.be.a('object'); + Profile.schema.paths.updateobj.should.be.a('object'); + Profile.schema.paths.ntoreturn.should.be.a('object'); + Profile.schema.paths.nreturned.should.be.a('object'); + Profile.schema.paths.nscanned.should.be.a('object'); + Profile.schema.paths.responseLength.should.be.a('object'); + Profile.schema.paths.client.should.be.a('object'); + Profile.schema.paths.user.should.be.a('object'); + Profile.schema.paths.idhack.should.be.a('object'); + Profile.schema.paths.scanAndOrder.should.be.a('object'); + Profile.schema.paths.keyUpdates.should.be.a('object'); + should.strictEqual(undefined, Profile.schema.paths._id); + should.strictEqual(undefined, Profile.schema.virtuals.id); + + var db = start(); + Profile = db.model('system.profile'); + db.close(); + Profile.schema.paths.ts.should.be.a('object'); + Profile.schema.paths.info.should.be.a('object'); + Profile.schema.paths.millis.should.be.a('object'); + Profile.schema.paths.op.should.be.a('object'); + Profile.schema.paths.ns.should.be.a('object'); + Profile.schema.paths.query.should.be.a('object'); + Profile.schema.paths.updateobj.should.be.a('object'); + Profile.schema.paths.ntoreturn.should.be.a('object'); + Profile.schema.paths.nreturned.should.be.a('object'); + Profile.schema.paths.nscanned.should.be.a('object'); + Profile.schema.paths.responseLength.should.be.a('object'); + Profile.schema.paths.client.should.be.a('object'); + Profile.schema.paths.user.should.be.a('object'); + Profile.schema.paths.idhack.should.be.a('object'); + Profile.schema.paths.scanAndOrder.should.be.a('object'); + Profile.schema.paths.keyUpdates.should.be.a('object'); + should.strictEqual(undefined, Profile.schema.paths._id); + should.strictEqual(undefined, Profile.schema.virtuals.id); + + // can override the default + db = start(); + // reset Mongoose state + delete db.base.modelSchemas['system.profile'] + delete db.base.models['system.profile'] + delete db.models['system.profile']; + db.close(); + // test + var over = db.model('system.profile', new Schema({ name: String })); + over.schema.paths.name.should.be.a('object'); + should.strictEqual(undefined, over.schema.paths.ts); + // reset + delete db.base.modelSchemas['system.profile'] + delete db.base.models['system.profile'] + delete db.models['system.profile']; + }, + + 'setting profiling levels': function () { + var db = start(); + db.setProfiling(3, function (err) { + err.message.should.eql('Invalid profiling level: 3'); + db.setProfiling('fail', function (err) { + err.message.should.eql('Invalid profiling level: fail'); + db.setProfiling(2, function (err, doc) { + should.strictEqual(err, null); + db.setProfiling(1, 50, function (err, doc) { + should.strictEqual(err, null); + doc.was.should.eql(2); + db.setProfiling(0, function (err, doc) { + db.close(); + should.strictEqual(err, null); + doc.was.should.eql(1); + doc.slowms.should.eql(50); + }); + }); + }); + }); + }); + }, + + 'test post hooks on embedded documents': function(){ + var save = false, + init = false, + remove = false; + + var EmbeddedSchema = new Schema({ + title : String + }); + + var ParentSchema = new Schema({ + embeds : [EmbeddedSchema] + }); + + EmbeddedSchema.post('save', function(next){ + save = true; + }); + + // Don't know how to test those on a embedded document. + //EmbeddedSchema.post('init', function () { + //init = true; + //}); + + //EmbeddedSchema.post('remove', function () { + //remove = true; + //}); + + mongoose.model('Parent', ParentSchema); + + var db = start(), + Parent = db.model('Parent'); + + var parent = new Parent(); + + parent.embeds.push({title: 'Testing post hooks for embedded docs'}); + + parent.save(function(err){ + db.close(); + should.strictEqual(err, null); + save.should.be.true; + }); + }, + + 'console.log shows helpful values': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var date = new Date(1305730951086); + var id0 = new DocumentObjectId('4dd3e169dbfb13b4570000b9'); + var id1 = new DocumentObjectId('4dd3e169dbfb13b4570000b6'); + var id2 = new DocumentObjectId('4dd3e169dbfb13b4570000b7'); + var id3 = new DocumentObjectId('4dd3e169dbfb13b4570000b8'); + + var post = new BlogPost({ + title: 'Test' + , _id: id0 + , date: date + , numbers: [5,6,7] + , owners: [id1] + , meta: { visitors: 45 } + , comments: [ + { _id: id2, title: 'my comment', date: date, body: 'this is a comment' }, + { _id: id3, title: 'the next thang', date: date, body: 'this is a comment too!' }] + }); + + db.close(); + + var a = '{ meta: { visitors: 45 },\n numbers: [ 5, 6, 7 ],\n owners: [ 4dd3e169dbfb13b4570000b6 ],\n comments: \n [{ _id: 4dd3e169dbfb13b4570000b7,\n comments: [],\n body: \'this is a comment\',\n date: Wed, 18 May 2011 15:02:31 GMT,\n title: \'my comment\' }\n { _id: 4dd3e169dbfb13b4570000b8,\n comments: [],\n body: \'this is a comment too!\',\n date: Wed, 18 May 2011 15:02:31 GMT,\n title: \'the next thang\' }],\n _id: 4dd3e169dbfb13b4570000b9,\n date: Wed, 18 May 2011 15:02:31 GMT,\n title: \'Test\' }' + + var out = post.inspect(); + ;/meta: { visitors: 45 }/.test(out).should.be.true; + ;/numbers: \[ 5, 6, 7 \]/.test(out).should.be.true; + ;/date: Wed, 18 May 2011 15:02:31 GMT/.test(out).should.be.true; + ;/activePaths:/.test(out).should.be.false; + ;/_atomics:/.test(out).should.be.false; + }, + + 'path can be used as pathname': function () { + var db = start() + , P = db.model('pathnametest', new Schema({ path: String })) + db.close(); + + var threw = false; + try { + new P({ path: 'i should not throw' }); + } catch (err) { + threw = true; + } + + threw.should.be.false; + }, + + 'when mongo is down, save callback should fire with err if auto_reconnect is disabled': function () { + var db = start({ server: { auto_reconnect: false }}); + var T = db.model('Thing', new Schema({ type: String })); + db.on('open', function () { + var t = new T({ type: "monster" }); + + var worked = false; + t.save(function (err) { + worked = true; + err.message.should.eql('no open connections'); + }); + + process.nextTick(function () { + db.close(); + }); + + setTimeout(function () { + worked.should.be.true; + }, 500); + }); + }, + + //'when mongo is down, auto_reconnect should kick in and db operation should succeed': function () { + //var db = start(); + //var T = db.model('Thing', new Schema({ type: String })); + //db.on('open', function () { + //var t = new T({ type: "monster" }); + + //var worked = false; + //t.save(function (err) { + //should.strictEqual(err, null); + //worked = true; + //}); + + //process.nextTick(function () { + //db.close(); + //}); + + //setTimeout(function () { + //worked.should.be.true; + //}, 500); + //}); + //}, + + 'subdocuments with changed values should persist the values': function () { + var db = start() + var Subdoc = new Schema({ name: String, mixed: Schema.Types.Mixed }); + var T = db.model('SubDocMixed', new Schema({ subs: [Subdoc] })); + + var t = new T({ subs: [{ name: "Hubot", mixed: { w: 1, x: 2 }}] }); + t.subs[0].name.should.equal("Hubot"); + t.subs[0].mixed.w.should.equal(1); + t.subs[0].mixed.x.should.equal(2); + + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t._id, function (err, t) { + should.strictEqual(null, err); + t.subs[0].name.should.equal("Hubot"); + t.subs[0].mixed.w.should.equal(1); + t.subs[0].mixed.x.should.equal(2); + + var sub = t.subs[0]; + sub.name = "Hubot1"; + sub.name.should.equal("Hubot1"); + sub.isModified('name').should.be.true; + t.modified.should.be.true; + + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t._id, function (err, t) { + should.strictEqual(null, err); + should.strictEqual(t.subs[0].name, "Hubot1"); + + var sub = t.subs[0]; + sub.mixed.w = 5; + sub.mixed.w.should.equal(5); + sub.isModified('mixed').should.be.false; + sub.commit('mixed'); + sub.isModified('mixed').should.be.true; + sub.modified.should.be.true; + t.modified.should.be.true; + + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t._id, function (err, t) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(t.subs[0].mixed.w, 5); + }) + }) + }); + }); + }) + }) + }, + + 'RegExps can be saved': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ mixed: { rgx: /^asdf$/ } }); + post.mixed.rgx.should.be.instanceof(RegExp); + post.mixed.rgx.source.should.eql('^asdf$'); + post.save(function (err) { + should.strictEqual(err, null); + BlogPost.findById(post._id, function (err, post) { + db.close(); + should.strictEqual(err, null); + post.mixed.rgx.should.be.instanceof(RegExp); + post.mixed.rgx.source.should.eql('^asdf$'); + }); + }); + }, + + 'null defaults are allowed': function () { + var db = start(); + var T = db.model('NullDefault', new Schema({ name: { type: String, default: null }}), collection); + var t = new T(); + + should.strictEqual(null, t.name); + + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t._id, function (err, t) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(null, t.name); + }); + }); + }, + + // GH-365, GH-390, GH-422 + 'test that setters are used on embedded documents': function () { + var db = start(); + + function setLat (val) { + return parseInt(val); + } + + var tick = 0; + function uptick () { + return ++tick; + } + + var Location = new Schema({ + lat: { type: Number, default: 0, set: setLat} + , long: { type: Number, set: uptick } + }); + + var Deal = new Schema({ + title: String + , locations: [Location] + }); + + Location = db.model('Location', Location, 'locations_' + random()); + Deal = db.model('Deal', Deal, 'deals_' + random()); + + var location = new Location({lat: 1.2, long: 10}); + location.lat.valueOf().should.equal(1); + location.long.valueOf().should.equal(1); + + var deal = new Deal({title: "My deal", locations: [{lat: 1.2, long: 33}]}); + deal.locations[0].lat.valueOf().should.equal(1); + deal.locations[0].long.valueOf().should.equal(2); + + deal.save(function (err) { + should.strictEqual(err, null); + Deal.findById(deal._id, function (err, deal) { + db.close(); + should.strictEqual(err, null); + deal.locations[0].lat.valueOf().should.equal(1); + // GH-422 + deal.locations[0].long.valueOf().should.equal(2); + }); + }); + }, + + // GH-289 + 'test that pre-init middleware has access to the true ObjectId when used with querying': function () { + var db = start() + , PreInitSchema = new Schema({}) + , preId; + PreInitSchema.pre('init', function (next) { + preId = this._id; + next(); + }); + var PreInit = db.model('PreInit', PreInitSchema, 'pre_inits' + random()); + + var doc = new PreInit(); + doc.save( function (err) { + should.strictEqual(err, null); + PreInit.findById(doc._id, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(undefined, preId); + }); + }); + }, + + // Demonstration showing why GH-261 is a misunderstanding + 'a single instantiated document should be able to update its embedded documents more than once': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.comments.push({title: 'one'}); + post.save(function (err) { + should.strictEqual(err, null); + post.comments[0].title.should.eql('one'); + post.comments[0].title = 'two'; + post.comments[0].title.should.eql('two'); + post.save( function (err) { + should.strictEqual(err, null); + BlogPost.findById(post._id, function (err, found) { + should.strictEqual(err, null); + db.close(); + should.strictEqual(err, null); + found.comments[0].title.should.eql('two'); + }); + }); + }); + }, + + 'defining a method on a Schema corresponding to an EmbeddedDocument should generate an instance method for the ED': function () { + var db = start(); + var ChildSchema = new Schema({ name: String }); + ChildSchema.method('talk', function () { + return 'gaga'; + }); + + var ParentSchema = new Schema({ + children: [ChildSchema] + }); + + var ChildA = db.model('ChildA', ChildSchema, 'children_' + random()); + var ParentA = db.model('ParentA', ParentSchema, 'parents_' + random()); + + var c = new ChildA; + c.talk.should.be.a('function'); + + var p = new ParentA(); + p.children.push({}); + p.children[0].talk.should.be.a('function'); + db.close(); + }, + + 'Model#save should emit an error on its db if a callback is not passed to it': function () { + var db = start(); + + var DefaultErrSchema = new Schema({}); + + var err = ""; + + DefaultErrSchema.pre('save', function (next, fn) { + try { + next(new Error); + } catch (error) { + // throws b/c nothing is listening to the error event + error.should.be.instanceof(Error); + + db.on('error', function (err) { + db.close(); + err.should.be.an.instanceof(Error); + }); + + next(new Error); + } + }); + + var DefaultErr = db.model('DefaultErr', DefaultErrSchema, 'default_err_' + random()); + + var e = new DefaultErr(); + + e.save(); + }, + + 'ensureIndex error should emit on the db': function () { + var db = start(); + + db.on('error', function (err) { + /^E11000 duplicate key error index:/.test(err.message).should.equal(true); + db.close(); + }); + + var schema = new Schema({ name: { type: String } }) + , Test = db.model('IndexError', schema, "x"+random()); + + Test.create({ name: 'hi' }, { name: 'hi' }, function (err) { + should.strictEqual(err, null); + Test.schema.index({ name: 1 }, { unique: true }); + Test.init(); + }); + }, + + 'backward compatibility with conflicted data in the db': function () { + var db = start(); + var M = db.model('backwardDataConflict', new Schema({ namey: { first: String, last: String }})); + var m = new M({ namey: "[object Object]" }); + m.namey = { first: 'GI', last: 'Joe' };// <-- should overwrite the string + m.save(function (err) { + db.close(); + should.strictEqual(err, null); + should.strictEqual('GI', m.namey.first); + should.strictEqual('Joe', m.namey.last); + }); + }, + + '#push should work on EmbeddedDocuments more than 2 levels deep': function () { + var db = start() + , Post = db.model('BlogPost', collection) + , Comment = db.model('CommentNesting', Comments, collection); + + var p =new Post({ title: "comment nesting" }); + var c1 =new Comment({ title: "c1" }); + var c2 =new Comment({ title: "c2" }); + var c3 =new Comment({ title: "c3" }); + + p.comments.push(c1); + c1.comments.push(c2); + c2.comments.push(c3); + + p.save(function (err) { + should.strictEqual(err, null); + + Post.findById(p._id, function (err, p) { + should.strictEqual(err, null); + + var c4=new Comment({ title: "c4" }); + p.comments[0].comments[0].comments[0].comments.push(c4); + p.save(function (err) { + should.strictEqual(err, null); + + Post.findById(p._id, function (err, p) { + db.close(); + should.strictEqual(err, null); + p.comments[0].comments[0].comments[0].comments[0].title.should.equal('c4'); + }); + }); + }); + }) + + }, + + 'test standalone invalidate': function() { + var db = start() + , InvalidateSchema = null + , Post = null + , post = null; + + InvalidateSchema = new Schema({ + prop: { type: String }, + }); + + mongoose.model('InvalidateSchema', InvalidateSchema); + + Post = db.model('InvalidateSchema'); + post = new Post(); + post.set({baz: 'val'}); + post.invalidate('baz', 'reason'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + err.errors.baz.should.be.an.instanceof(ValidatorError); + err.errors.baz.message.should.equal('Validator "reason" failed for path baz'); + err.errors.baz.type.should.equal('reason'); + err.errors.baz.path.should.equal('baz'); + + post.save(function(err){ + db.close(); + should.strictEqual(err, null); + }); + }); + }, + + 'test simple validation middleware': function() { + var db = start() + , ValidationMiddlewareSchema = null + , Post = null + , post = null; + + ValidationMiddlewareSchema = new Schema({ + baz: { type: String } + }); + + ValidationMiddlewareSchema.pre('validate', function(next) { + if (this.get('baz') == 'bad') { + this.invalidate('baz', 'bad'); + } + next(); + }); + + mongoose.model('ValidationMiddleware', ValidationMiddlewareSchema); + + Post = db.model('ValidationMiddleware'); + post = new Post(); + post.set({baz: 'bad'}); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.baz.type.should.equal('bad'); + err.errors.baz.path.should.equal('baz'); + + post.set('baz', 'good'); + post.save(function(err){ + db.close(); + should.strictEqual(err, null); + }); + }); + }, + + 'test async validation middleware': function() { + var db = start() + , AsyncValidationMiddlewareSchema = null + , Post = null + , post = null; + + AsyncValidationMiddlewareSchema = new Schema({ + prop: { type: String } + }); + + AsyncValidationMiddlewareSchema.pre('validate', true, function(next, done) { + var self = this; + setTimeout(function() { + if (self.get('prop') == 'bad') { + self.invalidate('prop', 'bad'); + } + done(); + }, 50); + next(); + }); + + mongoose.model('AsyncValidationMiddleware', AsyncValidationMiddlewareSchema); + + Post = db.model('AsyncValidationMiddleware'); + post = new Post(); + post.set({prop: 'bad'}); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.prop.type.should.equal('bad'); + err.errors.prop.path.should.equal('prop'); + + post.set('prop', 'good'); + post.save(function(err){ + db.close(); + should.strictEqual(err, null); + }); + }); + }, + + 'test complex validation middleware': function() { + var db = start() + , ComplexValidationMiddlewareSchema = null + , Post = null + , post = null + , abc = function(v) { + return v === 'abc'; + }; + + ComplexValidationMiddlewareSchema = new Schema({ + baz: { type: String }, + abc: { type: String, validate: [abc, 'must be abc'] }, + test: { type: String, validate: [/test/, 'must also be abc'] }, + required: { type: String, required: true } + }); + + ComplexValidationMiddlewareSchema.pre('validate', true, function(next, done) { + var self = this; + setTimeout(function() { + if (self.get('baz') == 'bad') { + self.invalidate('baz', 'bad'); + } + done(); + }, 50); + next(); + }); + + mongoose.model('ComplexValidationMiddleware', ComplexValidationMiddlewareSchema); + + Post = db.model('ComplexValidationMiddleware'); + post = new Post(); + post.set({ + baz: 'bad', + abc: 'not abc', + test: 'fail' + }); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + Object.keys(err.errors).length.should.equal(4); + err.errors.baz.should.be.an.instanceof(ValidatorError); + err.errors.baz.type.should.equal('bad'); + err.errors.baz.path.should.equal('baz'); + err.errors.abc.should.be.an.instanceof(ValidatorError); + err.errors.abc.type.should.equal('must be abc'); + err.errors.abc.path.should.equal('abc'); + err.errors.test.should.be.an.instanceof(ValidatorError); + err.errors.test.type.should.equal('must also be abc'); + err.errors.test.path.should.equal('test'); + err.errors.required.should.be.an.instanceof(ValidatorError); + err.errors.required.type.should.equal('required'); + err.errors.required.path.should.equal('required'); + + post.set({ + baz: 'good', + abc: 'abc', + test: 'test', + required: 'here' + }); + + post.save(function(err){ + db.close(); + should.strictEqual(err, null); + }); + }); + }, + + 'Model.create can accept an array': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create([{ title: 'hi'}, { title: 'bye'}], function (err, post1, post2) { + db.close(); + should.strictEqual(err, null); + post1.get('_id').should.be.an.instanceof(DocumentObjectId); + post2.get('_id').should.be.an.instanceof(DocumentObjectId); + + post1.title.should.equal('hi'); + post2.title.should.equal('bye'); + }); + }, + + 'Model.create when passed no docs still fires callback': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create(function (err, a) { + db.close(); + should.strictEqual(err, null); + should.not.exist(a); + }); + }, + + 'Model.create fires callback when an empty array is passed': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create([], function (err, a) { + db.close(); + should.strictEqual(err, null); + should.not.exist(a); + }); + }, + + 'enhanced date casting test (datejs - gh-502)': function () { + var db = start() + + Date.prototype.toObject = function() { + return { + millisecond: 86 + , second: 42 + , minute: 47 + , hour: 17 + , day: 13 + , week: 50 + , month: 11 + , year: 2011 + }; + }; + + var S = new Schema({ + name: String + , description: String + , sabreId: String + , data: { + lastPrice: Number + , comm: String + , curr: String + , rateName: String + } + , created: { type: Date, default: Date.now } + , valid: { type: Boolean, default: true } + }); + + var M = db.model('gh502', S); + + var m = new M; + m.save(function (err) { + should.strictEqual(err, null); + M.findById(m._id, function (err, m) { + should.strictEqual(err, null); + m.save(function (err) { + should.strictEqual(err, null); + M.remove(function (err) { + delete Date.prototype.toObject; + db.close(); + }); + }); + }); + }); + }, + + 'should not persist non-schema props': function () { + var db = start() + , B = db.model('BlogPost', collection) + + var b = new B; + b.whateveriwant = 10; + b.save(function (err) { + should.strictEqual(null, err); + B.collection.findOne({ _id: b._id }, function (err, doc) { + db.close(); + should.strictEqual(null, err); + ;('whateveriwant' in doc).should.be.false; + }); + }); + }, + + 'strict mode': function(){ + var db = start(); + + var lax = new Schema({ + ts : { type: Date, default: Date.now } + , content: String + }); + + var strict = new Schema({ + ts : { type: Date, default: Date.now } + , content: String + }, { strict: true }); + + var Lax = db.model('Lax', lax); + var Strict = db.model('Strict', strict); + + var l = new Lax({content: 'sample', rouge: 'data'}); + l._strictMode.should.be.false; + l = l.toObject(); + l.content.should.equal('sample') + l.rouge.should.equal('data'); + should.exist(l.rouge); + + var s = new Strict({content: 'sample', rouge: 'data'}); + s._strictMode.should.be.true; + s = s.toObject(); + s.should.have.property('ts'); + s.content.should.equal('sample'); + s.should.not.have.property('rouge'); + should.not.exist(s.rouge); + + // instance override + var instance = new Lax({content: 'sample', rouge: 'data'}, true); + instance._strictMode.should.be.true; + instance = instance.toObject(); + instance.content.should.equal('sample') + should.not.exist(instance.rouge); + instance.should.have.property('ts') + + // hydrate works as normal, but supports the schema level flag. + var s2 = new Strict({content: 'sample', rouge: 'data'}, false); + s2._strictMode.should.be.false; + s2 = s2.toObject(); + s2.should.have.property('ts') + s2.content.should.equal('sample'); + s2.should.have.property('rouge'); + should.exist(s2.rouge); + + // testing init + var s3 = new Strict(); + s3.init({content: 'sample', rouge: 'data'}); + var s3obj = s3.toObject(); + s3.content.should.equal('sample'); + s3.should.not.have.property('rouge'); + should.not.exist(s3.rouge); + + // strict on create + Strict.create({content: 'sample2', rouge: 'data'}, function(err, doc){ + doc.content.should.equal('sample2'); + doc.should.not.have.property('rouge'); + should.not.exist(doc.rouge); + db.close(); + }); + } +}; diff --git a/node_modules/mongoose/test/namedscope.test.js b/node_modules/mongoose/test/namedscope.test.js new file mode 100644 index 0000000..0160404 --- /dev/null +++ b/node_modules/mongoose/test/namedscope.test.js @@ -0,0 +1,253 @@ +//Query.prototype.where(criteria, callback) +//Query.prototype.where(path, val, callback) +// +//UserNS.namedScope({ +// twenties: Query.where('age').gte(20).lt(30) +// , male: Query.where('gender', 'male') +// , lastLogin: Query.where('lastLogin').get(+new Date - (24 * 3600 * 1000)) +//}); +// +//UserNS.find(twenties, male, active, function (err, found) { +//}); +// +//// twenties.male OR twenties.active +//UserNS.twenties.male.OR.twenties.active.find(callback); +//UserNS.find(twenties.male, twenties.active, function (err, found) { +//}); +// +//UserNS.find(olderThan(20).male, olderThan(30).active, function (err, found) { +//}); +//UserNS.twenties.male.active.remove(callback); + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Schema = mongoose.Schema + , _24hours = 24 * 3600 * 1000; + +/** + * Setup. + */ + +var UserNSSchema = new Schema({ + age: Number + , gender: String + , lastLogin: Date +}); + +UserNSSchema.namedScope('olderThan', function (age) { + return this.where('age').gt(age); +}); + +UserNSSchema.namedScope('youngerThan', function (age) { + return this.where('age').lt(age); +}); + +UserNSSchema.namedScope('twenties').olderThan(19).youngerThan(30); + +UserNSSchema.namedScope('male').where('gender', 'male'); + +UserNSSchema.namedScope('female').where('gender', 'female'); + +UserNSSchema.namedScope('active', function () { + return this.where('lastLogin').gte(+new Date - _24hours) +}); + +mongoose.model('UserNS', UserNSSchema); + +// TODO Add in tests for using named scopes where findOne, update, remove +module.exports = { + 'basic named scopes should work, for find': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {gender: 'male'} + , {gender: 'male'} + , {gender: 'female'} + , function (err, _) { + should.strictEqual(err, null); + UserNS.male.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(2); + }); + } + ); + }, + 'dynamic named scopes should work, for find': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 21} + , {age: 22} + , {age: 19} + , function (err, _) { + should.strictEqual(err, null); + UserNS.olderThan(20).find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(2); + }); + } + ); + }, + 'named scopes built on top of dynamic named scopes should work, for find': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 21} + , {age: 22} + , {age: 19} + , function (err, _) { + should.strictEqual(err, null); + UserNS.twenties.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(2); + }); + } + ); + }, + 'chaining named scopes should work, for find': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 21, gender: 'male', lastLogin: (+new Date) - _24hours - 3600} + , {age: 45, gender: 'male', lastLogin: +new Date} + , {age: 50, gender: 'female', lastLogin: +new Date} + , function (err, _, match, _) { + should.strictEqual(err, null); + UserNS.olderThan(40).active.male.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(match._id); + }); + } + ); + }, + + 'basic named scopes should work, for remove': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {gender: 'male'} + , {gender: 'male'} + , {gender: 'female'} + , function (err, _) { + UserNS.male.remove( function (err) { + should.strictEqual(!!err, false); + UserNS.male.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(0); + }); + }); + } + ); + }, + + // TODO multi-updates + 'basic named scopes should work, for update': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {gender: 'male'} + , {gender: 'male'} + , {gender: 'female'} + , function (err, male1, male2, female1) { + should.strictEqual(err, null); + UserNS.male.update({gender: 'female'}, function (err) { + should.strictEqual(err, null); + UserNS.female.find( function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + UserNS.male.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(1); + }); + }); + }); + } + ); + }, + + 'chained named scopes should work, for findOne': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 100, gender: 'male'} + , function (err, maleCentenarian) { + should.strictEqual(err, null); + UserNS.male.olderThan(99).findOne( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.id; + found._id.should.eql(maleCentenarian._id); + }); + } + ); + }, + + 'hybrid use of chained named scopes and ad hoc querying should work': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 100, gender: 'female'} + , function (err, femaleCentenarian) { + should.strictEqual(null, err); + UserNS.female.where('age').gt(99).findOne( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.id; + found._id.should.eql(femaleCentenarian._id); + }); + } + ); + }, +// 'using chained named scopes in a find': function () { +// var db = start() +// , UserNS = db.model('UserNS', 'users_' + random()); +// UserNS.create({age: 21, gender: 'male', lastLogin: (+new Date) - _24hours - 3600}, function (err, _) { +// should.strictEqual(err, null); +// UserNS.create({age: 45, gender: 'male', lastLogin: +new Date}, function (err, match) { +// should.strictEqual(err, null); +// UserNS.create({age: 50, gender: 'female', lastLogin: +new Date}, function (err, _) { +// should.strictEqual(err, null); +// UserNS.find(olderThan(40).active.male, function (err, found) { +// db.close(); +// should.strictEqual(err, null); +// found.should.have.length(1); +// found[0]._id.should.eql(match._id); +// }); +// }); +// }); +// }); +// }, +// 'using multiple chained named scopes in a find to do an OR': function () { +// var db = start() +// , UserNS = db.model('UserNS', collection); +// var db = start() +// , UserNS = db.model('UserNS', collection); +// UserNS.create( +// {age: 21, gender: 'male', lastLogin: (+new Date) - _24hours - 3600} +// , {age: 45, gender: 'male', lastLogin: +new Date} +// , {age: 50, gender: 'female', lastLogin: +new Date} +// , {age: 35, gender: 'female', lastLogin: +new Date} +// , function (err, a, b, c, d) { +// should.strictEqual(err, null); +// UserNS.find(twenties.active.male, thirties.active.female, function (err, found) { +// db.close(); +// should.strictEqual(err, null); +// found.should.have.length(2); +// }); +// } +// ); +// }, +}; diff --git a/node_modules/mongoose/test/promise.test.js b/node_modules/mongoose/test/promise.test.js new file mode 100644 index 0000000..6d23a55 --- /dev/null +++ b/node_modules/mongoose/test/promise.test.js @@ -0,0 +1,167 @@ + +/** + * Module dependencies. + */ + +var should = require('should') + , Promise = require('../lib/promise'); + +/** + * Test. + */ + +module.exports = { + + 'test that events fire right away after complete()ing': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.on('complete', function (a, b) { + a.should.eql('1'); + b.should.eql('2'); + called++; + }); + + promise.complete('1', '2'); + + promise.on('complete', function (a, b) { + a.should.eql('1'); + b.should.eql('2'); + called++; + }); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test that events fire right away after error()ing': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.on('err', function (err) { + err.should.be.an.instanceof(Error); + called++; + }); + + promise.error(new Error('booyah')); + + promise.on('err', function (err) { + err.should.be.an.instanceof(Error); + called++; + }); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test errback+callback from constructor': function (beforeExit) { + var promise = new Promise(function (err) { + err.should.be.an.instanceof(Error); + called++; + }) + , called = 0; + + promise.error(new Error('dawg')); + + beforeExit(function () { + called.should.eql(1); + }); + }, + + 'test errback+callback after complete()ing': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.complete('woot'); + + promise.addBack(function (err, data){ + data.should.eql('woot'); + called++; + }); + + promise.addBack(function (err, data){ + should.strictEqual(err, null); + called++; + }); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test errback+callback after error()ing': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.error(new Error('woot')); + + promise.addBack(function (err){ + err.should.be.an.instanceof(Error); + called++; + }); + + promise.addBack(function (err){ + err.should.be.an.instanceof(Error); + called++; + }); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test addCallback shortcut': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.addCallback(function (woot) { + should.strictEqual(woot, undefined); + called++; + }); + + promise.complete(); + + beforeExit(function () { + called.should.eql(1); + }); + }, + + 'test addErrback shortcut': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.addErrback(function (err) { + err.should.be.an.instanceof(Error); + called++; + }); + + promise.error(new Error); + + beforeExit(function () { + called.should.eql(1); + }); + }, + + 'test return value of #on()': function () { + var promise = new Promise() + promise.on('jump', function(){}).should.be.an.instanceof(Promise); + }, + + 'test return value of #addCallback()': function () { + var promise = new Promise() + promise.addCallback(function(){}).should.be.an.instanceof(Promise); + }, + + 'test return value of #addErrback()': function () { + var promise = new Promise() + promise.addErrback(function(){}).should.be.an.instanceof(Promise); + }, + + 'test return value of #addBack()': function () { + var promise = new Promise() + promise.addBack(function(){}).should.be.an.instanceof(Promise); + } + +}; diff --git a/node_modules/mongoose/test/query.test.js b/node_modules/mongoose/test/query.test.js new file mode 100644 index 0000000..b0725ef --- /dev/null +++ b/node_modules/mongoose/test/query.test.js @@ -0,0 +1,863 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , Query = require('../lib/query') + , mongoose = start.mongoose + , DocumentObjectId = mongoose.Types.ObjectId + , Schema = mongoose.Schema + , should = require('should') + +var Comment = new Schema({ + text: String +}); + +var Product = new Schema({ + tags: {} // mixed + , array: Array + , ids: [Schema.ObjectId] + , strings: [String] + , numbers: [Number] + , comments: [Comment] +}); + +mongoose.model('Product', Product); +mongoose.model('Comment', Comment); + +/** + * Test. + */ + +module.exports = { + 'test query.fields({a: 1, b: 1, c: 1})': function () { + var query = new Query(); + query.fields({a: 1, b: 1, c: 1}); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + 'test query.fields({only: "a b c"})': function () { + var query = new Query(); + query.fields({only: "a b c"}); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + 'test query.fields({only: ["a", "b", "c"]})': function () { + var query = new Query(); + query.fields({only: ['a', 'b', 'c']}); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + 'test query.fields("a b c")': function () { + var query = new Query(); + query.fields("a b c"); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + 'test query.fields("a", "b", "c")': function () { + var query = new Query(); + query.fields('a', 'b', 'c'); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "test query.fields(['a', 'b', 'c'])": function () { + var query = new Query(); + query.fields(['a', 'b', 'c']); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "Query#fields should not over-ride fields set in prior calls to Query#fields": function () { + var query = new Query(); + query.fields('a'); + query._fields.should.eql({a: 1}); + query.fields('b'); + query._fields.should.eql({a: 1, b: 1}); + }, +// "Query#fields should be able to over-ride fields set in prior calls to Query#fields if you specify override": function () { +// var query = new Query(); +// query.fields('a'); +// query._fields.should.eql({a: 1}); +// query.override.fields('b'); +// query._fields.should.eql({b: 1}); +// } + + "test query.only('a b c')": function () { + var query = new Query(); + query.only("a b c"); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "test query.only('a', 'b', 'c')": function () { + var query = new Query(); + query.only('a', 'b', 'c'); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "test query.only('a', 'b', 'c')": function () { + var query = new Query(); + query.only(['a', 'b', 'c']); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "Query#only should not over-ride fields set in prior calls to Query#only": function () { + var query = new Query(); + query.only('a'); + query._fields.should.eql({a: 1}); + query.only('b'); + query._fields.should.eql({a: 1, b: 1}); + }, + + "test query.exclude('a b c')": function () { + var query = new Query(); + query.exclude("a b c"); + query._fields.should.eql({a: 0, b: 0, c: 0}); + }, + "test query.exclude('a', 'b', 'c')": function () { + var query = new Query(); + query.exclude('a', 'b', 'c'); + query._fields.should.eql({a: 0, b: 0, c: 0}); + }, + "test query.exclude('a', 'b', 'c')": function () { + var query = new Query(); + query.exclude(['a', 'b', 'c']); + query._fields.should.eql({a: 0, b: 0, c: 0}); + }, + "Query#exclude should not over-ride fields set in prior calls to Query#exclude": function () { + var query = new Query(); + query.exclude('a'); + query._fields.should.eql({a: 0}); + query.exclude('b'); + query._fields.should.eql({a: 0, b: 0}); + }, + + 'test setting a condition via where': function () { + var query = new Query(); + query.where('name', 'guillermo'); + query._conditions.should.eql({name: 'guillermo'}); + }, + + 'test Query#gte where 2 arguments': function () { + var query = new Query(); + query.gte('age', 18); + query._conditions.should.eql({age: {$gte: 18}}); + }, + + 'test Query#gt where 2 arguments': function () { + var query = new Query(); + query.gt('age', 17); + query._conditions.should.eql({age: {$gt: 17}}); + }, + + 'test Query#lte where 2 arguments': function () { + var query = new Query(); + query.lte('age', 65); + query._conditions.should.eql({age: {$lte: 65}}); + }, + + 'test Query#lt where 2 arguments': function () { + var query = new Query(); + query.lt('age', 66); + query._conditions.should.eql({age: {$lt: 66}}); + }, + + 'test Query#gte where 1 argument': function () { + var query = new Query(); + query.where("age").gte(18); + query._conditions.should.eql({age: {$gte: 18}}); + }, + + 'test Query#gt where 1 argument': function () { + var query = new Query(); + query.where("age").gt(17); + query._conditions.should.eql({age: {$gt: 17}}); + }, + + 'test Query#lte where 1 argument': function () { + var query = new Query(); + query.where("age").lte(65); + query._conditions.should.eql({age: {$lte: 65}}); + }, + + 'test Query#lt where 1 argument': function () { + var query = new Query(); + query.where("age").lt(66); + query._conditions.should.eql({age: {$lt: 66}}); + }, + + 'test combined Query#lt and Query#gt': function () { + var query = new Query(); + query.where("age").lt(66).gt(17); + query._conditions.should.eql({age: {$lt: 66, $gt: 17}}); + }, + + 'test Query#lt on one path and Query#gt on another path on the same query': function () { + var query = new Query(); + query + .where("age").lt(66) + .where("height").gt(5); + query._conditions.should.eql({age: {$lt: 66}, height: {$gt: 5}}); + }, + + 'test Query#ne where 2 arguments': function () { + var query = new Query(); + query.ne('age', 21); + query._conditions.should.eql({age: {$ne: 21}}); + }, + + 'test Query#gte where 1 argument': function () { + var query = new Query(); + query.where("age").ne(21); + query._conditions.should.eql({age: {$ne: 21}}); + }, + + 'test Query#ne alias Query#notEqualTo': function () { + var query = new Query(); + query.where('age').notEqualTo(21); + query._conditions.should.eql({age: {$ne: 21}}); + + query = new Query(); + query.notEqualTo('age', 21); + query._conditions.should.eql({age: {$ne: 21}}); + }, + + 'test Query#in where 2 arguments': function () { + var query = new Query(); + query.in('age', [21, 25, 30]); + query._conditions.should.eql({age: {$in: [21, 25, 30]}}); + }, + + 'test Query#in where 1 argument': function () { + var query = new Query(); + query.where("age").in([21, 25, 30]); + query._conditions.should.eql({age: {$in: [21, 25, 30]}}); + }, + + 'test Query#in where a non-array value not via where': function () { + var query = new Query(); + query.in('age', 21); + query._conditions.should.eql({age: {$in: 21}}); + }, + + 'test Query#in where a non-array value via where': function () { + var query = new Query(); + query.where('age').in(21); + query._conditions.should.eql({age: {$in: 21}}); + }, + + 'test Query#nin where 2 arguments': function () { + var query = new Query(); + query.nin('age', [21, 25, 30]); + query._conditions.should.eql({age: {$nin: [21, 25, 30]}}); + }, + + 'test Query#nin where 1 argument': function () { + var query = new Query(); + query.where("age").nin([21, 25, 30]); + query._conditions.should.eql({age: {$nin: [21, 25, 30]}}); + }, + + 'test Query#nin where a non-array value not via where': function () { + var query = new Query(); + query.nin('age', 21); + query._conditions.should.eql({age: {$nin: 21}}); + }, + + 'test Query#nin where a non-array value via where': function () { + var query = new Query(); + query.where('age').nin(21); + query._conditions.should.eql({age: {$nin: 21}}); + }, + + 'test Query#mod not via where, where [a, b] param': function () { + var query = new Query(); + query.mod('age', [5, 2]); + query._conditions.should.eql({age: {$mod: [5, 2]}}); + }, + + 'test Query#mod not via where, where a and b params': function () { + var query = new Query(); + query.mod('age', 5, 2); + query._conditions.should.eql({age: {$mod: [5, 2]}}); + }, + + 'test Query#mod via where, where [a, b] param': function () { + var query = new Query(); + query.where("age").mod([5, 2]); + query._conditions.should.eql({age: {$mod: [5, 2]}}); + }, + + 'test Query#mod via where, where a and b params': function () { + var query = new Query(); + query.where("age").mod(5, 2); + query._conditions.should.eql({age: {$mod: [5, 2]}}); + }, + + 'test Query#near via where, where [lat, long] param': function () { + var query = new Query(); + query.where('checkin').near([40, -72]); + query._conditions.should.eql({checkin: {$near: [40, -72]}}); + }, + + 'test Query#near via where, where lat and long params': function () { + var query = new Query(); + query.where('checkin').near(40, -72); + query._conditions.should.eql({checkin: {$near: [40, -72]}}); + }, + + 'test Query#near not via where, where [lat, long] param': function () { + var query = new Query(); + query.near('checkin', [40, -72]); + query._conditions.should.eql({checkin: {$near: [40, -72]}}); + }, + + 'test Query#near not via where, where lat and long params': function () { + var query = new Query(); + query.near('checkin', 40, -72); + query._conditions.should.eql({checkin: {$near: [40, -72]}}); + }, + + 'test Query#maxDistance via where': function () { + var query = new Query(); + query.where('checkin').near([40, -72]).maxDistance(1); + query._conditions.should.eql({checkin: {$near: [40, -72], $maxDistance: 1}}); + query = new Query(); + query.where('checkin').near([40, -72]).$maxDistance(1); + query._conditions.should.eql({checkin: {$near: [40, -72], $maxDistance: 1}}); + }, + + 'test Query#wherein.box not via where': function () { + var query = new Query(); + query.wherein.box('gps', {ll: [5, 25], ur: [10, 30]}); + query._conditions.should.eql({gps: {$within: {$box: [[5, 25], [10, 30]]}}}); + }, + + 'test Query#wherein.box via where': function () { + var query = new Query(); + query.where('gps').wherein.box({ll: [5, 25], ur: [10, 30]}); + query._conditions.should.eql({gps: {$within: {$box: [[5, 25], [10, 30]]}}}); + }, + + 'test Query#wherein.center not via where': function () { + var query = new Query(); + query.wherein.center('gps', {center: [5, 25], radius: 5}); + query._conditions.should.eql({gps: {$within: {$center: [[5, 25], 5]}}}); + }, + + 'test Query#wherein.center not via where': function () { + var query = new Query(); + query.where('gps').wherein.center({center: [5, 25], radius: 5}); + query._conditions.should.eql({gps: {$within: {$center: [[5, 25], 5]}}}); + }, + + 'test Query#exists where 0 arguments via where': function () { + var query = new Query(); + query.where("username").exists(); + query._conditions.should.eql({username: {$exists: true}}); + }, + + 'test Query#exists where 1 argument via where': function () { + var query = new Query(); + query.where("username").exists(false); + query._conditions.should.eql({username: {$exists: false}}); + }, + + 'test Query#exists where 1 argument not via where': function () { + var query = new Query(); + query.exists('username'); + query._conditions.should.eql({username: {$exists: true}}); + }, + + 'test Query#exists where 1 argument not via where': function () { + var query = new Query(); + query.exists("username", false); + query._conditions.should.eql({username: {$exists: false}}); + }, + + // TODO $not + + 'test Query#all via where': function () { + var query = new Query(); + query.where('pets').all(['dog', 'cat', 'ferret']); + query._conditions.should.eql({pets: {$all: ['dog', 'cat', 'ferret']}}); + }, + + 'test Query#all not via where': function () { + var query = new Query(); + query.all('pets', ['dog', 'cat', 'ferret']); + query._conditions.should.eql({pets: {$all: ['dog', 'cat', 'ferret']}}); + }, + + 'test strict array equivalence condition via Query#find': function () { + var query = new Query(); + query.find({'pets': ['dog', 'cat', 'ferret']}); + query._conditions.should.eql({pets: ['dog', 'cat', 'ferret']}); + }, + + '#find with no args should not throw': function () { + var threw = false; + var q = new Query(); + + try { + q.find(); + } catch (err) { + threw = true; + } + + threw.should.be.false; + }, + + // TODO Check key.index queries + + 'test Query#size via where': function () { + var query = new Query(); + query.where('collection').size(5); + query._conditions.should.eql({collection: {$size: 5}}); + }, + + 'test Query#size not via where': function () { + var query = new Query(); + query.size('collection', 5); + query._conditions.should.eql({collection: {$size: 5}}); + }, + + 'test Query#slice via where, where just positive limit param': function () { + var query = new Query(); + query.where('collection').slice(5); + query._fields.should.eql({collection: {$slice: 5}}); + }, + + 'test Query#slice via where, where just negative limit param': function () { + var query = new Query(); + query.where('collection').slice(-5); + query._fields.should.eql({collection: {$slice: -5}}); + }, + + 'test Query#slice via where, where [skip, limit] param': function () { + var query = new Query(); + query.where('collection').slice([14, 10]); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#slice via where, where skip and limit params': function () { + var query = new Query(); + query.where('collection').slice(14, 10); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#slice via where, where just positive limit param': function () { + var query = new Query(); + query.where('collection').slice(5); + query._fields.should.eql({collection: {$slice: 5}}); + }, + + 'test Query#slice via where, where just negative limit param': function () { + var query = new Query(); + query.where('collection').slice(-5); + query._fields.should.eql({collection: {$slice: -5}}); + }, + + 'test Query#slice via where, where the [skip, limit] param': function () { + var query = new Query(); + query.where('collection').slice([14, 10]); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#slice via where, where the skip and limit params': function () { + var query = new Query(); + query.where('collection').slice(14, 10); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + + 'test Query#slice not via where, where just positive limit param': function () { + var query = new Query(); + query.slice('collection', 5); + query._fields.should.eql({collection: {$slice: 5}}); + }, + + 'test Query#slice not via where, where just negative limit param': function () { + var query = new Query(); + query.slice('collection', -5); + query._fields.should.eql({collection: {$slice: -5}}); + }, + + 'test Query#slice not via where, where [skip, limit] param': function () { + var query = new Query(); + query.slice('collection', [14, 10]); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#slice not via where, where skip and limit params': function () { + var query = new Query(); + query.slice('collection', 14, 10); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#elemMatch not via where': function () { + var query = new Query(); + query.elemMatch('comments', {author: 'bnoguchi', votes: {$gte: 5}}); + query._conditions.should.eql({comments: {$elemMatch: {author: 'bnoguchi', votes: {$gte: 5}}}}); + }, + + 'test Query#elemMatch not via where, where block notation': function () { + var query = new Query(); + query.elemMatch('comments', function (elem) { + elem.where('author', 'bnoguchi') + elem.where('votes').gte(5); + }); + query._conditions.should.eql({comments: {$elemMatch: {author: 'bnoguchi', votes: {$gte: 5}}}}); + }, + + 'test Query#elemMatch via where': function () { + var query = new Query(); + query.where('comments').elemMatch({author: 'bnoguchi', votes: {$gte: 5}}); + query._conditions.should.eql({comments: {$elemMatch: {author: 'bnoguchi', votes: {$gte: 5}}}}); + }, + + 'test Query#elemMatch via where, where block notation': function () { + var query = new Query(); + query.where('comments').elemMatch(function (elem) { + elem.where('author', 'bnoguchi') + elem.where('votes').gte(5); + }); + query._conditions.should.eql({comments: {$elemMatch: {author: 'bnoguchi', votes: {$gte: 5}}}}); + }, + + + 'test Query#$where where a function arg': function () { + var query = new Query(); + function filter () { + return this.lastName === this.firstName; + } + query.$where(filter); + query._conditions.should.eql({$where: filter}); + }, + + 'test Query#where where a javascript string arg': function () { + var query = new Query(); + query.$where('this.lastName === this.firstName'); + query._conditions.should.eql({$where: 'this.lastName === this.firstName'}); + }, + + 'test Query#limit': function () { + var query = new Query(); + query.limit(5); + query.options.limit.should.equal(5); + }, + + 'test Query#skip': function () { + var query = new Query(); + query.skip(9); + query.options.skip.should.equal(9); + }, + + 'test Query#sort': function () { + var query = new Query(); + query.sort('a', 1, 'c', -1, 'b', 1); + query.options.sort.should.eql([['a', 1], ['c', -1], ['b', 1]]); + }, + + 'test Query#asc and Query#desc': function () { + var query = new Query(); + query.asc('a', 'z').desc('c', 'v').asc('b'); + query.options.sort.should.eql([['a', 1], ['z', 1], ['c', -1], ['v', -1], ['b', 1]]); + }, + + 'Query#or': function () { + var query = new Query; + query.find({ $or: [{x:1},{x:2}] }); + query._conditions.$or.length.should.equal(2); + query.$or([{y:"We're under attack"}, {z:47}]); + query._conditions.$or.length.should.equal(4); + query._conditions.$or[3].z.should.equal(47); + query.or({z:"phew"}); + query._conditions.$or.length.should.equal(5); + query._conditions.$or[3].z.should.equal(47); + query._conditions.$or[4].z.should.equal("phew"); + }, + + 'test running an empty Query should not throw': function () { + var query = new Query(); + var threw = false; + + try { + query.exec(); + } catch (err) { + threw = true; + } + + threw.should.eql(false); + }, + + 'test casting an array set to mixed type works': function () { + var query = new Query(); + var db = start(); + var Product = db.model('Product'); + var params = { _id: new DocumentObjectId, tags: { $in: [ 4, 8, 15, 16 ] }}; + + query.cast(Product, params); + + params.tags.$in.should.eql([4,8,15,16]); + db.close(); + }, + + //'throwing inside a query callback should not execute the callback again': function () { + //var query = new Query(); + //var db = start(); + //var Product = db.model('Product'); + + //var threw = false; + //Product.find({}, function (err) { + //if (!threw) { + //db.close(); + //threw = true; + //throw new Error("Double callback"); + //} + + //should.strictEqual(err, null, 'Double callback detected'); + //}); + //}, + + 'Query#find $ne should not cast single value to array for schematype of Array': function () { + var query = new Query(); + var db = start(); + var Product = db.model('Product'); + var Comment = db.model('Comment'); + db.close(); + + var id = new DocumentObjectId; + var castedComment = { _id: id, text: 'hello there' }; + var comment = new Comment(castedComment); + + var params = { + array: { $ne: 5 } + , ids: { $ne: id } + , comments: { $ne: comment } + , strings: { $ne: 'Hi there' } + , numbers: { $ne: 10000 } + }; + + query.cast(Product, params); + params.array.$ne.should.equal(5); + params.ids.$ne.should.eql(id); + params.comments.$ne._id.toHexString(); + params.comments.$ne.should.eql(castedComment); + params.strings.$ne.should.eql('Hi there'); + params.numbers.$ne.should.eql(10000); + + params.array.$ne = [5]; + params.ids.$ne = [id]; + params.comments.$ne = [comment]; + params.strings.$ne = ['Hi there']; + params.numbers.$ne = [10000]; + query.cast(Product, params); + params.array.$ne.should.be.instanceof(Array); + params.array.$ne[0].should.eql(5); + params.ids.$ne.should.be.instanceof(Array); + params.ids.$ne[0].toString().should.eql(id.toString()); + params.comments.$ne.should.be.instanceof(Array); + params.comments.$ne[0].should.eql(castedComment); + params.strings.$ne.should.be.instanceof(Array); + params.strings.$ne[0].should.eql('Hi there'); + params.numbers.$ne.should.be.instanceof(Array); + params.numbers.$ne[0].should.eql(10000); + }, + + 'Querying a subdocument array with $ne: null should not throw': function () { + var query = new Query(); + var db = start(); + var Product = db.model('Product'); + var Comment = db.model('Comment'); + db.close(); + + var params = { + comments: { $ne: null } + }; + + query.cast(Product, params); + should.strictEqual(params.comments.$ne, null); + }, + + 'Query#find should not cast single value to array for schematype of Array': function () { + var query = new Query(); + var db = start(); + var Product = db.model('Product'); + var Comment = db.model('Comment'); + db.close(); + + var id = new DocumentObjectId; + var castedComment = { _id: id, text: 'hello there' }; + var comment = new Comment(castedComment); + + var params = { + array: 5 + , ids: id + , comments: comment + , strings: 'Hi there' + , numbers: 10000 + }; + + query.cast(Product, params); + params.array.should.equal(5); + params.ids.should.eql(id); + params.comments._id.toHexString(); + params.comments.should.eql(castedComment); + params.strings.should.eql('Hi there'); + params.numbers.should.eql(10000); + + params.array = [5]; + params.ids = [id]; + params.comments = [comment]; + params.strings = ['Hi there']; + params.numbers = [10000]; + query.cast(Product, params); + params.array.should.be.instanceof(Array); + params.array[0].should.eql(5); + params.ids.should.be.instanceof(Array); + params.ids[0].toString().should.eql(id.toString()); + params.comments.should.be.instanceof(Array); + params.comments[0].should.eql(castedComment); + params.strings.should.be.instanceof(Array); + params.strings[0].should.eql('Hi there'); + params.numbers.should.be.instanceof(Array); + params.numbers[0].should.eql(10000); + }, + + 'distinct Query op should be "distinct"': function () { + var db = start(); + var query = new Query(); + var Product = db.model('Product'); + new Query().bind(Product, 'distinct').distinct('blah', function(){ + db.close(); + }).op.should.equal('distinct'); + }, + + 'Query without a callback works': function () { + var db = start(); + var query = new Query(); + var Product = db.model('Product'); + new Query().bind(Product, 'count').count(); + setTimeout(db.close.bind(db), 1000); + }, + + '#findOne should set the op when callback is passed': function () { + var db = start(); + var query = new Query(); + var Product = db.model('Product'); + var q = new Query().bind(Product, 'distinct'); + q.op.should.equal('distinct'); + q.findOne(); + q.op.should.equal('findOne'); + db.close(); + }, + + 'querying/updating with model instance containing embedded docs should work (#454)': function () { + var db = start(); + var Product = db.model('Product'); + + var proddoc = { comments: [{ text: 'hello' }] }; + var prod2doc = { comments: [{ text: 'goodbye' }] }; + + var prod = new Product(proddoc); + var prod2 = new Product(prod2doc); + + prod.save(function (err) { + should.strictEqual(err, null); + + Product.findOne(prod, function (err, product) { + should.strictEqual(err, null); + product.comments.length.should.equal(1); + product.comments[0].text.should.equal('hello'); + + Product.update(product, prod2doc, function (err) { + should.strictEqual(err, null); + + Product.collection.findOne({ _id: product._id }, function (err, doc) { + db.close(); + should.strictEqual(err, null); + doc.comments.length.should.equal(1); + // ensure hidden private props were not saved to db + doc.comments[0].should.not.have.ownProperty('parentArr'); + doc.comments[0].text.should.equal('goodbye'); + }); + }); + }); + }); + }, + + 'optionsForExecute should retain key order': function () { + // this is important for query hints + var hint = { x: 1, y: 1, z: 1 }; + var a = JSON.stringify({ hint: hint, safe: true}); + + var q = new Query; + q.hint(hint); + + var options = q._optionsForExec({ options: { safe: true } }); + + a.should.equal(JSON.stringify(options)); + }, + + // Advanced Query options + + 'test Query#maxscan': function () { + var query = new Query(); + query.maxscan(100); + query.options.maxscan.should.equal(100); + }, + + 'test Query#slaveOk': function () { + var query = new Query(); + query.slaveOk(); + query.options.slaveOk.should.be.true; + + var query = new Query(); + query.slaveOk(true); + query.options.slaveOk.should.be.true; + + var query = new Query(); + query.slaveOk(false); + query.options.slaveOk.should.be.false; + }, + + 'test Query#hint': function () { + var query = new Query(); + query.hint('indexAttributeA', 1, 'indexAttributeB', -1); + query.options.hint.should.eql({'indexAttributeA': 1, 'indexAttributeB': -1}); + + var query2 = new Query(); + query2.hint({'indexAttributeA': 1, 'indexAttributeB': -1}); + query2.options.hint.should.eql({'indexAttributeA': 1, 'indexAttributeB': -1}); + + var query3 = new Query(); + query3.hint('indexAttributeA'); + query3.options.hint.should.eql({}); + }, + + 'test Query#snapshot': function () { + var query = new Query(); + query.snapshot(true); + query.options.snapshot.should.be.true; + }, + + 'test Query#batchSize': function () { + var query = new Query(); + query.batchSize(10); + query.options.batchSize.should.equal(10); + }, + + 'empty updates are not run': function () { + var q = new Query; + ;(!!q._castUpdate({})).should.be.false; + } + + // TODO +// 'test Query#min': function () { +// var query = new Query(); +// query.min(10); +// query.options.min.should.equal(10); +// }, +// + //TODO +// 'test Query#max': function () { +// var query = new Query(); +// query.max(100); +// query.options.max.should.equal(100); +// }, + + // TODO +// 'test Query#explain': function () { +// } +}; diff --git a/node_modules/mongoose/test/schema.onthefly.test.js b/node_modules/mongoose/test/schema.onthefly.test.js new file mode 100644 index 0000000..d038d78 --- /dev/null +++ b/node_modules/mongoose/test/schema.onthefly.test.js @@ -0,0 +1,105 @@ +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Schema = mongoose.Schema + , ObjectId = Schema.ObjectId; + +/** + * Setup. + */ + +var DecoratedSchema = new Schema({ + title : String +}); + +mongoose.model('Decorated', DecoratedSchema); + +var collection = 'decorated_' + random(); + +module.exports = { + 'setting on the fly schemas should cache the type schema and cast values appropriately': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var post = new Decorated(); + post.set('adhoc', '9', Number); + post.get('adhoc').valueOf().should.eql(9); + db.close(); + }, + + 'on the fly schemas should be local to the particular document': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var postOne = new Decorated(); + postOne.set('adhoc', '9', Number); + postOne._path('adhoc').should.not.equal(undefined); + + var postTwo = new Decorated(); + postTwo._path('title').should.not.equal(undefined); + should.strictEqual(undefined, postTwo._path('adhoc')); + db.close(); + }, + + 'querying a document that had an on the fly schema should work': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var post = new Decorated({title: 'AD HOC'}); + // Interpret adhoc as a Number + post.set('adhoc', '9', Number); + post.get('adhoc').valueOf().should.eql(9); + post.save( function (err) { + should.strictEqual(null, err); + Decorated.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(null, err); + found.get('adhoc').should.eql(9); + // Interpret adhoc as a String instead of a Number now + found.get('adhoc', String).should.eql('9'); + found.get('adhoc').should.eql('9'); + }); + }); + }, + + 'on the fly Embedded Array schemas should cast properly': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var post = new Decorated(); + post.set('moderators', [{name: 'alex trebek'}], [new Schema({name: String})]); + post.get('moderators')[0].name.should.eql('alex trebek'); + db.close(); + }, + + 'on the fly Embedded Array schemas should get from a fresh queried document properly': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var post = new Decorated() + , ModeratorSchema = new Schema({name: String, ranking: Number}); + post.set('moderators', [{name: 'alex trebek', ranking: '1'}], [ModeratorSchema]); + post.get('moderators')[0].name.should.eql('alex trebek'); + post.save( function (err) { + should.strictEqual(null, err); + Decorated.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(null, err); + var rankingPreCast = found.get('moderators')[0].ranking; + rankingPreCast.should.eql(1); + should.strictEqual(undefined, rankingPreCast.increment); + var rankingPostCast = found.get('moderators', [ModeratorSchema])[0].ranking; + rankingPostCast.valueOf().should.equal(1); + rankingPostCast.increment.should.not.equal(undefined); + + var NewModeratorSchema = new Schema({ name: String, ranking: String}); + rankingPostCast = found.get('moderators', [NewModeratorSchema])[0].ranking; + rankingPostCast.should.equal('1'); + }); + }); + }, + 'should support on the fly nested documents': function () { + // TODO + } +}; diff --git a/node_modules/mongoose/test/schema.test.js b/node_modules/mongoose/test/schema.test.js new file mode 100644 index 0000000..4f99bde --- /dev/null +++ b/node_modules/mongoose/test/schema.test.js @@ -0,0 +1,883 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('./common').mongoose + , should = require('should') + , Schema = mongoose.Schema + , Document = mongoose.Document + , SchemaType = mongoose.SchemaType + , VirtualType = mongoose.VirtualType + , ObjectId = Schema.ObjectId + , ValidatorError = SchemaType.ValidatorError + , CastError = SchemaType.CastError + , SchemaTypes = Schema.Types + , DocumentObjectId = mongoose.Types.ObjectId + , Mixed = SchemaTypes.Mixed + , MongooseNumber = mongoose.Types.Number + , MongooseArray = mongoose.Types.Array + , vm = require('vm') + +/** + * Test Document constructor. + */ + +function TestDocument () { + Document.apply(this, arguments); +}; + +/** + * Inherits from Document. + */ + +TestDocument.prototype.__proto__ = Document.prototype; + +/** + * Set a dummy schema to simulate compilation. + */ + +TestDocument.prototype.schema = new Schema({ + test : String +}); + +/** + * Test. + */ + +module.exports = { + + 'test different schema types support': function(){ + var Checkin = new Schema({ + date : Date + , location : { + lat: Number + , lng: Number + } + }); + + var Ferret = new Schema({ + name : String + , owner : ObjectId + , fur : String + , color : { type: String } + , age : Number + , checkins : [Checkin] + , friends : [ObjectId] + , likes : Array + , alive : Boolean + , extra : Mixed + }); + + Ferret.path('name').should.be.an.instanceof(SchemaTypes.String); + Ferret.path('owner').should.be.an.instanceof(SchemaTypes.ObjectId); + Ferret.path('fur').should.be.an.instanceof(SchemaTypes.String); + Ferret.path('color').should.be.an.instanceof(SchemaTypes.String); + Ferret.path('age').should.be.an.instanceof(SchemaTypes.Number); + Ferret.path('checkins').should.be.an.instanceof(SchemaTypes.DocumentArray); + Ferret.path('friends').should.be.an.instanceof(SchemaTypes.Array); + Ferret.path('likes').should.be.an.instanceof(SchemaTypes.Array); + Ferret.path('alive').should.be.an.instanceof(SchemaTypes.Boolean); + Ferret.path('extra').should.be.an.instanceof(SchemaTypes.Mixed); + + should.strictEqual(Ferret.path('unexistent'), undefined); + + Checkin.path('date').should.be.an.instanceof(SchemaTypes.Date); + }, + + 'dot notation support for accessing paths': function(){ + var Racoon = new Schema({ + name : { type: String, enum: ['Edwald', 'Tobi'] } + , age : Number + }); + + var Person = new Schema({ + name : String + , raccoons : [Racoon] + , location : { + city : String + , state : String + } + }); + + Person.path('name').should.be.an.instanceof(SchemaTypes.String); + Person.path('raccoons').should.be.an.instanceof(SchemaTypes.DocumentArray); + Person.path('location.city').should.be.an.instanceof(SchemaTypes.String); + Person.path('location.state').should.be.an.instanceof(SchemaTypes.String); + + should.strictEqual(Person.path('location.unexistent'), undefined); + }, + + 'nested paths more than 2 levels deep': function () { + var Nested = new Schema({ + first: { + second: { + third: String + } + } + }); + Nested.path('first.second.third').should.be.an.instanceof(SchemaTypes.String); + }, + + 'test default definition': function(){ + var Test = new Schema({ + simple : { type: String, default: 'a' } + , array : { type: Array, default: [1,2,3,4,5] } + , arrayX : { type: Array, default: 9 } + , arrayFn : { type: Array, default: function () { return [8] } } + , callback : { type: Number, default: function(){ + this.a.should.eql('b'); + return '3'; + }} + }); + + Test.path('simple').defaultValue.should.eql('a'); + Test.path('callback').defaultValue.should.be.a('function'); + + Test.path('simple').getDefault().should.eql('a'); + (+Test.path('callback').getDefault({ a: 'b' })).should.eql(3); + Test.path('array').defaultValue.should.be.a('function'); + Test.path('array').getDefault(new TestDocument)[3].should.eql(4); + Test.path('arrayX').getDefault(new TestDocument)[0].should.eql(9); + Test.path('arrayFn').defaultValue.should.be.a('function'); + Test.path('arrayFn').getDefault(new TestDocument).should.be.an.instanceof(MongooseArray); + }, + + 'test Mixed defaults can be empty arrays': function () { + var Test = new Schema({ + mixed1 : { type: Mixed, default: [] } + , mixed2 : { type: Mixed, default: Array } + }); + + Test.path('mixed1').getDefault().should.be.an.instanceof(Array); + Test.path('mixed1').getDefault().length.should.be.eql(0); + Test.path('mixed2').getDefault().should.be.an.instanceof(Array); + Test.path('mixed2').getDefault().length.should.be.eql(0); + }, + + 'test string required validation': function(){ + var Test = new Schema({ + simple: String + }); + + Test.path('simple').required(true); + Test.path('simple').validators.should.have.length(1); + + Test.path('simple').doValidate(null, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('simple').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('simple').doValidate('', function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('simple').doValidate('woot', function(err){ + should.strictEqual(err, null); + }); + }, + + 'test string enum validation': function(){ + var Test = new Schema({ + complex: { type: String, enum: ['a', 'b', undefined, 'c', null] } + }); + + Test.path('complex').should.be.an.instanceof(SchemaTypes.String); + Test.path('complex').enumValues.should.eql(['a', 'b', 'c', null]); + Test.path('complex').validators.should.have.length(1); + + Test.path('complex').enum('d', 'e'); + + Test.path('complex').enumValues.should.eql(['a', 'b', 'c', null, 'd', 'e']); + + Test.path('complex').doValidate('x', function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('complex').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('complex').doValidate(null, function(err){ + should.strictEqual(null, err); + }); + + Test.path('complex').doValidate('da', function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + }, + + 'test string regular expression validation': function(){ + var Test = new Schema({ + simple: { type: String, match: /[a-z]/ } + }); + + Test.path('simple').validators.should.have.length(1); + + Test.path('simple').doValidate('az', function(err){ + should.strictEqual(err, null); + }); + + Test.path('simple').match(/[0-9]/); + Test.path('simple').validators.should.have.length(2); + + Test.path('simple').doValidate('12', function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('simple').doValidate('a12', function(err){ + should.strictEqual(err, null); + }); + }, + + 'test string casting': function(){ + var Tobi = new Schema({ + nickname: String + }); + + function Test(){}; + Test.prototype.toString = function(){ + return 'woot'; + }; + + // test Number -> String cast + Tobi.path('nickname').cast(0).should.be.a('string'); + Tobi.path('nickname').cast(0).should.eql('0'); + + // test any object that implements toString + Tobi.path('nickname').cast(new Test()).should.be.a('string'); + Tobi.path('nickname').cast(new Test()).should.eql('woot'); + }, + + 'test number minimums and maximums validation': function(){ + var Tobi = new Schema({ + friends: { type: Number, max: 15, min: 5 } + }); + + Tobi.path('friends').validators.should.have.length(2); + + Tobi.path('friends').doValidate(10, function(err){ + should.strictEqual(err, null); + }); + + Tobi.path('friends').doValidate(100, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Tobi.path('friends').doValidate(1, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + }, + + 'test number required validation': function(){ + var Edwald = new Schema({ + friends: { type: Number, required: true } + }); + + Edwald.path('friends').doValidate(null, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Edwald.path('friends').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Edwald.path('friends').doValidate(0, function(err){ + should.strictEqual(err, null); + }); + }, + + 'test number casting': function(){ + var Tobi = new Schema({ + age: Number + }); + + // test String -> Number cast + Tobi.path('age').cast('0').should.be.an.instanceof(MongooseNumber); + (+Tobi.path('age').cast('0')).should.eql(0); + + Tobi.path('age').cast(0).should.be.an.instanceof(MongooseNumber); + (+Tobi.path('age').cast(0)).should.eql(0); + }, + + 'test date required validation': function(){ + var Loki = new Schema({ + birth_date: { type: Date, required: true } + }); + + Loki.path('birth_date').doValidate(null, function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('birth_date').doValidate(undefined, function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('birth_date').doValidate(new Date(), function (err) { + should.strictEqual(err, null); + }); + }, + + 'test date casting': function(){ + var Loki = new Schema({ + birth_date: { type: Date } + }); + + Loki.path('birth_date').cast(1294525628301).should.be.an.instanceof(Date); + Loki.path('birth_date').cast('8/24/2000').should.be.an.instanceof(Date); + Loki.path('birth_date').cast(new Date).should.be.an.instanceof(Date); + }, + + 'test object id required validator': function(){ + var Loki = new Schema({ + owner: { type: ObjectId, required: true } + }); + + Loki.path('owner').doValidate(new DocumentObjectId(), function(err){ + should.strictEqual(err, null); + }); + + Loki.path('owner').doValidate(null, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('owner').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + }, + + 'test object id casting': function(){ + var Loki = new Schema({ + owner: { type: ObjectId } + }); + + var doc = new TestDocument() + , id = doc._id.toString(); + + Loki.path('owner').cast('4c54f3453e688c000000001a') + .should.be.an.instanceof(DocumentObjectId); + + Loki.path('owner').cast(new DocumentObjectId()) + .should.be.an.instanceof(DocumentObjectId); + + Loki.path('owner').cast(doc) + .should.be.an.instanceof(DocumentObjectId); + + Loki.path('owner').cast(doc).toString().should.eql(id); + }, + + 'test array required validation': function(){ + var Loki = new Schema({ + likes: { type: Array, required: true } + }); + + Loki.path('likes').doValidate(null, function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('likes').doValidate(undefined, function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('likes').doValidate([], function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + }, + + 'test array casting': function(){ + var Loki = new Schema({ + oids : [ObjectId] + , dates : [Date] + , numbers : [Number] + , strings : [String] + , buffers : [Buffer] + , nocast : [] + , mixed : [Mixed] + }); + + var oids = Loki.path('oids').cast(['4c54f3453e688c000000001a', new DocumentObjectId]); + + oids[0].should.be.an.instanceof(DocumentObjectId); + oids[1].should.be.an.instanceof(DocumentObjectId); + + var dates = Loki.path('dates').cast(['8/24/2010', 1294541504958]); + + dates[0].should.be.an.instanceof(Date); + dates[1].should.be.an.instanceof(Date); + + var numbers = Loki.path('numbers').cast([152, '31']); + + numbers[0].should.be.a('number'); + numbers[1].should.be.a('number'); + + var strings = Loki.path('strings').cast(['test', 123]); + + strings[0].should.be.a('string'); + strings[0].should.eql('test'); + + strings[1].should.be.a('string'); + strings[1].should.eql('123'); + + var buffers = Loki.path('buffers').cast(['\0\0\0', new Buffer("abc")]); + + buffers[0].should.be.an.instanceof(Buffer); + buffers[1].should.be.an.instanceof(Buffer); + + var nocasts = Loki.path('nocast').cast(['test', 123]); + + nocasts[0].should.be.a('string'); + nocasts[0].should.eql('test'); + + nocasts[1].should.be.a('number'); + nocasts[1].should.eql(123); + + var mixed = Loki.path('mixed').cast(['test', 123, '123', {}, new Date, new DocumentObjectId]); + + mixed[0].should.be.a('string'); + mixed[1].should.be.a('number'); + mixed[2].should.be.a('string'); + mixed[3].should.be.a('object'); + mixed[4].should.be.an.instanceof(Date); + mixed[5].should.be.an.instanceof(DocumentObjectId); + }, + + 'test boolean required validator': function(){ + var Animal = new Schema({ + isFerret: { type: Boolean, required: true } + }); + + Animal.path('isFerret').doValidate(null, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Animal.path('isFerret').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Animal.path('isFerret').doValidate(true, function(err){ + should.strictEqual(err, null); + }); + + Animal.path('isFerret').doValidate(false, function(err){ + should.strictEqual(err, null); + }); + }, + + 'test boolean casting': function(){ + var Animal = new Schema({ + isFerret: { type: Boolean, required: true } + }); + + should.strictEqual(Animal.path('isFerret').cast(null), null); + Animal.path('isFerret').cast(undefined).should.be.false; + Animal.path('isFerret').cast(false).should.be.false; + Animal.path('isFerret').cast(0).should.be.false; + Animal.path('isFerret').cast('0').should.be.false; + Animal.path('isFerret').cast({}).should.be.true; + Animal.path('isFerret').cast(true).should.be.true; + Animal.path('isFerret').cast(1).should.be.true; + Animal.path('isFerret').cast('1').should.be.true; + }, + + 'test async validators': function(beforeExit){ + var executed = 0; + + function validator (value, fn) { + setTimeout(function(){ + executed++; + fn(value === true); + }, 50); + }; + + var Animal = new Schema({ + ferret: { type: Boolean, validate: validator } + }); + + Animal.path('ferret').doValidate(true, function(err){ + should.strictEqual(err, null); + }); + + Animal.path('ferret').doValidate(false, function(err){ + err.should.be.an.instanceof(Error); + }); + + beforeExit(function(){ + executed.should.eql(2); + }); + }, + + 'test async validators scope': function(beforeExit){ + var executed = false; + + function validator (value, fn) { + this.a.should.eql('b'); + + setTimeout(function(){ + executed = true; + fn(true); + }, 50); + }; + + var Animal = new Schema({ + ferret: { type: Boolean, validate: validator } + }); + + Animal.path('ferret').doValidate(true, function(err){ + should.strictEqual(err, null); + }, { a: 'b' }); + + beforeExit(function(){ + executed.should.be.true; + }); + }, + + 'test declaring new methods': function(){ + var a = new Schema(); + a.method('test', function(){}); + a.method({ + a: function(){} + , b: function(){} + }); + + Object.keys(a.methods).should.have.length(3); + }, + + 'test declaring new statics': function(){ + var a = new Schema(); + a.static('test', function(){}); + a.static({ + a: function(){} + , b: function(){} + , c: function(){} + }); + + Object.keys(a.statics).should.have.length(4); + }, + + 'test setter(s)': function(){ + function lowercase (v) { + return v.toLowerCase(); + }; + + var Tobi = new Schema({ + name: { type: String, set: lowercase } + }); + + Tobi.path('name').applySetters('WOOT').should.eql('woot'); + Tobi.path('name').setters.should.have.length(1); + + Tobi.path('name').set(function(v){ + return v + 'WOOT'; + }); + + Tobi.path('name').applySetters('WOOT').should.eql('wootwoot'); + Tobi.path('name').setters.should.have.length(2); + }, + + 'test setters scope': function(){ + function lowercase (v) { + this.a.should.eql('b'); + return v.toLowerCase(); + }; + + var Tobi = new Schema({ + name: { type: String, set: lowercase } + }); + + Tobi.path('name').applySetters('WHAT', { a: 'b' }).should.eql('what'); + }, + + 'test string built-in setter `lowercase`': function () { + var Tobi = new Schema({ + name: { type: String, lowercase: true } + }); + + Tobi.path('name').applySetters('WHAT').should.eql('what'); + }, + + 'test string built-in setter `uppercase`': function () { + var Tobi = new Schema({ + name: { type: String, uppercase: true } + }); + + Tobi.path('name').applySetters('what').should.eql('WHAT'); + }, + + 'test string built-in setter `trim`': function () { + var Tobi = new Schema({ + name: { type: String, uppercase: true, trim: true } + }); + + Tobi.path('name').applySetters(' what ').should.eql('WHAT'); + }, + + 'test getter(s)': function(){ + function woot (v) { + return v + ' woot'; + }; + + var Tobi = new Schema({ + name: { type: String, get: woot } + }); + + Tobi.path('name').getters.should.have.length(1); + Tobi.path('name').applyGetters('test').should.eql('test woot'); + }, + + 'test getters scope': function(){ + function woot (v) { + this.a.should.eql('b'); + return v.toLowerCase(); + }; + + var Tobi = new Schema({ + name: { type: String, get: woot } + }); + + Tobi.path('name').applyGetters('YEP', { a: 'b' }).should.eql('yep'); + }, + + 'test setters casting': function(){ + function last (v) { + v.should.be.a('string'); + v.should.eql('0'); + return 'last'; + }; + + function first (v) { + return 0; + }; + + var Tobi = new Schema({ + name: { type: String, set: last } + }); + + Tobi.path('name').set(first); + Tobi.path('name').applySetters('woot').should.eql('last'); + }, + + 'test getters casting': function(){ + function last (v) { + v.should.be.a('string'); + v.should.eql('0'); + return 'last'; + }; + + function first (v) { + return 0; + }; + + var Tobi = new Schema({ + name: { type: String, get: last } + }); + + Tobi.path('name').get(first); + Tobi.path('name').applyGetters('woot').should.eql('last'); + }, + + 'test hooks registration': function(){ + var Tobi = new Schema(); + + Tobi.pre('save', function(){}); + Tobi.callQueue.should.have.length(1); + + Tobi.post('save', function(){}); + Tobi.callQueue.should.have.length(2); + + Tobi.pre('save', function(){}); + Tobi.callQueue.should.have.length(3); + }, + + 'test applying setters when none have been defined': function(){ + var Tobi = new Schema({ + name: String + }); + + Tobi.path('name').applySetters('woot').should.eql('woot'); + }, + + 'test applying getters when none have been defined': function(){ + var Tobi = new Schema({ + name: String + }); + + Tobi.path('name').applyGetters('woot').should.eql('woot'); + }, + + 'test defining an index': function(){ + var Tobi = new Schema({ + name: { type: String, index: true } + }); + + Tobi.path('name')._index.should.be.true; + Tobi.path('name').index({ unique: true }); + Tobi.path('name')._index.should.eql({ unique: true }); + Tobi.path('name').unique(false); + Tobi.path('name')._index.should.eql({ unique: false}); + + var T1 = new Schema({ + name: { type: String, sparse: true } + }); + T1.path('name')._index.should.eql({ sparse: true }); + + var T2 = new Schema({ + name: { type: String, unique: true } + }); + T2.path('name')._index.should.eql({ unique: true }); + + var T3 = new Schema({ + name: { type: String, sparse: true, unique: true } + }); + T3.path('name')._index.should.eql({ sparse: true, unique: true }); + + var T4 = new Schema({ + name: { type: String, unique: true, sparse: true } + }); + var i = T4.path('name')._index; + i.unique.should.be.true; + i.sparse.should.be.true; + + var T5 = new Schema({ + name: { type: String, index: { sparse: true, unique: true } } + }); + var i = T5.path('name')._index; + i.unique.should.be.true; + i.sparse.should.be.true; + }, + + 'test defining compound indexes': function(){ + var Tobi = new Schema({ + name: { type: String, index: true } + , last: { type: Number, sparse: true } + }); + + Tobi.index({ firstname: 1, last: 1 }, { unique: true }); + + Tobi.indexes.should.eql([ + [{ name: 1 }, {}] + , [{ last: 1 }, { sparse: true }] + , [{ firstname: 1, last: 1}, {unique: true}] + ]); + }, + + 'test plugins': function (beforeExit) { + var Tobi = new Schema() + , called = false; + + Tobi.plugin(function(schema){ + schema.should.equal(Tobi); + called = true; + }); + + beforeExit(function () { + called.should.be.true; + }); + }, + + 'test that default options are set': function () { + var Tobi = new Schema(); + + Tobi.options.should.be.a('object'); + Tobi.options.safe.should.be.true; + }, + + 'test setting options': function () { + var Tobi = new Schema({}, { collection: 'users' }); + + Tobi.set('a', 'b'); + Tobi.set('safe', false); + Tobi.options.collection.should.eql('users'); + + Tobi.options.a.should.eql('b'); + Tobi.options.safe.should.be.false; + }, + + 'test declaring virtual attributes': function () { + var Contact = new Schema({ + firstName: String + , lastName: String + }); + Contact.virtual('fullName') + .get( function () { + return this.get('firstName') + ' ' + this.get('lastName'); + }).set(function (fullName) { + var split = fullName.split(' '); + this.set('firstName', split[0]); + this.set('lastName', split[1]); + }); + + Contact.virtualpath('fullName').should.be.an.instanceof(VirtualType); + }, + + 'test GH-298 - The default creation of a virtual `id` should be muted when someone defines their own `id` attribute': function () { + new Schema({ id: String }); + }, + + 'allow disabling the auto .id virtual': function () { + var schema = new Schema({ name: String }, { noVirtualId: true }); + should.strictEqual(undefined, schema.virtuals.id); + }, + + 'schema creation works with objects from other contexts': function () { + var str = 'code = {' + + ' name: String' + + ', arr1: Array ' + + ', arr2: { type: [] }' + + ', date: Date ' + + ', num: { type: Number }' + + ', bool: Boolean' + + ', nest: { sub: { type: {}, required: true }}' + + '}'; + + var script = vm.createScript(str, 'testSchema.vm'); + var sandbox = { code: null }; + script.runInNewContext(sandbox); + + var Ferret = new Schema(sandbox.code); + Ferret.path('nest.sub').should.be.an.instanceof(SchemaTypes.Mixed); + Ferret.path('name').should.be.an.instanceof(SchemaTypes.String); + Ferret.path('arr1').should.be.an.instanceof(SchemaTypes.Array); + Ferret.path('arr2').should.be.an.instanceof(SchemaTypes.Array); + Ferret.path('date').should.be.an.instanceof(SchemaTypes.Date); + Ferret.path('num').should.be.an.instanceof(SchemaTypes.Number); + Ferret.path('bool').should.be.an.instanceof(SchemaTypes.Boolean); + }, + + 'schema string casts undefined to "undefined"': function () { + var db= require('./common')(); + var schema = new Schema({ arr: [String] }); + var M = db.model('castingStringArrayWithUndefined', schema); + M.find({ arr: { $in: [undefined] }}, function (err) { + db.close(); + should.equal(err && err.message, 'Cast to string failed for value "undefined"'); + }) + }, + + 'array of object literal missing a `type` is interpreted as Mixed': function () { + var s = new Schema({ + arr: [ + { something: { type: String } } + ] + }); + }, + + 'helpful schema debugging msg': function () { + var err; + try { + new Schema({ name: { first: null } }) + } catch (e) { + err = e; + } + err.message.should.equal('Invalid value for schema path `name.first`') + try { + new Schema({ age: undefined }) + } catch (e) { + err = e; + } + err.message.should.equal('Invalid value for schema path `age`') + } + +}; diff --git a/node_modules/mongoose/test/types.array.test.js b/node_modules/mongoose/test/types.array.test.js new file mode 100644 index 0000000..ac00eb4 --- /dev/null +++ b/node_modules/mongoose/test/types.array.test.js @@ -0,0 +1,548 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = require('./common').mongoose + , Schema = mongoose.Schema + , random = require('../lib/utils').random + , MongooseArray = mongoose.Types.Array; + +var User = new Schema({ + name: String + , pets: [Schema.ObjectId] +}); + +mongoose.model('User', User); + +var Pet = new Schema({ + name: String +}); + +mongoose.model('Pet', Pet); + +/** + * Test. + */ + +module.exports = { + + 'test that a mongoose array behaves and quacks like an array': function(){ + var a = new MongooseArray; + + a.should.be.an.instanceof(Array); + a.should.be.an.instanceof(MongooseArray); + Array.isArray(a).should.be.true; + ;(a._atomics.constructor).should.eql(Object); + + }, + + 'doAtomics does not throw': function () { + var b = new MongooseArray([12,3,4,5]).filter(Boolean); + var threw = false; + + try { + b.doAtomics + } catch (_) { + threw = true; + } + + threw.should.be.false; + + var a = new MongooseArray([67,8]).filter(Boolean); + try { + a.push(3,4); + } catch (_) { + console.error(_); + threw = true; + } + + threw.should.be.false; + + }, + + 'test indexOf()': function(){ + var db = start() + , User = db.model('User', 'users_' + random()) + , Pet = db.model('Pet', 'pets' + random()); + + var tj = new User({ name: 'tj' }) + , tobi = new Pet({ name: 'tobi' }) + , loki = new Pet({ name: 'loki' }) + , jane = new Pet({ name: 'jane' }) + , pets = []; + + tj.pets.push(tobi); + tj.pets.push(loki); + tj.pets.push(jane); + + var pending = 3; + + ;[tobi, loki, jane].forEach(function(pet){ + pet.save(function(){ + --pending || done(); + }); + }); + + function done() { + Pet.find({}, function(err, pets){ + tj.save(function(err){ + User.findOne({ name: 'tj' }, function(err, user){ + db.close(); + should.equal(null, err, 'error in callback'); + user.pets.should.have.length(3); + user.pets.indexOf(tobi.id).should.equal(0); + user.pets.indexOf(loki.id).should.equal(1); + user.pets.indexOf(jane.id).should.equal(2); + user.pets.indexOf(tobi._id).should.equal(0); + user.pets.indexOf(loki._id).should.equal(1); + user.pets.indexOf(jane._id).should.equal(2); + }); + }); + }); + } + }, + + 'test #splice() with numbers': function () { + var collection = 'splicetest-number' + random(); + var db = start() + , schema = new Schema({ numbers: Array }) + , A = db.model('splicetestNumber', schema, collection); + + var a = new A({ numbers: [4,5,6,7] }); + a.save(function (err) { + should.equal(null, err, 'could not save splice test'); + A.findById(a._id, function (err, doc) { + should.equal(null, err, 'error finding splice doc'); + doc.numbers.splice(1, 1); + doc.numbers.toObject().should.eql([4,6,7]); + doc.save(function (err) { + should.equal(null, err, 'could not save splice test'); + A.findById(a._id, function (err, doc) { + should.equal(null, err, 'error finding splice doc'); + doc.numbers.toObject().should.eql([4,6,7]); + + A.collection.drop(function (err) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + }); + }, + + 'test #splice() on embedded docs': function () { + var collection = 'splicetest-embeddeddocs' + random(); + var db = start() + , schema = new Schema({ types: [new Schema({ type: String }) ]}) + , A = db.model('splicetestEmbeddedDoc', schema, collection); + + var a = new A({ types: [{type:'bird'},{type:'boy'},{type:'frog'},{type:'cloud'}] }); + a.save(function (err) { + should.equal(null, err, 'could not save splice test'); + A.findById(a._id, function (err, doc) { + should.equal(null, err, 'error finding splice doc'); + + doc.types.splice(1, 1); + + var obj = doc.types.toObject(); + obj[0].type.should.eql('bird'); + obj[1].type.should.eql('frog'); + obj[2].type.should.eql('cloud'); + + doc.save(function (err) { + should.equal(null, err, 'could not save splice test'); + A.findById(a._id, function (err, doc) { + should.equal(null, err, 'error finding splice doc'); + + var obj = doc.types.toObject(); + obj[0].type.should.eql('bird'); + obj[1].type.should.eql('frog'); + obj[2].type.should.eql('cloud'); + + A.collection.drop(function (err) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + }); + }, + + '#unshift': function () { + var db = start() + , schema = new Schema({ + types: [new Schema({ type: String })] + , nums: [Number] + , strs: [String] + }) + , A = db.model('unshift', schema, 'unshift'+random()); + + var a = new A({ + types: [{type:'bird'},{type:'boy'},{type:'frog'},{type:'cloud'}] + , nums: [1,2,3] + , strs: 'one two three'.split(' ') + }); + + a.save(function (err) { + should.equal(null, err); + A.findById(a._id, function (err, doc) { + should.equal(null, err); + + var tlen = doc.types.unshift({type:'tree'}); + var nlen = doc.nums.unshift(0); + var slen = doc.strs.unshift('zero'); + + tlen.should.equal(5); + nlen.should.equal(4); + slen.should.equal(4); + + var obj = doc.types.toObject(); + obj[0].type.should.eql('tree'); + obj[1].type.should.eql('bird'); + obj[2].type.should.eql('boy'); + obj[3].type.should.eql('frog'); + obj[4].type.should.eql('cloud'); + + obj = doc.nums.toObject(); + obj[0].valueOf().should.equal(0); + obj[1].valueOf().should.equal(1); + obj[2].valueOf().should.equal(2); + obj[3].valueOf().should.equal(3); + + obj = doc.strs.toObject(); + obj[0].should.equal('zero'); + obj[1].should.equal('one'); + obj[2].should.equal('two'); + obj[3].should.equal('three'); + + doc.save(function (err) { + should.equal(null, err); + A.findById(a._id, function (err, doc) { + should.equal(null, err); + + var obj = doc.types.toObject(); + obj[0].type.should.eql('tree'); + obj[1].type.should.eql('bird'); + obj[2].type.should.eql('boy'); + obj[3].type.should.eql('frog'); + obj[4].type.should.eql('cloud'); + + obj = doc.nums.toObject(); + obj[0].valueOf().should.equal(0); + obj[1].valueOf().should.equal(1); + obj[2].valueOf().should.equal(2); + obj[3].valueOf().should.equal(3); + + obj = doc.strs.toObject(); + obj[0].should.equal('zero'); + obj[1].should.equal('one'); + obj[2].should.equal('two'); + obj[3].should.equal('three'); + + A.collection.drop(function (err) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + }); + }, + + '#addToSet': function () { + var db = start() + , e = new Schema({ name: String, arr: [] }) + , schema = new Schema({ + num: [Number] + , str: [String] + , doc: [e] + , date: [Date] + , id: [Schema.ObjectId] + }); + + var M = db.model('testAddToSet', schema); + var m = new M; + + m.num.push(1,2,3); + m.str.push('one','two','tres'); + m.doc.push({ name: 'Dubstep', arr: [1] }, { name: 'Polka', arr: [{ x: 3 }]}); + + var d1 = new Date; + var d2 = new Date( +d1 + 60000); + var d3 = new Date( +d1 + 30000); + var d4 = new Date( +d1 + 20000); + var d5 = new Date( +d1 + 90000); + var d6 = new Date( +d1 + 10000); + m.date.push(d1, d2); + + var id1 = new mongoose.Types.ObjectId; + var id2 = new mongoose.Types.ObjectId; + var id3 = new mongoose.Types.ObjectId; + var id4 = new mongoose.Types.ObjectId; + var id5 = new mongoose.Types.ObjectId; + var id6 = new mongoose.Types.ObjectId; + + m.id.push(id1, id2); + + m.num.addToSet(3,4,5); + m.num.length.should.equal(5); + m.str.$addToSet('four', 'five', 'two'); + m.str.length.should.equal(5); + m.id.addToSet(id2, id3); + m.id.length.should.equal(3); + m.doc.$addToSet(m.doc[0]); + m.doc.length.should.equal(2); + m.doc.$addToSet({ name: 'Waltz', arr: [1] }, m.doc[0]); + m.doc.length.should.equal(3); + m.date.length.should.equal(2); + m.date.$addToSet(d1); + m.date.length.should.equal(2); + m.date.addToSet(d3); + m.date.length.should.equal(3); + + m.save(function (err) { + should.strictEqual(null, err); + M.findById(m, function (err, m) { + should.strictEqual(null, err); + + m.num.length.should.equal(5); + (~m.num.indexOf(1)).should.be.ok; + (~m.num.indexOf(2)).should.be.ok; + (~m.num.indexOf(3)).should.be.ok; + (~m.num.indexOf(4)).should.be.ok; + (~m.num.indexOf(5)).should.be.ok; + + m.str.length.should.equal(5); + (~m.str.indexOf('one')).should.be.ok; + (~m.str.indexOf('two')).should.be.ok; + (~m.str.indexOf('tres')).should.be.ok; + (~m.str.indexOf('four')).should.be.ok; + (~m.str.indexOf('five')).should.be.ok; + + m.id.length.should.equal(3); + (~m.id.indexOf(id1)).should.be.ok; + (~m.id.indexOf(id2)).should.be.ok; + (~m.id.indexOf(id3)).should.be.ok; + + m.date.length.should.equal(3); + (~m.date.indexOf(d1.toString())).should.be.ok; + (~m.date.indexOf(d2.toString())).should.be.ok; + (~m.date.indexOf(d3.toString())).should.be.ok; + + m.doc.length.should.equal(3); + m.doc.some(function(v){return v.name === 'Waltz'}).should.be.ok + m.doc.some(function(v){return v.name === 'Dubstep'}).should.be.ok + m.doc.some(function(v){return v.name === 'Polka'}).should.be.ok + + // test single $addToSet + m.num.addToSet(3,4,5,6); + m.num.length.should.equal(6); + m.str.$addToSet('four', 'five', 'two', 'six'); + m.str.length.should.equal(6); + m.id.addToSet(id2, id3, id4); + m.id.length.should.equal(4); + + m.date.$addToSet(d1, d3, d4); + m.date.length.should.equal(4); + + m.doc.$addToSet(m.doc[0], { name: '8bit' }); + m.doc.length.should.equal(4); + + m.save(function (err) { + should.strictEqual(null, err); + + M.findById(m, function (err, m) { + should.strictEqual(null, err); + + m.num.length.should.equal(6); + (~m.num.indexOf(1)).should.be.ok; + (~m.num.indexOf(2)).should.be.ok; + (~m.num.indexOf(3)).should.be.ok; + (~m.num.indexOf(4)).should.be.ok; + (~m.num.indexOf(5)).should.be.ok; + (~m.num.indexOf(6)).should.be.ok; + + m.str.length.should.equal(6); + (~m.str.indexOf('one')).should.be.ok; + (~m.str.indexOf('two')).should.be.ok; + (~m.str.indexOf('tres')).should.be.ok; + (~m.str.indexOf('four')).should.be.ok; + (~m.str.indexOf('five')).should.be.ok; + (~m.str.indexOf('six')).should.be.ok; + + m.id.length.should.equal(4); + (~m.id.indexOf(id1)).should.be.ok; + (~m.id.indexOf(id2)).should.be.ok; + (~m.id.indexOf(id3)).should.be.ok; + (~m.id.indexOf(id4)).should.be.ok; + + m.date.length.should.equal(4); + (~m.date.indexOf(d1.toString())).should.be.ok; + (~m.date.indexOf(d2.toString())).should.be.ok; + (~m.date.indexOf(d3.toString())).should.be.ok; + (~m.date.indexOf(d4.toString())).should.be.ok; + + m.doc.length.should.equal(4); + m.doc.some(function(v){return v.name === 'Waltz'}).should.be.ok + m.doc.some(function(v){return v.name === 'Dubstep'}).should.be.ok + m.doc.some(function(v){return v.name === 'Polka'}).should.be.ok + m.doc.some(function(v){return v.name === '8bit'}).should.be.ok + + // test multiple $addToSet + m.num.addToSet(7,8); + m.num.length.should.equal(8); + m.str.$addToSet('seven', 'eight'); + m.str.length.should.equal(8); + m.id.addToSet(id5, id6); + m.id.length.should.equal(6); + + m.date.$addToSet(d5, d6); + m.date.length.should.equal(6); + + m.doc.$addToSet(m.doc[1], { name: 'BigBeat' }, { name: 'Funk' }); + m.doc.length.should.equal(6); + + m.save(function (err) { + should.strictEqual(null, err); + + M.findById(m, function (err, m) { + db.close(); + should.strictEqual(null, err); + + m.num.length.should.equal(8); + (~m.num.indexOf(1)).should.be.ok; + (~m.num.indexOf(2)).should.be.ok; + (~m.num.indexOf(3)).should.be.ok; + (~m.num.indexOf(4)).should.be.ok; + (~m.num.indexOf(5)).should.be.ok; + (~m.num.indexOf(6)).should.be.ok; + (~m.num.indexOf(7)).should.be.ok; + (~m.num.indexOf(8)).should.be.ok; + + m.str.length.should.equal(8); + (~m.str.indexOf('one')).should.be.ok; + (~m.str.indexOf('two')).should.be.ok; + (~m.str.indexOf('tres')).should.be.ok; + (~m.str.indexOf('four')).should.be.ok; + (~m.str.indexOf('five')).should.be.ok; + (~m.str.indexOf('six')).should.be.ok; + (~m.str.indexOf('seven')).should.be.ok; + (~m.str.indexOf('eight')).should.be.ok; + + m.id.length.should.equal(6); + (~m.id.indexOf(id1)).should.be.ok; + (~m.id.indexOf(id2)).should.be.ok; + (~m.id.indexOf(id3)).should.be.ok; + (~m.id.indexOf(id4)).should.be.ok; + (~m.id.indexOf(id5)).should.be.ok; + (~m.id.indexOf(id6)).should.be.ok; + + m.date.length.should.equal(6); + (~m.date.indexOf(d1.toString())).should.be.ok; + (~m.date.indexOf(d2.toString())).should.be.ok; + (~m.date.indexOf(d3.toString())).should.be.ok; + (~m.date.indexOf(d4.toString())).should.be.ok; + (~m.date.indexOf(d5.toString())).should.be.ok; + (~m.date.indexOf(d6.toString())).should.be.ok; + + m.doc.length.should.equal(6); + m.doc.some(function(v){return v.name === 'Waltz'}).should.be.ok + m.doc.some(function(v){return v.name === 'Dubstep'}).should.be.ok + m.doc.some(function(v){return v.name === 'Polka'}).should.be.ok + m.doc.some(function(v){return v.name === '8bit'}).should.be.ok + m.doc.some(function(v){return v.name === 'BigBeat'}).should.be.ok + m.doc.some(function(v){return v.name === 'Funk'}).should.be.ok + }); + }); + }); + }); + }); + }); + }, + + 'setting doc array should adjust path positions': function () { + var db = start(); + + var D = db.model('subDocPositions', new Schema({ + em1: [new Schema({ name: String })] + })); + + var d = new D({ + em1: [ + { name: 'pos0' } + , { name: 'pos1' } + , { name: 'pos2' } + ] + }); + + d.save(function (err) { + should.strictEqual(null, err); + D.findById(d, function (err, d) { + should.strictEqual(null, err); + + var n = d.em1.slice(); + n[2].name = 'position two'; + var x = []; + x[1] = n[2]; + x[2] = n[1]; + d.em1 = x.filter(Boolean); + + d.save(function (err) { + should.strictEqual(null, err); + D.findById(d, function (err, d) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }); + }, + + 'paths with similar names should be saved': function () { + var db = start(); + + var D = db.model('similarPathNames', new Schema({ + account: { + role: String + , roles: [String] + } + , em: [new Schema({ name: String })] + })); + + var d = new D({ + account: { role: 'teacher', roles: ['teacher', 'admin'] } + , em: [{ name: 'bob' }] + }); + + d.save(function (err) { + should.strictEqual(null, err); + D.findById(d, function (err, d) { + should.strictEqual(null, err); + + d.account.role = 'president'; + d.account.roles = ['president', 'janitor']; + d.em[0].name = 'memorable'; + d.em = [{ name: 'frida' }]; + + d.save(function (err) { + should.strictEqual(null, err); + D.findById(d, function (err, d) { + db.close(); + should.strictEqual(null, err); + d.account.role.should.equal('president'); + d.account.roles.length.should.equal(2); + d.account.roles[0].should.equal('president'); + d.account.roles[1].should.equal('janitor'); + d.em.length.should.equal(1); + d.em[0].name.should.equal('frida'); + }); + }); + }); + }); + } +}; diff --git a/node_modules/mongoose/test/types.buffer.test.js b/node_modules/mongoose/test/types.buffer.test.js new file mode 100644 index 0000000..b9c8d5d --- /dev/null +++ b/node_modules/mongoose/test/types.buffer.test.js @@ -0,0 +1,350 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = require('./common').mongoose + , Schema = mongoose.Schema + , random = require('../lib/utils').random + , MongooseBuffer = mongoose.Types.Buffer; + +// can't index Buffer fields yet + +function valid (v) { + return !v || v.length > 10; +} + +var subBuf = new Schema({ + name: String + , buf: { type: Buffer, validate: [valid, 'valid failed'], required: true } +}); + +var UserBuffer = new Schema({ + name: String + , serial: Buffer + , array: [Buffer] + , required: { type: Buffer, required: true, index: true } + , sub: [subBuf] +}); + +// Dont put indexed models on the default connection, it +// breaks index.test.js tests on a "pure" default conn. +// mongoose.model('UserBuffer', UserBuffer); + +/** + * Test. + */ + +module.exports = { + + 'test that a mongoose buffer behaves and quacks like an buffer': function(){ + var a = new MongooseBuffer; + + a.should.be.an.instanceof(Buffer); + a.should.be.an.instanceof(MongooseBuffer); + Buffer.isBuffer(a).should.be.true; + + var a = new MongooseBuffer([195, 188, 98, 101, 114]); + var b = new MongooseBuffer("buffer shtuffs are neat"); + var c = new MongooseBuffer('aGVsbG8gd29ybGQ=', 'base64'); + + a.toString('utf8').should.equal('über'); + b.toString('utf8').should.equal('buffer shtuffs are neat'); + c.toString('utf8').should.equal('hello world'); + }, + + 'buffer validation': function () { + var db = start() + , User = db.model('UserBuffer', UserBuffer, 'usersbuffer_' + random()); + + User.on('index', function () { + var t = new User({ + name: 'test validation' + }); + + t.validate(function (err) { + err.message.should.eql('Validation failed'); + err.errors.required.type.should.equal('required'); + t.required = 20; + t.save(function (err) { + err.name.should.eql('CastError'); + err.type.should.eql('buffer'); + err.value.should.equal(20); + err.message.should.eql('Cast to buffer failed for value "20"'); + t.required = new Buffer("hello"); + + t.sub.push({ name: 'Friday Friday' }); + t.save(function (err) { + err.message.should.eql('Validation failed'); + err.errors.buf.type.should.equal('required'); + t.sub[0].buf = new Buffer("well well"); + t.save(function (err) { + err.message.should.eql('Validation failed'); + err.errors.buf.type.should.equal('valid failed'); + + t.sub[0].buf = new Buffer("well well well"); + t.validate(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }); + }); + }) + }, + + 'buffer storage': function(){ + var db = start() + , User = db.model('UserBuffer', UserBuffer, 'usersbuffer_' + random()); + + User.on('index', function () { + var sampleBuffer = new Buffer([123, 223, 23, 42, 11]); + + var tj = new User({ + name: 'tj' + , serial: sampleBuffer + , required: new Buffer(sampleBuffer) + }); + + tj.save(function (err) { + should.equal(null, err); + User.find({}, function (err, users) { + db.close(); + should.equal(null, err); + users.should.have.length(1); + var user = users[0]; + var base64 = sampleBuffer.toString('base64'); + should.equal(base64, + user.serial.toString('base64'), 'buffer mismatch'); + should.equal(base64, + user.required.toString('base64'), 'buffer mismatch'); + }); + }); + }); + }, + + 'test write markModified': function(){ + var db = start() + , User = db.model('UserBuffer', UserBuffer, 'usersbuffer_' + random()); + + User.on('index', function () { + var sampleBuffer = new Buffer([123, 223, 23, 42, 11]); + + var tj = new User({ + name: 'tj' + , serial: sampleBuffer + , required: sampleBuffer + }); + + tj.save(function (err) { + should.equal(null, err); + + tj.serial.write('aa', 1, 'ascii'); + tj.isModified('serial').should.be.true; + + tj.save(function (err) { + should.equal(null, err); + + User.findById(tj._id, function (err, user) { + db.close(); + should.equal(null, err); + + var expectedBuffer = new Buffer([123, 97, 97, 42, 11]); + + should.equal(expectedBuffer.toString('base64'), + user.serial.toString('base64'), 'buffer mismatch'); + + tj.isModified('required').should.be.false; + tj.serial.copy(tj.required, 1); + tj.isModified('required').should.be.true; + should.equal('e3thYSo=', tj.required.toString('base64')); + + // buffer method tests + var fns = { + 'writeUInt8': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt8(0x3, 0, 'big'); + tj.isModified('required').should.be.true; + } + , 'writeUInt16': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt16(0xbeef, 0, 'little'); + tj.isModified('required').should.be.true; + } + , 'writeUInt16LE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt16LE(0xbeef, 0); + tj.isModified('required').should.be.true; + } + , 'writeUInt16BE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt16BE(0xbeef, 0); + tj.isModified('required').should.be.true; + } + , 'writeUInt32': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt32(0xfeedface, 0, 'little'); + tj.isModified('required').should.be.true; + } + , 'writeUInt32LE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt32LE(0xfeedface, 0); + tj.isModified('required').should.be.true; + } + , 'writeUInt32BE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt32BE(0xfeedface, 0); + tj.isModified('required').should.be.true; + } + , 'writeInt8': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt8(-5, 0, 'big'); + tj.isModified('required').should.be.true; + } + , 'writeInt16': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt16(0x0023, 2, 'little'); + tj.isModified('required').should.be.true; + tj.required[2].should.eql(0x23); + tj.required[3].should.eql(0x00); + } + , 'writeInt16LE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt16LE(0x0023, 2); + tj.isModified('required').should.be.true; + tj.required[2].should.eql(0x23); + tj.required[3].should.eql(0x00); + } + , 'writeInt16BE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt16BE(0x0023, 2); + tj.isModified('required').should.be.true; + } + , 'writeInt32': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt32(0x23, 0, 'big'); + tj.isModified('required').should.be.true; + tj.required[0].should.eql(0x00); + tj.required[1].should.eql(0x00); + tj.required[2].should.eql(0x00); + tj.required[3].should.eql(0x23); + tj.required = new Buffer(8); + } + , 'writeInt32LE': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt32LE(0x23, 0); + tj.isModified('required').should.be.true; + } + , 'writeInt32BE': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt32BE(0x23, 0); + tj.isModified('required').should.be.true; + tj.required[0].should.eql(0x00); + tj.required[1].should.eql(0x00); + tj.required[2].should.eql(0x00); + tj.required[3].should.eql(0x23); + } + , 'writeFloat': function () { + tj.required = new Buffer(16); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeFloat(2.225073858507201e-308, 0, 'big'); + tj.isModified('required').should.be.true; + tj.required[0].should.eql(0x00); + tj.required[1].should.eql(0x0f); + tj.required[2].should.eql(0xff); + tj.required[3].should.eql(0xff); + tj.required[4].should.eql(0xff); + tj.required[5].should.eql(0xff); + tj.required[6].should.eql(0xff); + tj.required[7].should.eql(0xff); + } + , 'writeFloatLE': function () { + tj.required = new Buffer(16); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeFloatLE(2.225073858507201e-308, 0); + tj.isModified('required').should.be.true; + } + , 'writeFloatBE': function () { + tj.required = new Buffer(16); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeFloatBE(2.225073858507201e-308, 0); + tj.isModified('required').should.be.true; + } + , 'writeDoubleLE': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeDoubleLE(0xdeadbeefcafebabe, 0); + tj.isModified('required').should.be.true; + } + , 'writeDoubleBE': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeDoubleBE(0xdeadbeefcafebabe, 0); + tj.isModified('required').should.be.true; + } + , 'fill': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.fill(0); + tj.isModified('required').should.be.true; + for (var i = 0; i < tj.required.length; i++) { + tj.required[i].should.eql(0); + } + } + , 'set': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.set(0, 1); + tj.isModified('required').should.be.true; + } + }; + + var keys = Object.keys(fns) + , i = keys.length + , key + + while (i--) { + key = keys[i]; + if (Buffer.prototype[key]) { + fns[key](); + } + } + }); + }); + }); + }); + + function reset (model) { + // internal + model._activePaths.clear('modify'); + model.schema.requiredPaths.forEach(function (path) { + model._activePaths.require(path); + }); + } + } +}; diff --git a/node_modules/mongoose/test/types.document.test.js b/node_modules/mongoose/test/types.document.test.js new file mode 100644 index 0000000..a422403 --- /dev/null +++ b/node_modules/mongoose/test/types.document.test.js @@ -0,0 +1,197 @@ + +/** + * Module dependencies. + */ + +var should = require('should') + , start = require('./common') + , mongoose = start.mongoose + , EmbeddedDocument = require('../lib/types/embedded') + , DocumentArray = require('../lib/types/documentarray') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , ValidatorError = SchemaType.ValidatorError + , ValidationError = mongoose.Document.ValidationError + +/** + * Setup. + */ + +function Subdocument () { + EmbeddedDocument.call(this, {}, new DocumentArray); +}; + +/** + * Inherits from EmbeddedDocument. + */ + +Subdocument.prototype.__proto__ = EmbeddedDocument.prototype; + +/** + * Set schema. + */ + +Subdocument.prototype.schema = new Schema({ + test: { type: String, required: true } + , work: { type: String, validate: /^good/ } +}); + +/** + * Schema. + */ + +var RatingSchema = new Schema({ + stars: Number +}); + +var MovieSchema = new Schema({ + title: String + , ratings: [RatingSchema] +}); + +mongoose.model('Movie', MovieSchema); + +/** + * Test. + */ + +module.exports = { + + 'test that save fires errors': function(){ + var a = new Subdocument(); + a.set('test', ''); + a.set('work', 'nope'); + + a.save(function(err){ + err.should.be.an.instanceof(ValidationError); + err.toString().should.eql('ValidationError: Validator "required" failed for path test, Validator failed for path work'); + }); + }, + + 'test that save fires with null if no errors': function(){ + var a = new Subdocument(); + a.set('test', 'cool'); + a.set('work', 'goods'); + + a.save(function(err){ + should.strictEqual(err, null); + }); + }, + + 'objects can be passed to #set': function () { + var a = new Subdocument(); + a.set({ test: 'paradiddle', work: 'good flam'}); + a.test.should.eql('paradiddle'); + a.work.should.eql('good flam'); + }, + + 'Subdocuments can be passed to #set': function () { + var a = new Subdocument(); + a.set({ test: 'paradiddle', work: 'good flam'}); + a.test.should.eql('paradiddle'); + a.work.should.eql('good flam'); + var b = new Subdocument(); + b.set(a); + b.test.should.eql('paradiddle'); + b.work.should.eql('good flam'); + }, + + 'cached _ids': function () { + var db = start(); + var Movie = db.model('Movie'); + db.close(); + var m = new Movie; + + should.equal(m.id, m.__id); + var old = m.id; + m._id = new mongoose.Types.ObjectId; + should.equal(m.id, m.__id); + should.strictEqual(true, old !== m.__id); + + var m2= new Movie; + delete m2._doc._id; + m2.init({ _id: new mongoose.Types.ObjectId }); + should.equal(m2.id, m2.__id); + should.strictEqual(true, m.__id !== m2.__id); + should.strictEqual(true, m.id !== m2.id); + should.strictEqual(true, m.__id !== m2.__id); + } + + /* + 'Subdocument#remove': function (beforeExit) { + var db = start(); + var Movie = db.model('Movie'); + + var super8 = new Movie({ title: 'Super 8' }); + + var id1 = '4e3d5fc7da5d7eb635063c96'; + var id2 = '4e3d5fc7da5d7eb635063c97'; + var id3 = '4e3d5fc7da5d7eb635063c98'; + var id4 = '4e3d5fc7da5d7eb635063c99'; + + super8.ratings.push({ stars: 9, _id: id1 }); + super8.ratings.push({ stars: 8, _id: id2 }); + super8.ratings.push({ stars: 7, _id: id3 }); + super8.ratings.push({ stars: 6, _id: id4 }); + console.error('pre save', super8); + + super8.save(function (err) { + should.strictEqual(err, null); + + super8.title.should.eql('Super 8'); + super8.ratings.id(id1).stars.valueOf().should.eql(9); + super8.ratings.id(id2).stars.valueOf().should.eql(8); + super8.ratings.id(id3).stars.valueOf().should.eql(7); + super8.ratings.id(id4).stars.valueOf().should.eql(6); + + super8.ratings.id(id1).stars = 5; + super8.ratings.id(id2).remove(); + super8.ratings.id(id3).stars = 4; + super8.ratings.id(id4).stars = 3; + + super8.save(function (err) { + should.strictEqual(err, null); + + Movie.findById(super8._id, function (err, movie) { + should.strictEqual(err, null); + + movie.title.should.eql('Super 8'); + movie.ratings.length.should.equal(3); + movie.ratings.id(id1).stars.valueOf().should.eql(5); + movie.ratings.id(id3).stars.valueOf().should.eql(4); + movie.ratings.id(id4).stars.valueOf().should.eql(3); + + console.error('after save + findbyId', movie); + + movie.ratings.id(id1).stars = 2; + movie.ratings.id(id3).remove(); + movie.ratings.id(id4).stars = 1; + + console.error('after modified', movie); + + movie.save(function (err) { + should.strictEqual(err, null); + + Movie.findById(super8._id, function (err, movie) { + db.close(); + should.strictEqual(err, null); + movie.ratings.length.should.equal(2); + movie.ratings.id(id1).stars.valueOf().should.eql(2); + movie.ratings.id(id4).stars.valueOf().should.eql(1); + console.error('after resave + findById', movie); + }); + }); + }); + }); + }); + + beforeExit(function () { + var db = start(); + Movie.remove({}, function (err) { + db.close(); + }); + }); + } + */ + +}; diff --git a/node_modules/mongoose/test/types.documentarray.test.js b/node_modules/mongoose/test/types.documentarray.test.js new file mode 100644 index 0000000..ff8b381 --- /dev/null +++ b/node_modules/mongoose/test/types.documentarray.test.js @@ -0,0 +1,132 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('./common').mongoose + , MongooseArray = mongoose.Types.Array + , MongooseDocumentArray = mongoose.Types.DocumentArray + , EmbeddedDocument = require('../lib/types/embedded') + , DocumentArray = require('../lib/types/documentarray') + , Schema = mongoose.Schema + +/** + * Setup. + */ + +function TestDoc (schema) { + var Subdocument = function () { + EmbeddedDocument.call(this, {}, new DocumentArray); + }; + + /** + * Inherits from EmbeddedDocument. + */ + + Subdocument.prototype.__proto__ = EmbeddedDocument.prototype; + + /** + * Set schema. + */ + + var SubSchema = new Schema({ + title: { type: String } + }); + + Subdocument.prototype.schema = schema || SubSchema; + + return Subdocument; +} + +/** + * Test. + */ + +module.exports = { + + 'test that a mongoose array behaves and quacks like an array': function(){ + var a = new MongooseDocumentArray(); + + a.should.be.an.instanceof(Array); + a.should.be.an.instanceof(MongooseArray); + a.should.be.an.instanceof(MongooseDocumentArray); + Array.isArray(a).should.be.true; + a._atomics.constructor.name.should.equal('Object'); + 'object'.should.eql(typeof a); + + var b = new MongooseArray([1,2,3,4]); + 'object'.should.eql(typeof b); + Object.keys(b.toObject()).length.should.equal(4); + }, + + '#id': function () { + var Subdocument = TestDoc(); + + var sub1 = new Subdocument(); + sub1.title = 'Hello again to all my friends'; + var id = sub1.id; + + var a = new MongooseDocumentArray([sub1]); + a.id(id).title.should.equal('Hello again to all my friends'); + a.id(sub1._id).title.should.equal('Hello again to all my friends'); + + // test with custom string _id + var Custom = new Schema({ + title: { type: String } + , _id: { type: String, required: true } + }); + + var Subdocument = TestDoc(Custom); + + var sub2 = new Subdocument(); + sub2.title = 'together we can play some rock-n-roll'; + sub2._id = 'a25'; + var id2 = sub2.id; + + var a = new MongooseDocumentArray([sub2]); + a.id(id2).title.should.equal('together we can play some rock-n-roll'); + a.id(sub2._id).title.should.equal('together we can play some rock-n-roll'); + + // test with custom number _id + var CustNumber = new Schema({ + title: { type: String } + , _id: { type: Number, required: true } + }); + + var Subdocument = TestDoc(CustNumber); + + var sub3 = new Subdocument(); + sub3.title = 'rock-n-roll'; + sub3._id = 1995; + var id3 = sub3.id; + + var a = new MongooseDocumentArray([sub3]); + a.id(id3).title.should.equal('rock-n-roll'); + a.id(sub3._id).title.should.equal('rock-n-roll'); + }, + + 'inspect works with bad data': function () { + var threw = false; + var a = new MongooseDocumentArray([null]); + try { + a.inspect(); + } catch (err) { + threw = true; + console.error(err.stack); + } + threw.should.be.false; + }, + + 'toObject works with bad data': function () { + var threw = false; + var a = new MongooseDocumentArray([null]); + try { + a.toObject(); + } catch (err) { + threw = true; + console.error(err.stack); + } + threw.should.be.false; + } + +}; diff --git a/node_modules/mongoose/test/types.number.test.js b/node_modules/mongoose/test/types.number.test.js new file mode 100644 index 0000000..68ebe5e --- /dev/null +++ b/node_modules/mongoose/test/types.number.test.js @@ -0,0 +1,37 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('./common').mongoose + , MongooseNumber = mongoose.Types.Number + , SchemaNumber = mongoose.Schema.Types.Number + , should = require('should') + +/** + * Test. + */ + +module.exports = { + + 'test that a mongoose number behaves and quacks like a number': function(){ + var a = new MongooseNumber(5); + + a.should.be.an.instanceof(Number); + a.should.be.an.instanceof(MongooseNumber); + a.toString().should.eql('5'); + + (a._atomics.constructor).should.eql(Object); + }, + + 'an empty string casts to null': function () { + var n = new SchemaNumber(); + should.strictEqual(n.cast(''), null); + }, + + 'a null number should castForQuery to null': function () { + var n = new SchemaNumber(); + should.strictEqual(n.castForQuery(null), null); + } + +}; diff --git a/node_modules/mongoose/test/utils.test.js b/node_modules/mongoose/test/utils.test.js new file mode 100644 index 0000000..847702f --- /dev/null +++ b/node_modules/mongoose/test/utils.test.js @@ -0,0 +1,178 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , mongoose = start.mongoose + , Schema = mongoose.Schema + , utils = require('../lib/utils') + , StateMachine = require('../lib/statemachine') + , ObjectId = require('../lib/types/objectid') + +/** + * Setup. + */ + +var ActiveRoster = StateMachine.ctor('require', 'init', 'modify'); + +/** + * Test. + */ + +module.exports = { + + 'should detect a path as required if it has been required': function () { + var ar = new ActiveRoster(); + ar.require('hello'); + ar.stateOf('hello').should.equal('require'); + }, + + 'should detect a path as inited if it has been inited': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.stateOf('hello').should.equal('init'); + }, + + 'should detect a path as modified': function () { + var ar = new ActiveRoster(); + ar.modify('hello'); + ar.stateOf('hello').should.equal('modify'); + }, + + 'should remove a path from an old state upon a state change': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.modify('hello'); + ar.states.init.should.not.have.property('hello'); + ar.states.modify.should.have.property('hello'); + }, + + 'forEach should be able to iterate through the paths belonging to one state': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.init('goodbye'); + ar.modify('world'); + ar.require('foo'); + ar.forEach('init', function (path) { + ['hello', 'goodbye'].should.contain(path); + }); + }, + + 'forEach should be able to iterate through the paths in the union of two or more states': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.init('goodbye'); + ar.modify('world'); + ar.require('foo'); + ar.forEach('modify', 'require', function (path) { + ['world', 'foo'].should.contain(path); + }); + }, + + 'forEach should iterate through all paths that have any state if given no state arguments': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.init('goodbye'); + ar.modify('world'); + ar.require('foo'); + ar.forEach(function (path) { + ['hello', 'goodbye', 'world', 'foo'].should.contain(path); + }); + }, + + 'should be able to detect if at least one path exists in a set of states': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.modify('world'); + ar.some('init').should.be.true; + ar.some('modify').should.be.true; + ar.some('require').should.be.false; + ar.some('init', 'modify').should.be.true; + ar.some('init', 'require').should.be.true; + ar.some('modify', 'require').should.be.true; + }, + + 'should be able to `map` over the set of paths in a given state': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.modify('world'); + ar.require('iAmTheWalrus'); + var suffixedPaths = ar.map('init', 'modify', function (path) { + return path + '-suffix'; + }); + suffixedPaths.should.eql(['hello-suffix', 'world-suffix']); + }, + + "should `map` over all states' paths if no states are specified in a `map` invocation": function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.modify('world'); + ar.require('iAmTheWalrus'); + var suffixedPaths = ar.map(function (path) { + return path + '-suffix'; + }); + suffixedPaths.should.eql(['iAmTheWalrus-suffix', 'hello-suffix', 'world-suffix']); + }, + + 'test utils.options': function () { + var o = { a: 1, b: 2, c: 3, 0: 'zero1' }; + var defaults = { b: 10, d: 20, 0: 'zero2' }; + var result = utils.options(defaults, o); + result.a.should.equal(1); + result.b.should.equal(2); + result.c.should.equal(3); + result.d.should.equal(20); + o.d.should.equal(result.d); + result['0'].should.equal('zero1'); + + var result2 = utils.options(defaults); + result2.b.should.equal(10); + result2.d.should.equal(20); + result2['0'].should.equal('zero2'); + + // same properties/vals + defaults.should.eql(result2); + + // same object + defaults.should.not.equal(result2); + }, + + 'test deepEquals on ObjectIds': function () { + var s = (new ObjectId).toString(); + + var a = new ObjectId(s) + , b = new ObjectId(s); + + utils.deepEqual(a, b).should.be.true; + utils.deepEqual(a, a).should.be.true; + utils.deepEqual(a, new ObjectId).should.be.false; + }, + + 'deepEquals on MongooseDocumentArray works': function () { + var db = start() + , A = new Schema({ a: String }) + , M = db.model('deepEqualsOnMongooseDocArray', new Schema({ + a1: [A] + , a2: [A] + })); + + db.close(); + + var m1 = new M({ + a1: [{a: 'Hi'}, {a: 'Bye'}] + }); + + m1.a2 = m1.a1; + utils.deepEqual(m1.a1, m1.a2).should.be.true; + + var m2 = new M; + m2.init(m1.toObject()); + + utils.deepEqual(m1.a1, m2.a1).should.be.true; + + m2.set(m1.toObject()); + utils.deepEqual(m1.a1, m2.a1).should.be.true; + } + +}; diff --git a/node_modules/mongoose/test/zzlast.test.js b/node_modules/mongoose/test/zzlast.test.js new file mode 100644 index 0000000..706b7f1 --- /dev/null +++ b/node_modules/mongoose/test/zzlast.test.js @@ -0,0 +1,11 @@ + +var should = require('should'); +var gleak = require('gleak')(); +gleak.whitelist.push('currentObjectStored', 'reg_exp'); // node-mongodb-native + +module.exports.globals = function (beforeExit) { + beforeExit(function () { + var leaks = gleak.detect(); + should.strictEqual(leaks.length, 0, 'detected global var leaks: ' + leaks.join(', ')); + }) +} diff --git a/node_modules/socket.io/.npmignore b/node_modules/socket.io/.npmignore new file mode 100644 index 0000000..39e9864 --- /dev/null +++ b/node_modules/socket.io/.npmignore @@ -0,0 +1,3 @@ +support +test +examples diff --git a/node_modules/socket.io/History.md b/node_modules/socket.io/History.md new file mode 100644 index 0000000..0c3b4d1 --- /dev/null +++ b/node_modules/socket.io/History.md @@ -0,0 +1,187 @@ + +0.8.7 / 2011-11-05 +================== + + * Fixed memory leaks in closed clients. + * Fixed memory leaks in namespaces. + * Fixed websocket handling for malformed requests from proxies. [einaros] + * Node 0.6 compatibility. [einaros] [3rd-Eden] + * Adapted tests and examples. + +0.8.6 / 2011-10-27 +================== + + * Added JSON decoding on jsonp-polling transport. + * Fixed README example. + * Major speed optimizations [3rd-Eden] [einaros] [visionmedia] + * Added decode/encode benchmarks [visionmedia] + * Added support for black-listing client sent events. + * Fixed logging options, closes #540 [3rd-Eden] + * Added vary header for gzip [3rd-Eden] + * Properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client + * Patched to properly shut down when a finishClose call is made during connection establishment + * Added support for socket.io version on url and far-future Expires [3rd-Eden] [getify] + * Began IE10 compatibility [einaros] [tbranyen] + * Misc WebSocket fixes [einaros] + * Added UTF8 to respone headers for htmlfile [3rd-Eden] + +0.8.5 / 2011-10-07 +================== + + * Added websocket draft HyBi-16 support. [einaros] + * Fixed websocket continuation bugs. [einaros] + * Fixed flashsocket transport name. + * Fixed websocket tests. + * Ensured `parser#decodePayload` doesn't choke. + * Added http referrer verification to manager verifyOrigin. + * Added access control for cross domain xhr handshakes [3rd-Eden] + * Added support for automatic generation of socket.io files [3rd-Eden] + * Added websocket binary support [einaros] + * Added gzip support for socket.io.js [3rd-Eden] + * Expose socket.transport [3rd-Eden] + * Updated client. + +0.8.4 / 2011-09-06 +================== + + * Client build + +0.8.3 / 2011-09-03 +================== + + * Fixed `\n` parsing for non-JSON packets (fixes #479). + * Fixed parsing of certain unicode characters (fixes #451). + * Fixed transport message packet logging. + * Fixed emission of `error` event resulting in an uncaught exception if unhandled (fixes #476). + * Fixed; allow for falsy values as the configuration value of `log level` (fixes #491). + * Fixed repository URI in `package.json`. Fixes #504. + * Added text/plain content-type to handshake responses [einaros] + * Improved single byte writes [einaros] + * Updated socket.io-flashsocket default port from 843 to 10843 [3rd-Eden] + * Updated client. + +0.8.2 / 2011-08-29 +================== + + * Updated client. + +0.8.1 / 2011-08-29 +================== + + * Fixed utf8 bug in send framing in websocket [einaros] + * Fixed typo in docs [Znarkus] + * Fixed bug in send framing for over 64kB of data in websocket [einaros] + * Corrected ping handling in websocket transport [einaros] + +0.8.0 / 2011-08-28 +================== + + * Updated to work with two-level websocket versioning. [einaros] + * Added hybi07 support. [einaros] + * Added hybi10 support. [einaros] + * Added http referrer verification to manager.js verifyOrigin. [einaors] + +0.7.11 / 2011-08-27 +=================== + + * Updated socket.io-client. + +0.7.10 / 2011-08-27 +=================== + + * Updated socket.io-client. + +0.7.9 / 2011-08-12 +================== + + * Updated socket.io-client. + * Make sure we only do garbage collection when the server we receive is actually run. + +0.7.8 / 2011-08-08 +================== + + * Changed; make sure sio#listen passes options to both HTTP server and socket.io manager. + * Added docs for sio#listen. + * Added options parameter support for Manager constructor. + * Added memory leaks tests and test-leaks Makefile task. + * Removed auto npm-linking from make test. + * Make sure that you can disable heartbeats. [3rd-Eden] + * Fixed rooms memory leak [3rd-Eden] + * Send response once we got all POST data, not immediately [Pita] + * Fixed onLeave behavior with missing clientsk [3rd-Eden] + * Prevent duplicate references in rooms. + * Added alias for `to` to `in` and `in` to `to`. + * Fixed roomClients definition. + * Removed dependency on redis for installation without npm [3rd-Eden] + * Expose path and querystring in handshakeData [3rd-Eden] + +0.7.7 / 2011-07-12 +================== + + * Fixed double dispatch handling with emit to closed clients. + * Added test for emitting to closed clients to prevent regression. + * Fixed race condition in redis test. + * Changed Transport#end instrumentation. + * Leveraged $emit instead of emit internally. + * Made tests faster. + * Fixed double disconnect events. + * Fixed disconnect logic + * Simplified remote events handling in Socket. + * Increased testcase timeout. + * Fixed unknown room emitting (GH-291). [3rd-Eden] + * Fixed `address` in handshakeData. [3rd-Eden] + * Removed transports definition in chat example. + * Fixed room cleanup + * Fixed; make sure the client is cleaned up after booting. + * Make sure to mark the client as non-open if the connection is closed. + * Removed unneeded `buffer` declarations. + * Fixed; make sure to clear socket handlers and subscriptions upon transport close. + +0.7.6 / 2011-06-30 +================== + + * Fixed general dispatching when a client has closed. + +0.7.5 / 2011-06-30 +================== + + * Fixed dispatching to clients that are disconnected. + +0.7.4 / 2011-06-30 +================== + + * Fixed; only clear handlers if they were set. [level09] + +0.7.3 / 2011-06-30 +================== + + * Exposed handshake data to clients. + * Refactored dispatcher interface. + * Changed; Moved id generation method into the manager. + * Added sub-namespace authorization. [3rd-Eden] + * Changed; normalized SocketNamespace local eventing [dvv] + * Changed; Use packet.reason or default to 'packet' [3rd-Eden] + * Changed console.error to console.log. + * Fixed; bind both servers at the same time do that the test never times out. + * Added 304 support. + * Removed `Transport#name` for abstract interface. + * Changed; lazily require http and https module only when needed. [3rd-Eden] + +0.7.2 / 2011-06-22 +================== + + * Make sure to write a packet (of type `noop`) when closing a poll. + This solves a problem with cross-domain requests being flagged as aborted and + reconnection being triggered. + * Added `noop` message type. + +0.7.1 / 2011-06-21 +================== + + * Fixed cross-domain XHR. + * Added CORS test to xhr-polling suite. + +0.7.0 / 2010-06-21 +================== + + * http://socket.io/announcement.html diff --git a/node_modules/socket.io/Makefile b/node_modules/socket.io/Makefile new file mode 100644 index 0000000..832cba8 --- /dev/null +++ b/node_modules/socket.io/Makefile @@ -0,0 +1,31 @@ + +ALL_TESTS = $(shell find test/ -name '*.test.js') +ALL_BENCH = $(shell find benchmarks -name '*.bench.js') + +run-tests: + @./node_modules/.bin/expresso \ + -t 3000 \ + -I support \ + --serial \ + $(TESTFLAGS) \ + $(TESTS) + +test: + @$(MAKE) NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests + +test-cov: + @TESTFLAGS=--cov $(MAKE) test + +test-leaks: + @ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc + +run-bench: + @node $(PROFILEFLAGS) benchmarks/runner.js + +bench: + @$(MAKE) BENCHMARKS="$(ALL_BENCH)" run-bench + +profile: + @PROFILEFLAGS='--prof --trace-opt --trace-bailout --trace-deopt' $(MAKE) bench + +.PHONY: test bench profile diff --git a/node_modules/socket.io/Readme.md b/node_modules/socket.io/Readme.md new file mode 100644 index 0000000..779ce57 --- /dev/null +++ b/node_modules/socket.io/Readme.md @@ -0,0 +1,343 @@ +# Socket.IO + +Socket.IO is a Node.JS project that makes WebSockets and realtime possible in +all browsers. It also enhances WebSockets by providing built-in multiplexing, +horizontal scalability, automatic JSON encoding/decoding, and more. + +## How to Install + + npm install socket.io + +## How to use + +First, require `socket.io`: + +```js +var io = require('socket.io'); +``` + +Next, attach it to a HTTP/HTTPS server. If you're using the fantastic `express` +web framework: + +```js +var app = express.createServer() + , io = io.listen(app); + +app.listen(80); + +io.sockets.on('connection', function (socket) { + socket.emit('news', { hello: 'world' }); + socket.on('my other event', function (data) { + console.log(data); + }); +}); +``` + +Finally, load it from the client side code: + +```html + + +``` + +For more thorough examples, look at the `examples/` directory. + +## Short recipes + +### Sending and receiving events. + +Socket.IO allows you to emit and receive custom events. +Besides `connect`, `message` and `disconnect`, you can emit custom events: + +```js +// note, io.listen() will create a http server for you +var io = require('socket.io').listen(80); + +io.sockets.on('connection', function (socket) { + io.sockets.emit('this', { will: 'be received by everyone' }); + + socket.on('private message', function (from, msg) { + console.log('I received a private message by ', from, ' saying ', msg); + }); + + socket.on('disconnect', function () { + io.sockets.emit('user disconnected'); + }); +}); +``` + +### Storing data associated to a client + +Sometimes it's necessary to store data associated with a client that's +necessary for the duration of the session. + +#### Server side + +```js +var io = require('socket.io').listen(80); + +io.sockets.on('connection', function (socket) { + socket.on('set nickname', function (name) { + socket.set('nickname', name, function () { socket.emit('ready'); }); + }); + + socket.on('msg', function () { + socket.get('nickname', function (err, name) { + console.log('Chat message by ', name); + }); + }); +}); +``` + +#### Client side + +```html + +``` + +### Restricting yourself to a namespace + +If you have control over all the messages and events emitted for a particular +application, using the default `/` namespace works. + +If you want to leverage 3rd-party code, or produce code to share with others, +socket.io provides a way of namespacing a `socket`. + +This has the benefit of `multiplexing` a single connection. Instead of +socket.io using two `WebSocket` connections, it'll use one. + +The following example defines a socket that listens on '/chat' and one for +'/news': + +#### Server side + +```js +var io = require('socket.io').listen(80); + +var chat = io + .of('/chat'); + .on('connection', function (socket) { + socket.emit('a message', { that: 'only', '/chat': 'will get' }); + chat.emit('a message', { everyone: 'in', '/chat': 'will get' }); + }); + +var news = io + .of('/news'); + .on('connection', function (socket) { + socket.emit('item', { news: 'item' }); + }); +``` + +#### Client side: + +```html + +``` + +### Sending volatile messages. + +Sometimes certain messages can be dropped. Let's say you have an app that +shows realtime tweets for the keyword `bieber`. + +If a certain client is not ready to receive messages (because of network slowness +or other issues, or because he's connected through long polling and is in the +middle of a request-response cycle), if he doesn't receive ALL the tweets related +to bieber your application won't suffer. + +In that case, you might want to send those messages as volatile messages. + +#### Server side + +```js +var io = require('socket.io').listen(80); + +io.sockets.on('connection', function (socket) { + var tweets = setInterval(function () { + getBieberTweet(function (tweet) { + socket.volatile.emit('bieber tweet', tweet); + }); + }, 100); + + socket.on('disconnect', function () { + clearInterval(tweets); + }); +}); +``` + +#### Client side + +In the client side, messages are received the same way whether they're volatile +or not. + +### Getting acknowledgements + +Sometimes, you might want to get a callback when the client confirmed the message +reception. + +To do this, simply pass a function as the last parameter of `.send` or `.emit`. +What's more, when you use `.emit`, the acknowledgement is done by you, which +means you can also pass data along: + +#### Server side + +```js +var io = require('socket.io').listen(80); + +io.sockets.on('connection', function (socket) { + socket.on('ferret', function (name, fn) { + fn('woot'); + }); +}); +``` + +#### Client side + +```html + +``` + +### Broadcasting messages + +To broadcast, simply add a `broadcast` flag to `emit` and `send` method calls. +Broadcasting means sending a message to everyone else except for the socket +that starts it. + +#### Server side + +```js +var io = require('socket.io').listen(80); + +io.sockets.on('connection', function (socket) { + socket.broadcast.emit('user connected'); + socket.broadcast.json.send({ a: 'message' }); +}); +``` + +### Rooms + +Sometimes you want to put certain sockets in the same room, so that it's easy +to broadcast to all of them together. + +Think of this as built-in channels for sockets. Sockets `join` and `leave` +rooms in each socket. + +#### Server side + +```js +var io = require('socket.io').listen(80); + +io.sockets.on('connection', function (socket) { + socket.join('justin bieber fans'); + socket.broadcast.to('justin bieber fans').emit('new fan'); + io.sockets.in('rammstein fans').emit('new non-fan'); +}); +``` + +### Using it just as a cross-browser WebSocket + +If you just want the WebSocket semantics, you can do that too. +Simply leverage `send` and listen on the `message` event: + +#### Server side + +```js +var io = require('socket.io').listen(80); + +io.sockets.on('connection', function (socket) { + socket.on('message', function () { }); + socket.on('disconnect', function () { }); +}); +``` + +#### Client side + +```html + +``` + +### Changing configuration + +Configuration in socket.io is TJ-style: + +#### Server side + +```js +var io = require('socket.io').listen(80); + +io.configure(function () { + io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']); +}); + +io.configure('development', function () { + io.set('transports', ['websocket', 'xhr-polling']); + io.enable('log'); +}); +``` + +## License + +(The MIT License) + +Copyright (c) 2011 Guillermo Rauch <guillermo@learnboost.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/socket.io/benchmarks/decode.bench.js b/node_modules/socket.io/benchmarks/decode.bench.js new file mode 100644 index 0000000..4855d80 --- /dev/null +++ b/node_modules/socket.io/benchmarks/decode.bench.js @@ -0,0 +1,64 @@ + +/** + * Module dependencies. + */ + +var benchmark = require('benchmark') + , colors = require('colors') + , io = require('../') + , parser = io.parser + , suite = new benchmark.Suite('Decode packet'); + +suite.add('string', function () { + parser.decodePacket('4:::"2"'); +}); + +suite.add('event', function () { + parser.decodePacket('5:::{"name":"woot"}'); +}); + +suite.add('event+ack', function () { + parser.decodePacket('5:1+::{"name":"tobi"}'); +}); + +suite.add('event+data', function () { + parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}'); +}); + +suite.add('heartbeat', function () { + parser.decodePacket('2:::'); +}); + +suite.add('error', function () { + parser.decodePacket('7:::2+0'); +}); + +var payload = parser.encodePayload([ + parser.encodePacket({ type: 'message', data: '5', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: '53d', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' }) +]); + +suite.add('payload', function () { + parser.decodePayload(payload); +}); + +suite.on('cycle', function (bench, details) { + console.log('\n' + suite.name.grey, details.name.white.bold); + console.log([ + details.hz.toFixed(2).cyan + ' ops/sec'.grey + , details.count.toString().white + ' times executed'.grey + , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey + , + ].join(', '.grey)); +}); + +if (!module.parent) { + suite.run(); +} else { + module.exports = suite; +} diff --git a/node_modules/socket.io/benchmarks/encode.bench.js b/node_modules/socket.io/benchmarks/encode.bench.js new file mode 100644 index 0000000..5037702 --- /dev/null +++ b/node_modules/socket.io/benchmarks/encode.bench.js @@ -0,0 +1,90 @@ + +/** + * Module dependencies. + */ + +var benchmark = require('benchmark') + , colors = require('colors') + , io = require('../') + , parser = io.parser + , suite = new benchmark.Suite('Encode packet'); + +suite.add('string', function () { + parser.encodePacket({ + type: 'json' + , endpoint: '' + , data: '2' + }); +}); + +suite.add('event', function () { + parser.encodePacket({ + type: 'event' + , name: 'woot' + , endpoint: '' + , args: [] + }); +}); + +suite.add('event+ack', function () { + parser.encodePacket({ + type: 'json' + , id: 1 + , ack: 'data' + , endpoint: '' + , data: { a: 'b' } + }); +}); + +suite.add('event+data', function () { + parser.encodePacket({ + type: 'event' + , name: 'edwald' + , endpoint: '' + , args: [{a: 'b'}, 2, '3'] + }); +}); + +suite.add('heartbeat', function () { + parser.encodePacket({ + type: 'heartbeat' + , endpoint: '' + }) +}); + +suite.add('error', function () { + parser.encodePacket({ + type: 'error' + , reason: 'unauthorized' + , advice: 'reconnect' + , endpoint: '' + }) +}) + +suite.add('payload', function () { + parser.encodePayload([ + parser.encodePacket({ type: 'message', data: '5', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: '53d', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' }) + , parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' }) + ]); +}); + +suite.on('cycle', function (bench, details) { + console.log('\n' + suite.name.grey, details.name.white.bold); + console.log([ + details.hz.toFixed(2).cyan + ' ops/sec'.grey + , details.count.toString().white + ' times executed'.grey + , 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey + , + ].join(', '.grey)); +}); + +if (!module.parent) { + suite.run(); +} else { + module.exports = suite; +} diff --git a/node_modules/socket.io/benchmarks/runner.js b/node_modules/socket.io/benchmarks/runner.js new file mode 100644 index 0000000..81e55ca --- /dev/null +++ b/node_modules/socket.io/benchmarks/runner.js @@ -0,0 +1,55 @@ +/** + * Benchmark runner dependencies + */ + +var colors = require('colors') + , path = require('path'); + +/** + * Find all the benchmarks + */ + +var benchmarks_files = process.env.BENCHMARKS.split(' ') + , all = [].concat(benchmarks_files) + , first = all.shift() + , benchmarks = {}; + +// find the benchmarks and load them all in our obj +benchmarks_files.forEach(function (file) { + benchmarks[file] = require(path.join(__dirname, '..', file)); +}); + +// setup the complete listeners +benchmarks_files.forEach(function (file) { + var benchmark = benchmarks[file] + , next_file = all.shift() + , next = benchmarks[next_file]; + + /** + * Generate a oncomplete function for the tests, either we are done or we + * have more benchmarks to process. + */ + + function complete () { + if (!next) { + console.log( + '\n\nBenchmark completed in'.grey + , (Date.now() - start).toString().green + ' ms'.grey + ); + } else { + console.log('\nStarting benchmark '.grey + next_file.yellow); + next.run(); + } + } + + // attach the listener + benchmark.on('complete', complete); +}); + +/** + * Start the benchmark + */ + +var start = Date.now(); +console.log('Starting benchmark '.grey + first.yellow); +benchmarks[first].run(); diff --git a/node_modules/socket.io/examples/chat/app.js b/node_modules/socket.io/examples/chat/app.js new file mode 100644 index 0000000..bdfad2a --- /dev/null +++ b/node_modules/socket.io/examples/chat/app.js @@ -0,0 +1,80 @@ +/** + * Module dependencies. + */ + +var express = require('express') + , stylus = require('stylus') + , nib = require('nib') + , sio = require('../../lib/socket.io'); + +/** + * App. + */ + +var app = express.createServer(); + +/** + * App configuration. + */ + +app.configure(function () { + app.use(stylus.middleware({ src: __dirname + '/public', compile: compile })); + app.use(express.static(__dirname + '/public')); + app.set('views', __dirname); + app.set('view engine', 'jade'); + + function compile (str, path) { + return stylus(str) + .set('filename', path) + .use(nib()); + }; +}); + +/** + * App routes. + */ + +app.get('/', function (req, res) { + res.render('index', { layout: false }); +}); + +/** + * App listen. + */ + +app.listen(3000, function () { + var addr = app.address(); + console.log(' app listening on http://' + addr.address + ':' + addr.port); +}); + +/** + * Socket.IO server (single process only) + */ + +var io = sio.listen(app) + , nicknames = {}; + +io.sockets.on('connection', function (socket) { + socket.on('user message', function (msg) { + socket.broadcast.emit('user message', socket.nickname, msg); + }); + + socket.on('nickname', function (nick, fn) { + if (nicknames[nick]) { + fn(true); + } else { + fn(false); + nicknames[nick] = socket.nickname = nick; + socket.broadcast.emit('announcement', nick + ' connected'); + io.sockets.emit('nicknames', nicknames); + } + }); + + socket.on('disconnect', function () { + if (!socket.nickname) return; + + delete nicknames[socket.nickname]; + socket.broadcast.emit('announcement', socket.nickname + ' disconnected'); + socket.broadcast.emit('nicknames', nicknames); + }); +}); diff --git a/node_modules/socket.io/examples/chat/index.jade b/node_modules/socket.io/examples/chat/index.jade new file mode 100644 index 0000000..0633d8b --- /dev/null +++ b/node_modules/socket.io/examples/chat/index.jade @@ -0,0 +1,83 @@ +doctype 5 +html + head + link(href='/stylesheets/style.css', rel='stylesheet') + script(src='http://code.jquery.com/jquery-1.6.1.min.js') + script(src='/socket.io/socket.io.js') + script + // socket.io specific code + var socket = io.connect(); + + socket.on('connect', function () { + $('#chat').addClass('connected'); + }); + + socket.on('announcement', function (msg) { + $('#lines').append($('

    ').append($('').text(msg))); + }); + + socket.on('nicknames', function (nicknames) { + $('#nicknames').empty().append($('Online: ')); + for (var i in nicknames) { + $('#nicknames').append($('').text(nicknames[i])); + } + }); + + socket.on('user message', message); + socket.on('reconnect', function () { + $('#lines').remove(); + message('System', 'Reconnected to the server'); + }); + + socket.on('reconnecting', function () { + message('System', 'Attempting to re-connect to the server'); + }); + + socket.on('error', function (e) { + message('System', e ? e : 'A unknown error occurred'); + }); + + function message (from, msg) { + $('#lines').append($('

    ').append($('').text(from), msg)); + } + + // dom manipulation + $(function () { + $('#set-nickname').submit(function (ev) { + socket.emit('nickname', $('#nick').val(), function (set) { + if (!set) { + clear(); + return $('#chat').addClass('nickname-set'); + } + $('#nickname-err').css('visibility', 'visible'); + }); + return false; + }); + + $('#send-message').submit(function () { + message('me', $('#message').val()); + socket.emit('user message', $('#message').val()); + clear(); + $('#lines').get(0).scrollTop = 10000000; + return false; + }); + + function clear () { + $('#message').val('').focus(); + }; + }); + body + #chat + #nickname + form.wrap#set-nickname + p Please type in your nickname and press enter. + input#nick + p#nickname-err Nickname already in use + #connecting + .wrap Connecting to socket.io server + #messages + #nicknames + #lines + form#send-message + input#message + button Send diff --git a/node_modules/socket.io/examples/chat/package.json b/node_modules/socket.io/examples/chat/package.json new file mode 100644 index 0000000..8b3338a --- /dev/null +++ b/node_modules/socket.io/examples/chat/package.json @@ -0,0 +1,11 @@ +{ + "name": "chat.io" + , "description": "example chat application with socket.io" + , "version": "0.0.1" + , "dependencies": { + "express": "2.5.0" + , "jade": "0.16.4" + , "stylus": "0.19.0" + , "nib": "0.2.0" + } +} diff --git a/node_modules/socket.io/examples/chat/public/stylesheets/mixins.styl b/node_modules/socket.io/examples/chat/public/stylesheets/mixins.styl new file mode 100644 index 0000000..fb5644c --- /dev/null +++ b/node_modules/socket.io/examples/chat/public/stylesheets/mixins.styl @@ -0,0 +1,96 @@ +border-radius(n) + -webkit-border-radius n + -moz-border-radius n + border-radius n + +// replace str with val + +replace(expr, str, val) + expr = clone(expr) + for e, i in expr + if str == e + expr[i] = val + expr + +// normalize gradient point (webkit) + +grad-point(pos) + if length(pos) == 1 + return left pos if pos in (top bottom) + return pos top if pos in (left right) + else if pos[0] in (top bottom) + pos[1] pos[0] + else + pos + +// implicit color stop position + +pos-in-stops(i, stops) + len = length(stops) + if len - 1 == i + 100% + else if i + unit(i / len * 100, '%') + else + 0% + +// normalize color stops +// - (color pos) -> (pos color) +// - (color) -> (implied-pos color) + +normalize-stops(stops) + stops = clone(stops) + for stop, i in stops + if length(stop) == 1 + color = stop[0] + stop[0] = pos-in-stops(i, stops) + stop[1] = color + else if typeof(stop[1]) == 'unit' + pos = stop[1] + stop[1] = stop[0] + stop[0] = pos + stops + +// join color stops with the given translation function + +join-stops(stops, translate) + str = '' + len = length(stops) + for stop, i in stops + str += ', ' if i + pos = stop[0] + color = stop[1] + str += translate(color, pos) + unquote(str) + +// webkit translation function + +webkit-stop(color, pos) + s('color-stop(%d, %s)', pos / 100, color) + +// mozilla translation function + +moz-stop(color, pos) + s('%s %s', color, pos) + +// create a linear gradient with the given start +// position, followed by color stops + +linear-gradient(start, stops...) + error('color stops required') unless length(stops) + prop = current-property[0] + val = current-property[1] + stops = normalize-stops(stops) + + // webkit + end = grad-point(opposite-position(start)) + webkit = s('-webkit-gradient(linear, %s, %s, %s)', grad-point(start), end, join-stops(stops, webkit-stop)) + add-property(prop, replace(val, '__CALL__', webkit)) + + // moz + stops = join-stops(stops, moz-stop) + moz = s('-moz-linear-gradient(%s, %s)', start, stops) + add-property(prop, replace(val, '__CALL__', moz)) + + // literal + s('linear-gradient(%s, %s)', start, stops) diff --git a/node_modules/socket.io/examples/chat/public/stylesheets/style.css b/node_modules/socket.io/examples/chat/public/stylesheets/style.css new file mode 100644 index 0000000..42cf98f --- /dev/null +++ b/node_modules/socket.io/examples/chat/public/stylesheets/style.css @@ -0,0 +1,188 @@ +#chat, +#nickname, +#messages { + width: 600px; +} +#chat { + position: relative; + border: 1px solid #ccc; +} +#nickname, +#connecting { + position: absolute; + height: 410px; + z-index: 100; + left: 0; + top: 0; + background: #fff; + text-align: center; + width: 600px; + font: 15px Georgia; + color: #666; + display: block; +} +#nickname .wrap, +#connecting .wrap { + padding-top: 150px; +} +#nickname input { + border: 1px solid #ccc; + padding: 10px; +} +#nickname input:focus { + border-color: #999; + outline: 0; +} +#nickname #nickname-err { + color: #8b0000; + font-size: 12px; + visibility: hidden; +} +.connected #connecting { + display: none; +} +.nickname-set #nickname { + display: none; +} +#messages { + height: 380px; + background: #eee; +} +#messages em { + text-shadow: 0 1px 0 #fff; + color: #999; +} +#messages p { + padding: 0; + margin: 0; + font: 12px Helvetica, Arial; + padding: 5px 10px; +} +#messages p b { + display: inline-block; + padding-right: 10px; +} +#messages p:nth-child(even) { + background: #fafafa; +} +#messages #nicknames { + background: #ccc; + padding: 2px 4px 4px; + font: 11px Helvetica; +} +#messages #nicknames span { + color: #000; +} +#messages #nicknames b { + display: inline-block; + color: #fff; + background: #999; + padding: 3px 6px; + margin-right: 5px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + text-shadow: 0 1px 0 #666; +} +#messages #lines { + height: 355px; + overflow: auto; + overflow-x: hidden; + overflow-y: auto; +} +#messages #lines::-webkit-scrollbar { + width: 6px; + height: 6px; +} +#messages #lines::-webkit-scrollbar-button:start:decrement, +#messages #lines ::-webkit-scrollbar-button:end:increment { + display: block; + height: 10px; +} +#messages #lines::-webkit-scrollbar-button:vertical:increment { + background-color: #fff; +} +#messages #lines::-webkit-scrollbar-track-piece { + background-color: #fff; + -webkit-border-radius: 3px; +} +#messages #lines::-webkit-scrollbar-thumb:vertical { + height: 50px; + background-color: #ccc; + -webkit-border-radius: 3px; +} +#messages #lines::-webkit-scrollbar-thumb:horizontal { + width: 50px; + background-color: #fff; + -webkit-border-radius: 3px; +} +#send-message { + background: #fff; + position: relative; +} +#send-message input { + border: none; + height: 30px; + padding: 0 10px; + line-height: 30px; + vertical-align: middle; + width: 580px; +} +#send-message input:focus { + outline: 0; +} +#send-message button { + position: absolute; + top: 5px; + right: 5px; +} +button { + margin: 0; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + display: inline-block; + text-decoration: none; + background: #43a1f7; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #377ad0)); + background: -webkit-linear-gradient(top, #43a1f7 0%, #377ad0 100%); + background: -moz-linear-gradient(top, #43a1f7 0%, #377ad0 100%); + background: linear-gradient(top, #43a1f7 0%, #377ad0 100%); + border: 1px solid #2e70c4; + -webkit-border-radius: 16px; + -moz-border-radius: 16px; + border-radius: 16px; + color: #fff; + font-family: "lucida grande", sans-serif; + font-size: 11px; + font-weight: normal; + line-height: 1; + padding: 3px 10px 5px 10px; + text-align: center; + text-shadow: 0 -1px 1px #2d6dc0; +} +button:hover, +button.hover { + background: darker; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #2e70c4)); + background: -webkit-linear-gradient(top, #43a1f7 0%, #2e70c4 100%); + background: -moz-linear-gradient(top, #43a1f7 0%, #2e70c4 100%); + background: linear-gradient(top, #43a1f7 0%, #2e70c4 100%); + border: 1px solid #2e70c4; + cursor: pointer; + text-shadow: 0 -1px 1px #2c6bbb; +} +button:active, +button.active { + background: #2e70c4; + border: 1px solid #2e70c4; + border-bottom: 1px solid #2861aa; + text-shadow: 0 -1px 1px #2b67b5; +} +button:focus, +button.focus { + outline: none; + -webkit-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0; + -moz-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0; + box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0; +} diff --git a/node_modules/socket.io/examples/chat/public/stylesheets/style.styl b/node_modules/socket.io/examples/chat/public/stylesheets/style.styl new file mode 100644 index 0000000..6289c94 --- /dev/null +++ b/node_modules/socket.io/examples/chat/public/stylesheets/style.styl @@ -0,0 +1,118 @@ +@import 'nib' + +#chat, #nickname, #messages + width 600px + +#chat + position relative + border 1px solid #ccc + +#nickname, #connecting + position absolute + height 410px + z-index 100 + left 0 + top 0 + background #fff + text-align center + width 600px + font 15px Georgia + color #666 + display block + .wrap + padding-top 150px + +#nickname + input + border 1px solid #ccc + padding 10px + &:focus + border-color #999 + outline 0 + #nickname-err + color darkred + font-size 12px + visibility hidden + +.connected + #connecting + display none + +.nickname-set + #nickname + display none + +#messages + height 380px + background #eee + em + text-shadow 0 1px 0 #fff + color #999 + p + padding 0 + margin 0 + font 12px Helvetica, Arial + padding 5px 10px + b + display inline-block + padding-right 10px + p:nth-child(even) + background #fafafa + #nicknames + background #ccc + padding 2px 4px 4px + font 11px Helvetica + span + color black + b + display inline-block + color #fff + background #999 + padding 3px 6px + margin-right 5px + border-radius 10px + text-shadow 0 1px 0 #666 + #lines + height 355px + overflow auto + overflow-x hidden + overflow-y auto + &::-webkit-scrollbar + width 6px + height 6px + &::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment + display block + height 10px + &::-webkit-scrollbar-button:vertical:increment + background-color #fff + &::-webkit-scrollbar-track-piece + background-color #fff + -webkit-border-radius 3px + &::-webkit-scrollbar-thumb:vertical + height 50px + background-color #ccc + -webkit-border-radius 3px + &::-webkit-scrollbar-thumb:horizontal + width 50px + background-color #fff + -webkit-border-radius 3px + +#send-message + background #fff + position relative + input + border none + height 30px + padding 0 10px + line-height 30px + vertical-align middle + width 580px + &:focus + outline 0 + button + position absolute + top 5px + right 5px + +button + download-button() diff --git a/node_modules/socket.io/examples/irc-output/app.js b/node_modules/socket.io/examples/irc-output/app.js new file mode 100644 index 0000000..784886a --- /dev/null +++ b/node_modules/socket.io/examples/irc-output/app.js @@ -0,0 +1,74 @@ +/** + * Module dependencies. + */ + +var express = require('express') + , stylus = require('stylus') + , nib = require('nib') + , sio = require('../../lib/socket.io') + , irc = require('./irc'); + +/** + * App. + */ + +var app = express.createServer(); + +/** + * App configuration. + */ + +app.configure(function () { + app.use(stylus.middleware({ src: __dirname + '/public', compile: compile })) + app.use(express.static(__dirname + '/public')); + app.set('views', __dirname); + app.set('view engine', 'jade'); + + function compile (str, path) { + return stylus(str) + .set('filename', path) + .use(nib()); + }; +}); + +/** + * App routes. + */ + +app.get('/', function (req, res) { + res.render('index', { layout: false }); +}); + +/** + * App listen. + */ + +app.listen(3000, function () { + var addr = app.address(); + console.log(' app listening on http://' + addr.address + ':' + addr.port); +}); + +/** + * Socket.IO server + */ + +var io = sio.listen(app) + +/** + * Connect to IRC. + */ + +var client = new irc.Client('irc.freenode.net', 6667); +client.connect('socketio\\test\\' + String(Math.random()).substr(-3)); +client.on('001', function () { + this.send('JOIN', '#node.js'); +}); +client.on('PART', function (prefix) { + io.sockets.emit('announcement', irc.user(prefix) + ' left the channel'); +}); +client.on('JOIN', function (prefix) { + io.sockets.emit('announcement', irc.user(prefix) + ' joined the channel'); +}); +client.on('PRIVMSG', function (prefix, channel, text) { + io.sockets.emit('irc message', irc.user(prefix), text); +}); diff --git a/node_modules/socket.io/examples/irc-output/index.jade b/node_modules/socket.io/examples/irc-output/index.jade new file mode 100644 index 0000000..5468632 --- /dev/null +++ b/node_modules/socket.io/examples/irc-output/index.jade @@ -0,0 +1,28 @@ +doctype 5 +html + head + link(href='/stylesheets/style.css', rel='stylesheet') + script(src='http://code.jquery.com/jquery-1.6.1.min.js') + script(src='/socket.io/socket.io.js') + script + var socket = io.connect(); + + socket.on('connect', function () { + $('#irc').addClass('connected'); + }); + + socket.on('announcement', function (msg) { + $('#messages').append($('

    ').append($('').text(msg))); + $('#messages').get(0).scrollTop = 10000000; + }); + + socket.on('irc message', function (user, msg) { + $('#messages').append($('

    ').append($('').text(user), msg)); + $('#messages').get(0).scrollTop = 10000000; + }); + body + h2 Node.JS IRC + #irc + #connecting + .wrap Connecting to socket.io server + #messages diff --git a/node_modules/socket.io/examples/irc-output/irc.js b/node_modules/socket.io/examples/irc-output/irc.js new file mode 100644 index 0000000..cc679d5 --- /dev/null +++ b/node_modules/socket.io/examples/irc-output/irc.js @@ -0,0 +1,164 @@ +/** + * From https://github.com/felixge/nodelog/ + */ + +var sys = require('util'); +var tcp = require('net'); +var irc = exports; + +function bind(fn, scope) { + var bindArgs = Array.prototype.slice.call(arguments); + bindArgs.shift(); + bindArgs.shift(); + + return function() { + var args = Array.prototype.slice.call(arguments); + fn.apply(scope, bindArgs.concat(args)); + }; +} + +function each(set, iterator) { + for (var i = 0; i < set.length; i++) { + var r = iterator(set[i], i); + if (r === false) { + return; + } + } +} + +var Client = irc.Client = function(host, port) { + this.host = host || 'localhost'; + this.port = port || 6667; + + this.connection = null; + this.buffer = ''; + this.encoding = 'utf8'; + this.timeout = 10 * 60 * 60 * 1000; + + this.nick = null; + this.user = null; + this.real = null; +} +sys.inherits(Client, process.EventEmitter); + +Client.prototype.connect = function(nick, user, real) { + var connection = tcp.createConnection(this.port, this.host); + connection.setEncoding(this.encoding); + connection.setTimeout(this.timeout); + connection.addListener('connect', bind(this.onConnect, this)); + connection.addListener('data', bind(this.onReceive, this)); + connection.addListener('end', bind(this.onEof, this)); + connection.addListener('timeout', bind(this.onTimeout, this)); + connection.addListener('close', bind(this.onClose, this)); + + this.nick = nick; + this.user = user || 'guest'; + this.real = real || 'Guest'; + + this.connection = connection; +}; + +Client.prototype.disconnect = function(why) { + if (this.connection.readyState !== 'closed') { + this.connection.close(); + sys.puts('disconnected (reason: '+why+')'); + this.emit('DISCONNECT', why); + } +}; + +Client.prototype.send = function(arg1) { + if (this.connection.readyState !== 'open') { + return this.disconnect('cannot send with readyState: '+this.connection.readyState); + } + + var message = []; + for (var i = 0; i< arguments.length; i++) { + if (arguments[i]) { + message.push(arguments[i]); + } + } + message = message.join(' '); + + sys.puts('> '+message); + message = message + "\r\n"; + this.connection.write(message, this.encoding); +}; + +Client.prototype.parse = function(message) { + var match = message.match(/(?:(:[^\s]+) )?([^\s]+) (.+)/); + var parsed = { + prefix: match[1], + command: match[2] + }; + + var params = match[3].match(/(.*?) ?:(.*)/); + if (params) { + // Params before : + params[1] = (params[1]) + ? params[1].split(' ') + : []; + // Rest after : + params[2] = params[2] + ? [params[2]] + : []; + + params = params[1].concat(params[2]); + } else { + params = match[3].split(' '); + } + + parsed.params = params; + return parsed; +}; + +Client.prototype.onConnect = function() { + this.send('NICK', this.nick); + this.send('USER', this.user, '0', '*', ':'+this.real); +}; + +Client.prototype.onReceive = function(chunk) { + this.buffer = this.buffer + chunk; + + while (this.buffer) { + var offset = this.buffer.indexOf("\r\n"); + if (offset < 0) { + return; + } + + var message = this.buffer.substr(0, offset); + this.buffer = this.buffer.substr(offset + 2); + sys.puts('< '+message); + + message = this.parse(message); + + this.emit.apply(this, [message.command, message.prefix].concat(message.params)); + + if (message !== false) { + this.onMessage(message); + } + } +}; + +Client.prototype.onMessage = function(message) { + switch (message.command) { + case 'PING': + this.send('PONG', ':'+message.params[0]); + break; + } +}; + +Client.prototype.onEof = function() { + this.disconnect('eof'); +}; + +Client.prototype.onTimeout = function() { + this.disconnect('timeout'); +}; + +Client.prototype.onClose = function() { + this.disconnect('close'); +}; + +exports.user = function(prefix) { + return prefix.match(/:([^!]+)!/)[1] +}; diff --git a/node_modules/socket.io/examples/irc-output/package.json b/node_modules/socket.io/examples/irc-output/package.json new file mode 100644 index 0000000..0987651 --- /dev/null +++ b/node_modules/socket.io/examples/irc-output/package.json @@ -0,0 +1,10 @@ +{ + "name": "socket.io-irc" + , "version": "0.0.1" + , "dependencies": { + "express": "2.5.0" + , "jade": "0.16.4" + , "stylus": "0.19.0" + , "nib": "0.2.0" + } +} diff --git a/node_modules/socket.io/examples/irc-output/public/stylesheets/style.styl b/node_modules/socket.io/examples/irc-output/public/stylesheets/style.styl new file mode 100644 index 0000000..2713512 --- /dev/null +++ b/node_modules/socket.io/examples/irc-output/public/stylesheets/style.styl @@ -0,0 +1,69 @@ +@import 'nib' + +h2 + font bold 18px Helvetica Neue, Arial + +#irc, #messages + width 600px + +#irc + position relative + border 1px solid #ccc + +#connecting + position absolute + height 410px + z-index 100 + left 0 + top 0 + background #fff + text-align center + width 600px + font 15px Georgia + color #666 + display block + .wrap + padding-top 150px + +.connected + #connecting + display none + +#messages + height 380px + background #eee + overflow auto + overflow-x hidden + overflow-y auto + &::-webkit-scrollbar + width 6px + height 6px + &::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment + display block + height 10px + &::-webkit-scrollbar-button:vertical:increment + background-color #fff + &::-webkit-scrollbar-track-piece + background-color #fff + -webkit-border-radius 3px + &::-webkit-scrollbar-thumb:vertical + height 50px + background-color #ccc + -webkit-border-radius 3px + &::-webkit-scrollbar-thumb:horizontal + width 50px + background-color #fff + -webkit-border-radius 3px + em + text-shadow 0 1px 0 #fff + color #999 + p + padding 0 + margin 0 + font 12px Helvetica, Arial + padding 5px 10px + b + display inline-block + padding-right 10px + p:nth-child(even) + background #fafafa diff --git a/node_modules/socket.io/index.js b/node_modules/socket.io/index.js new file mode 100644 index 0000000..cc00c10 --- /dev/null +++ b/node_modules/socket.io/index.js @@ -0,0 +1,8 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +module.exports = require('./lib/socket.io'); diff --git a/node_modules/socket.io/lib/logger.js b/node_modules/socket.io/lib/logger.js new file mode 100644 index 0000000..49d02c9 --- /dev/null +++ b/node_modules/socket.io/lib/logger.js @@ -0,0 +1,97 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var util = require('./util') + , toArray = util.toArray; + +/** + * Log levels. + */ + +var levels = [ + 'error' + , 'warn' + , 'info' + , 'debug' +]; + +/** + * Colors for log levels. + */ + +var colors = [ + 31 + , 33 + , 36 + , 90 +]; + +/** + * Pads the nice output to the longest log level. + */ + +function pad (str) { + var max = 0; + + for (var i = 0, l = levels.length; i < l; i++) + max = Math.max(max, levels[i].length); + + if (str.length < max) + return str + new Array(max - str.length + 1).join(' '); + + return str; +}; + +/** + * Logger (console). + * + * @api public + */ + +var Logger = module.exports = function (opts) { + opts = opts || {} + this.colors = false !== opts.colors; + this.level = 3; + this.enabled = true; +}; + +/** + * Log method. + * + * @api public + */ + +Logger.prototype.log = function (type) { + var index = levels.indexOf(type); + + if (index > this.level || !this.enabled) + return this; + + console.log.apply( + console + , [this.colors + ? ' \033[' + colors[index] + 'm' + pad(type) + ' -\033[39m' + : type + ':' + ].concat(toArray(arguments).slice(1)) + ); + + return this; +}; + +/** + * Generate methods. + */ + +levels.forEach(function (name) { + Logger.prototype[name] = function () { + this.log.apply(this, [name].concat(toArray(arguments))); + }; +}); diff --git a/node_modules/socket.io/lib/manager.js b/node_modules/socket.io/lib/manager.js new file mode 100644 index 0000000..8d637fb --- /dev/null +++ b/node_modules/socket.io/lib/manager.js @@ -0,0 +1,961 @@ +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var fs = require('fs') + , url = require('url') + , util = require('./util') + , store = require('./store') + , client = require('socket.io-client') + , transports = require('./transports') + , Logger = require('./logger') + , Socket = require('./socket') + , MemoryStore = require('./stores/memory') + , SocketNamespace = require('./namespace') + , Static = require('./static') + , EventEmitter = process.EventEmitter; + +/** + * Export the constructor. + */ + +exports = module.exports = Manager; + +/** + * Default transports. + */ + +var defaultTransports = exports.defaultTransports = [ + 'websocket' + , 'htmlfile' + , 'xhr-polling' + , 'jsonp-polling' +]; + +/** + * Inherited defaults. + */ + +var parent = module.parent.exports + , protocol = parent.protocol; + +/** + * Manager constructor. + * + * @param {HTTPServer} server + * @param {Object} options, optional + * @api public + */ + +function Manager (server, options) { + this.server = server; + this.namespaces = {}; + this.sockets = this.of(''); + this.settings = { + origins: '*:*' + , log: true + , store: new MemoryStore + , logger: new Logger + , static: new Static(this) + , heartbeats: true + , resource: '/socket.io' + , transports: defaultTransports + , authorization: false + , blacklist: ['disconnect'] + , 'log level': 3 + , 'log colors': true + , 'close timeout': 25 + , 'heartbeat timeout': 15 + , 'heartbeat interval': 20 + , 'polling duration': 20 + , 'flash policy server': true + , 'flash policy port': 10843 + , 'destroy upgrade': true + , 'browser client': true + , 'browser client cache': true + , 'browser client minification': false + , 'browser client etag': false + , 'browser client expires': 315360000 + , 'browser client gzip': false + , 'browser client handler': false + , 'client store expiration': 15 + }; + + for (var i in options) { + this.settings[i] = options[i]; + } + + var self = this; + + // default error handler + server.on('error', function(err) { + self.log.warn('error raised: ' + err); + }); + + this.initStore(); + + this.on('set:store', function() { + self.initStore(); + }); + + // reset listeners + this.oldListeners = server.listeners('request'); + server.removeAllListeners('request'); + + server.on('request', function (req, res) { + self.handleRequest(req, res); + }); + + server.on('upgrade', function (req, socket, head) { + self.handleUpgrade(req, socket, head); + }); + + server.on('close', function () { + clearInterval(self.gc); + }); + + server.once('listening', function () { + self.gc = setInterval(self.garbageCollection.bind(self), 10000); + }); + + for (var i in transports) { + if (transports[i].init) { + transports[i].init(this); + } + } + + this.log.info('socket.io started'); +}; + +Manager.prototype.__proto__ = EventEmitter.prototype + +/** + * Store accessor shortcut. + * + * @api public + */ + +Manager.prototype.__defineGetter__('store', function () { + var store = this.get('store'); + store.manager = this; + return store; +}); + +/** + * Logger accessor. + * + * @api public + */ + +Manager.prototype.__defineGetter__('log', function () { + var logger = this.get('logger'); + + logger.level = this.get('log level') || -1; + logger.colors = this.get('log colors'); + logger.enabled = this.enabled('log'); + + return logger; +}); + +/** + * Static accessor. + * + * @api public + */ + +Manager.prototype.__defineGetter__('static', function () { + return this.get('static'); +}); + +/** + * Get settings. + * + * @api public + */ + +Manager.prototype.get = function (key) { + return this.settings[key]; +}; + +/** + * Set settings + * + * @api public + */ + +Manager.prototype.set = function (key, value) { + if (arguments.length == 1) return this.get(key); + this.settings[key] = value; + this.emit('set:' + key, this.settings[key], key); + return this; +}; + +/** + * Enable a setting + * + * @api public + */ + +Manager.prototype.enable = function (key) { + this.settings[key] = true; + this.emit('set:' + key, this.settings[key], key); + return this; +}; + +/** + * Disable a setting + * + * @api public + */ + +Manager.prototype.disable = function (key) { + this.settings[key] = false; + this.emit('set:' + key, this.settings[key], key); + return this; +}; + +/** + * Checks if a setting is enabled + * + * @api public + */ + +Manager.prototype.enabled = function (key) { + return !!this.settings[key]; +}; + +/** + * Checks if a setting is disabled + * + * @api public + */ + +Manager.prototype.disabled = function (key) { + return !this.settings[key]; +}; + +/** + * Configure callbacks. + * + * @api public + */ + +Manager.prototype.configure = function (env, fn) { + if ('function' == typeof env) { + env.call(this); + } else if (env == process.env.NODE_ENV) { + fn.call(this); + } + + return this; +}; + +/** + * Initializes everything related to the message dispatcher. + * + * @api private + */ + +Manager.prototype.initStore = function () { + this.handshaken = {}; + this.connected = {}; + this.open = {}; + this.closed = {}; + this.rooms = {}; + this.roomClients = {}; + + var self = this; + + this.store.subscribe('handshake', function (id, data) { + self.onHandshake(id, data); + }); + + this.store.subscribe('connect', function (id) { + self.onConnect(id); + }); + + this.store.subscribe('open', function (id) { + self.onOpen(id); + }); + + this.store.subscribe('join', function (id, room) { + self.onJoin(id, room); + }); + + this.store.subscribe('leave', function (id, room) { + self.onLeave(id, room); + }); + + this.store.subscribe('close', function (id) { + self.onClose(id); + }); + + this.store.subscribe('dispatch', function (room, packet, volatile, exceptions) { + self.onDispatch(room, packet, volatile, exceptions); + }); + + this.store.subscribe('disconnect', function (id) { + self.onDisconnect(id); + }); +}; + +/** + * Called when a client handshakes. + * + * @param text + */ + +Manager.prototype.onHandshake = function (id, data) { + this.handshaken[id] = data; +}; + +/** + * Called when a client connects (ie: transport first opens) + * + * @api private + */ + +Manager.prototype.onConnect = function (id) { + this.connected[id] = true; +}; + +/** + * Called when a client opens a request in a different node. + * + * @api private + */ + +Manager.prototype.onOpen = function (id) { + this.open[id] = true; + + // if we were buffering messages for the client, clear them + if (this.closed[id]) { + var self = this; + + this.store.unsubscribe('dispatch:' + id, function () { + delete self.closed[id]; + }); + } + + // clear the current transport + if (this.transports[id]) { + this.transports[id].discard(); + this.transports[id] = null; + } +}; + +/** + * Called when a message is sent to a namespace and/or room. + * + * @api private + */ + +Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) { + if (this.rooms[room]) { + for (var i = 0, l = this.rooms[room].length; i < l; i++) { + var id = this.rooms[room][i]; + + if (!~exceptions.indexOf(id)) { + if (this.transports[id] && this.transports[id].open) { + this.transports[id].onDispatch(packet, volatile); + } else if (!volatile) { + this.onClientDispatch(id, packet); + } + } + } + } +}; + +/** + * Called when a client joins a nsp / room. + * + * @api private + */ + +Manager.prototype.onJoin = function (id, name) { + if (!this.roomClients[id]) { + this.roomClients[id] = {}; + } + + if (!this.rooms[name]) { + this.rooms[name] = []; + } + + if (!~this.rooms[name].indexOf(id)) { + this.rooms[name].push(id); + this.roomClients[id][name] = true; + } +}; + +/** + * Called when a client leaves a nsp / room. + * + * @param private + */ + +Manager.prototype.onLeave = function (id, room) { + if (this.rooms[room]) { + var index = this.rooms[room].indexOf(id); + + if (index >= 0) { + this.rooms[room].splice(index, 1); + } + + if (!this.rooms[room].length) { + delete this.rooms[room]; + } + delete this.roomClients[id][room]; + } +}; + +/** + * Called when a client closes a request in different node. + * + * @api private + */ + +Manager.prototype.onClose = function (id) { + if (this.open[id]) { + delete this.open[id]; + } + + this.closed[id] = []; + + var self = this; + + this.store.subscribe('dispatch:' + id, function (packet, volatile) { + if (!volatile) { + self.onClientDispatch(id, packet); + } + }); +}; + +/** + * Dispatches a message for a closed client. + * + * @api private + */ + +Manager.prototype.onClientDispatch = function (id, packet) { + if (this.closed[id]) { + this.closed[id].push(packet); + } +}; + +/** + * Receives a message for a client. + * + * @api private + */ + +Manager.prototype.onClientMessage = function (id, packet) { + if (this.namespaces[packet.endpoint]) { + this.namespaces[packet.endpoint].handlePacket(id, packet); + } +}; + +/** + * Fired when a client disconnects (not triggered). + * + * @api private + */ + +Manager.prototype.onClientDisconnect = function (id, reason) { + for (var name in this.namespaces) { + this.namespaces[name].handleDisconnect(id, reason, typeof this.roomClients[id][name] !== 'undefined'); + } + + this.onDisconnect(id); +}; + +/** + * Called when a client disconnects. + * + * @param text + */ + +Manager.prototype.onDisconnect = function (id, local) { + delete this.handshaken[id]; + + if (this.open[id]) { + delete this.open[id]; + } + + if (this.connected[id]) { + delete this.connected[id]; + } + + if (this.transports[id]) { + this.transports[id].discard(); + delete this.transports[id]; + } + + if (this.closed[id]) { + delete this.closed[id]; + } + + if (this.roomClients[id]) { + for (var room in this.roomClients[id]) { + this.onLeave(id, room); + } + delete this.roomClients[id] + } + + this.store.destroyClient(id, this.get('client store expiration')); + + this.store.unsubscribe('dispatch:' + id); + + if (local) { + this.store.unsubscribe('message:' + id); + this.store.unsubscribe('disconnect:' + id); + } +}; + +/** + * Handles an HTTP request. + * + * @api private + */ + +Manager.prototype.handleRequest = function (req, res) { + var data = this.checkRequest(req); + + if (!data) { + for (var i = 0, l = this.oldListeners.length; i < l; i++) { + this.oldListeners[i].call(this.server, req, res); + } + + return; + } + + if (data.static || !data.transport && !data.protocol) { + if (data.static && this.enabled('browser client')) { + this.static.write(data.path, req, res); + } else { + res.writeHead(200); + res.end('Welcome to socket.io.'); + + this.log.info('unhandled socket.io url'); + } + + return; + } + + if (data.protocol != protocol) { + res.writeHead(500); + res.end('Protocol version not supported.'); + + this.log.info('client protocol version unsupported'); + } else { + if (data.id) { + this.handleHTTPRequest(data, req, res); + } else { + this.handleHandshake(data, req, res); + } + } +}; + +/** + * Handles an HTTP Upgrade. + * + * @api private + */ + +Manager.prototype.handleUpgrade = function (req, socket, head) { + var data = this.checkRequest(req) + , self = this; + + if (!data) { + if (this.enabled('destroy upgrade')) { + socket.end(); + this.log.debug('destroying non-socket.io upgrade'); + } + + return; + } + + req.head = head; + this.handleClient(data, req); +}; + +/** + * Handles a normal handshaken HTTP request (eg: long-polling) + * + * @api private + */ + +Manager.prototype.handleHTTPRequest = function (data, req, res) { + req.res = res; + this.handleClient(data, req); +}; + +/** + * Intantiantes a new client. + * + * @api private + */ + +Manager.prototype.handleClient = function (data, req) { + var socket = req.socket + , store = this.store + , self = this; + + if (undefined != data.query.disconnect) { + if (this.transports[data.id] && this.transports[data.id].open) { + this.transports[data.id].onForcedDisconnect(); + } else { + this.store.publish('disconnect-force:' + data.id); + } + return; + } + + if (!~this.get('transports').indexOf(data.transport)) { + this.log.warn('unknown transport: "' + data.transport + '"'); + req.connection.end(); + return; + } + + var transport = new transports[data.transport](this, data, req) + , handshaken = this.handshaken[data.id]; + + if (handshaken) { + if (transport.open) { + if (this.closed[data.id] && this.closed[data.id].length) { + transport.payload(this.closed[data.id]); + this.closed[data.id] = []; + } + + this.onOpen(data.id); + this.store.publish('open', data.id); + this.transports[data.id] = transport; + } + + if (!this.connected[data.id]) { + this.onConnect(data.id); + this.store.publish('connect', data.id); + + // flag as used + delete handshaken.issued; + this.onHandshake(data.id, handshaken); + this.store.publish('handshake', data.id, handshaken); + + // initialize the socket for all namespaces + for (var i in this.namespaces) { + var socket = this.namespaces[i].socket(data.id, true); + + // echo back connect packet and fire connection event + if (i === '') { + this.namespaces[i].handlePacket(data.id, { type: 'connect' }); + } + } + + this.store.subscribe('message:' + data.id, function (packet) { + self.onClientMessage(data.id, packet); + }); + + this.store.subscribe('disconnect:' + data.id, function (reason) { + self.onClientDisconnect(data.id, reason); + }); + } + } else { + if (transport.open) { + transport.error('client not handshaken', 'reconnect'); + } + + transport.discard(); + } +}; + +/** + * Generates a session id. + * + * @api private + */ + +Manager.prototype.generateId = function () { + return Math.abs(Math.random() * Math.random() * Date.now() | 0).toString() + + Math.abs(Math.random() * Math.random() * Date.now() | 0).toString(); +}; + +/** + * Handles a handshake request. + * + * @api private + */ + +Manager.prototype.handleHandshake = function (data, req, res) { + var self = this + , origin = req.headers.origin + , headers = { + 'Content-Type': 'text/plain' + }; + + function writeErr (status, message) { + if (data.query.jsonp) { + res.writeHead(200, { 'Content-Type': 'application/javascript' }); + res.end('io.j[' + data.query.jsonp + '](new Error("' + message + '"));'); + } else { + res.writeHead(status, headers); + res.end(message); + } + }; + + function error (err) { + writeErr(500, 'handshake error'); + self.log.warn('handshake error ' + err); + }; + + if (!this.verifyOrigin(req)) { + writeErr(403, 'handshake bad origin'); + return; + } + + var handshakeData = this.handshakeData(data); + + if (origin) { + // https://developer.mozilla.org/En/HTTP_Access_Control + headers['Access-Control-Allow-Origin'] = '*'; + + if (req.headers.cookie) { + headers['Access-Control-Allow-Credentials'] = 'true'; + } + } + + this.authorize(handshakeData, function (err, authorized, newData) { + if (err) return error(err); + + if (authorized) { + var id = self.generateId() + , hs = [ + id + , self.enabled('heartbeats') ? self.get('heartbeat timeout') || '' : '' + , self.get('close timeout') || '' + , self.transports(data).join(',') + ].join(':'); + + if (data.query.jsonp) { + hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');'; + res.writeHead(200, { 'Content-Type': 'application/javascript' }); + } else { + res.writeHead(200, headers); + } + + res.end(hs); + + self.onHandshake(id, newData || handshakeData); + self.store.publish('handshake', id, newData || handshakeData); + + self.log.info('handshake authorized', id); + } else { + writeErr(403, 'handshake unauthorized'); + self.log.info('handshake unauthorized'); + } + }) +}; + +/** + * Gets normalized handshake data + * + * @api private + */ + +Manager.prototype.handshakeData = function (data) { + var connection = data.request.connection + , connectionAddress + , date = new Date; + + if (connection.remoteAddress) { + connectionAddress = { + address: connection.remoteAddress + , port: connection.remotePort + }; + } else if (connection.socket && connection.socket.remoteAddress) { + connectionAddress = { + address: connection.socket.remoteAddress + , port: connection.socket.remotePort + }; + } + + return { + headers: data.headers + , address: connectionAddress + , time: date.toString() + , query: data.query + , url: data.request.url + , xdomain: !!data.request.headers.origin + , secure: data.request.connection.secure + , issued: +date + }; +}; + +/** + * Verifies the origin of a request. + * + * @api private + */ + +Manager.prototype.verifyOrigin = function (request) { + var origin = request.headers.origin || request.headers.referer + , origins = this.get('origins'); + + if (origin === 'null') origin = '*'; + + if (origins.indexOf('*:*') !== -1) { + return true; + } + + if (origin) { + try { + var parts = url.parse(origin); + var ok = + ~origins.indexOf(parts.hostname + ':' + parts.port) || + ~origins.indexOf(parts.hostname + ':*') || + ~origins.indexOf('*:' + parts.port); + if (!ok) this.log.warn('illegal origin: ' + origin); + return ok; + } catch (ex) { + this.log.warn('error parsing origin'); + } + } + else { + this.log.warn('origin missing from handshake, yet required by config'); + } + return false; +}; + +/** + * Handles an incoming packet. + * + * @api private + */ + +Manager.prototype.handlePacket = function (sessid, packet) { + this.of(packet.endpoint || '').handlePacket(sessid, packet); +}; + +/** + * Performs authentication. + * + * @param Object client request data + * @api private + */ + +Manager.prototype.authorize = function (data, fn) { + if (this.get('authorization')) { + var self = this; + + this.get('authorization').call(this, data, function (err, authorized) { + self.log.debug('client ' + authorized ? 'authorized' : 'unauthorized'); + fn(err, authorized); + }); + } else { + this.log.debug('client authorized'); + fn(null, true); + } + + return this; +}; + +/** + * Retrieves the transports adviced to the user. + * + * @api private + */ + +Manager.prototype.transports = function (data) { + var transp = this.get('transports') + , ret = []; + + for (var i = 0, l = transp.length; i < l; i++) { + var transport = transp[i]; + + if (transport) { + if (!transport.checkClient || transport.checkClient(data)) { + ret.push(transport); + } + } + } + + return ret; +}; + +/** + * Checks whether a request is a socket.io one. + * + * @return {Object} a client request data object or `false` + * @api private + */ + +var regexp = /^\/([^\/]+)\/?([^\/]+)?\/?([^\/]+)?\/?$/ + +Manager.prototype.checkRequest = function (req) { + var resource = this.get('resource'); + + if (req.url.substr(0, resource.length) == resource) { + var uri = url.parse(req.url.substr(resource.length), true) + , path = uri.pathname || '' + , pieces = path.match(regexp); + + // client request data + var data = { + query: uri.query || {} + , headers: req.headers + , request: req + , path: path + }; + + if (pieces) { + data.protocol = Number(pieces[1]); + data.transport = pieces[2]; + data.id = pieces[3]; + data.static = !!this.static.has(path); + }; + + return data; + } + + return false; +}; + +/** + * Declares a socket namespace + * + * @api public + */ + +Manager.prototype.of = function (nsp) { + if (this.namespaces[nsp]) { + return this.namespaces[nsp]; + } + + return this.namespaces[nsp] = new SocketNamespace(this, nsp); +}; + +/** + * Perform garbage collection on long living objects and properties that cannot + * be removed automatically. + * + * @api private + */ + +Manager.prototype.garbageCollection = function () { + // clean up unused handshakes + var ids = Object.keys(this.handshaken) + , i = ids.length + , now = Date.now() + , handshake; + + while (i--) { + handshake = this.handshaken[ids[i]]; + + if ('issued' in handshake && (now - handshake.issued) >= 3E4) { + this.onDisconnect(ids[i]); + } + } +}; diff --git a/node_modules/socket.io/lib/namespace.js b/node_modules/socket.io/lib/namespace.js new file mode 100644 index 0000000..6e1e1c9 --- /dev/null +++ b/node_modules/socket.io/lib/namespace.js @@ -0,0 +1,355 @@ +/** + * Module dependencies. + */ + +var Socket = require('./socket') + , EventEmitter = process.EventEmitter + , parser = require('./parser') + , util = require('./util'); + +/** + * Exports the constructor. + */ + +exports = module.exports = SocketNamespace; + +/** + * Constructor. + * + * @api public. + */ + +function SocketNamespace (mgr, name) { + this.manager = mgr; + this.name = name || ''; + this.sockets = {}; + this.auth = false; + this.setFlags(); +}; + +/** + * Inherits from EventEmitter. + */ + +SocketNamespace.prototype.__proto__ = EventEmitter.prototype; + +/** + * Copies emit since we override it. + * + * @api private + */ + +SocketNamespace.prototype.$emit = EventEmitter.prototype.emit; + +/** + * Retrieves all clients as Socket instances as an array. + * + * @api public + */ + +SocketNamespace.prototype.clients = function (room) { + var room = this.name + (room !== undefined ? + '/' + room : ''); + + if (!this.manager.rooms[room]) { + return []; + } + + return this.manager.rooms[room].map(function (id) { + return this.socket(id); + }, this); +}; + +/** + * Access logger interface. + * + * @api public + */ + +SocketNamespace.prototype.__defineGetter__('log', function () { + return this.manager.log; +}); + +/** + * Access store. + * + * @api public + */ + +SocketNamespace.prototype.__defineGetter__('store', function () { + return this.manager.store; +}); + +/** + * JSON message flag. + * + * @api public + */ + +SocketNamespace.prototype.__defineGetter__('json', function () { + this.flags.json = true; + return this; +}); + +/** + * Volatile message flag. + * + * @api public + */ + +SocketNamespace.prototype.__defineGetter__('volatile', function () { + this.flags.volatile = true; + return this; +}); + +/** + * Overrides the room to relay messages to (flag). + * + * @api public + */ + +SocketNamespace.prototype.in = SocketNamespace.prototype.to = function (room) { + this.flags.endpoint = this.name + (room ? '/' + room : ''); + return this; +}; + +/** + * Adds a session id we should prevent relaying messages to (flag). + * + * @api public + */ + +SocketNamespace.prototype.except = function (id) { + this.flags.exceptions.push(id); + return this; +}; + +/** + * Sets the default flags. + * + * @api private + */ + +SocketNamespace.prototype.setFlags = function () { + this.flags = { + endpoint: this.name + , exceptions: [] + }; + return this; +}; + +/** + * Sends out a packet. + * + * @api private + */ + +SocketNamespace.prototype.packet = function (packet) { + packet.endpoint = this.name; + + var store = this.store + , log = this.log + , volatile = this.flags.volatile + , exceptions = this.flags.exceptions + , packet = parser.encodePacket(packet); + + this.manager.onDispatch(this.flags.endpoint, packet, volatile, exceptions); + this.store.publish('dispatch', this.flags.endpoint, packet, volatile, exceptions); + + this.setFlags(); + + return this; +}; + +/** + * Sends to everyone. + * + * @api public + */ + +SocketNamespace.prototype.send = function (data) { + return this.packet({ + type: this.flags.json ? 'json' : 'message' + , data: data + }); +}; + +/** + * Emits to everyone (override). + * + * @api public + */ + +SocketNamespace.prototype.emit = function (name) { + if (name == 'newListener') { + return this.$emit.apply(this, arguments); + } + + return this.packet({ + type: 'event' + , name: name + , args: util.toArray(arguments).slice(1) + }); +}; + +/** + * Retrieves or creates a write-only socket for a client, unless specified. + * + * @param {Boolean} whether the socket will be readable when initialized + * @api public + */ + +SocketNamespace.prototype.socket = function (sid, readable) { + if (!this.sockets[sid]) { + this.sockets[sid] = new Socket(this.manager, sid, this, readable); + } + + return this.sockets[sid]; +}; + +/** + * Sets authorization for this namespace. + * + * @api public + */ + +SocketNamespace.prototype.authorization = function (fn) { + this.auth = fn; + return this; +}; + +/** + * Called when a socket disconnects entirely. + * + * @api private + */ + +SocketNamespace.prototype.handleDisconnect = function (sid, reason, raiseOnDisconnect) { + if (this.sockets[sid] && this.sockets[sid].readable) { + if (raiseOnDisconnect) this.sockets[sid].onDisconnect(reason); + delete this.sockets[sid]; + } +}; + +/** + * Performs authentication. + * + * @param Object client request data + * @api private + */ + +SocketNamespace.prototype.authorize = function (data, fn) { + if (this.auth) { + var self = this; + + this.auth.call(this, data, function (err, authorized) { + self.log.debug('client ' + + (authorized ? '' : 'un') + 'authorized for ' + self.name); + fn(err, authorized); + }); + } else { + this.log.debug('client authorized for ' + this.name); + fn(null, true); + } + + return this; +}; + +/** + * Handles a packet. + * + * @api private + */ + +SocketNamespace.prototype.handlePacket = function (sessid, packet) { + var socket = this.socket(sessid) + , dataAck = packet.ack == 'data' + , manager = this.manager + , self = this; + + function ack () { + self.log.debug('sending data ack packet'); + socket.packet({ + type: 'ack' + , args: util.toArray(arguments) + , ackId: packet.id + }); + }; + + function error (err) { + self.log.warn('handshake error ' + err + ' for ' + self.name); + socket.packet({ type: 'error', reason: err }); + }; + + function connect () { + self.manager.onJoin(sessid, self.name); + self.store.publish('join', sessid, self.name); + + // packet echo + socket.packet({ type: 'connect' }); + + // emit connection event + self.$emit('connection', socket); + }; + + switch (packet.type) { + case 'connect': + if (packet.endpoint == '') { + connect(); + } else { + var handshakeData = manager.handshaken[sessid]; + + this.authorize(handshakeData, function (err, authorized, newData) { + if (err) return error(err); + + if (authorized) { + manager.onHandshake(sessid, newData || handshakeData); + self.store.publish('handshake', sessid, newData || handshakeData); + connect(); + } else { + error('unauthorized'); + } + }); + } + break; + + case 'ack': + if (socket.acks[packet.ackId]) { + socket.acks[packet.ackId].apply(socket, packet.args); + } else { + this.log.info('unknown ack packet'); + } + break; + + case 'event': + // check if the emitted event is not blacklisted + if (-~manager.get('blacklist').indexOf(packet.name)) { + this.log.debug('ignoring blacklisted event `' + packet.name + '`'); + } else { + var params = [packet.name].concat(packet.args); + + if (dataAck) { + params.push(ack); + } + + socket.$emit.apply(socket, params); + } + break; + + case 'disconnect': + this.manager.onLeave(sessid, this.name); + this.store.publish('leave', sessid, this.name); + + socket.$emit('disconnect', packet.reason || 'packet'); + break; + + case 'json': + case 'message': + var params = ['message', packet.data]; + + if (dataAck) + params.push(ack); + + socket.$emit.apply(socket, params); + }; +}; diff --git a/node_modules/socket.io/lib/parser.js b/node_modules/socket.io/lib/parser.js new file mode 100644 index 0000000..fe0cc37 --- /dev/null +++ b/node_modules/socket.io/lib/parser.js @@ -0,0 +1,249 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +/** + * Packet types. + */ + +var packets = exports.packets = { + 'disconnect': 0 + , 'connect': 1 + , 'heartbeat': 2 + , 'message': 3 + , 'json': 4 + , 'event': 5 + , 'ack': 6 + , 'error': 7 + , 'noop': 8 + } + , packetslist = Object.keys(packets); + +/** + * Errors reasons. + */ + +var reasons = exports.reasons = { + 'transport not supported': 0 + , 'client not handshaken': 1 + , 'unauthorized': 2 + } + , reasonslist = Object.keys(reasons); + +/** + * Errors advice. + */ + +var advice = exports.advice = { + 'reconnect': 0 + } + , advicelist = Object.keys(advice); + +/** + * Encodes a packet. + * + * @api private + */ + +exports.encodePacket = function (packet) { + var type = packets[packet.type] + , id = packet.id || '' + , endpoint = packet.endpoint || '' + , ack = packet.ack + , data = null; + + switch (packet.type) { + case 'message': + if (packet.data !== '') + data = packet.data; + break; + + case 'event': + var ev = { name: packet.name }; + + if (packet.args && packet.args.length) { + ev.args = packet.args; + } + + data = JSON.stringify(ev); + break; + + case 'json': + data = JSON.stringify(packet.data); + break; + + case 'ack': + data = packet.ackId + + (packet.args && packet.args.length + ? '+' + JSON.stringify(packet.args) : ''); + break; + + case 'connect': + if (packet.qs) + data = packet.qs; + break; + + case 'error': + var reason = packet.reason ? reasons[packet.reason] : '' + , adv = packet.advice ? advice[packet.advice] : '' + + if (reason !== '' || adv !== '') + data = reason + (adv !== '' ? ('+' + adv) : '') + + break; + } + + // construct packet with required fragments + var encoded = type + ':' + id + (ack == 'data' ? '+' : '') + ':' + endpoint; + + // data fragment is optional + if (data !== null && data !== undefined) + encoded += ':' + data; + + return encoded; +}; + +/** + * Encodes multiple messages (payload). + * + * @param {Array} messages + * @api private + */ + +exports.encodePayload = function (packets) { + var decoded = ''; + + if (packets.length == 1) + return packets[0]; + + for (var i = 0, l = packets.length; i < l; i++) { + var packet = packets[i]; + decoded += '\ufffd' + packet.length + '\ufffd' + packets[i] + } + + return decoded; +}; + +/** + * Decodes a packet + * + * @api private + */ + +var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; + +/** + * Wrap the JSON.parse in a seperate function the crankshaft optimizer will + * only punish this function for the usage for try catch + * + * @api private + */ + +function parse (data) { + try { return JSON.parse(data) } + catch (e) { return false } +} + +exports.decodePacket = function (data) { + var pieces = data.match(regexp); + + if (!pieces) return {}; + + var id = pieces[2] || '' + , data = pieces[5] || '' + , packet = { + type: packetslist[pieces[1]] + , endpoint: pieces[4] || '' + }; + + // whether we need to acknowledge the packet + if (id) { + packet.id = id; + if (pieces[3]) + packet.ack = 'data'; + else + packet.ack = true; + } + + // handle different packet types + switch (packet.type) { + case 'message': + packet.data = data || ''; + break; + + case 'event': + pieces = parse(data); + if (pieces) { + packet.name = pieces.name; + packet.args = pieces.args; + } + + packet.args = packet.args || []; + break; + + case 'json': + packet.data = parse(data); + break; + + case 'connect': + packet.qs = data || ''; + break; + + case 'ack': + pieces = data.match(/^([0-9]+)(\+)?(.*)/); + if (pieces) { + packet.ackId = pieces[1]; + packet.args = []; + + if (pieces[3]) { + packet.args = parse(pieces[3]) || []; + } + } + break; + + case 'error': + pieces = data.split('+'); + packet.reason = reasonslist[pieces[0]] || ''; + packet.advice = advicelist[pieces[1]] || ''; + } + + return packet; +}; + +/** + * Decodes data payload. Detects multiple messages + * + * @return {Array} messages + * @api public + */ + +exports.decodePayload = function (data) { + if (undefined == data || null == data) { + return []; + } + + if (data[0] == '\ufffd') { + var ret = []; + + for (var i = 1, length = ''; i < data.length; i++) { + if (data[i] == '\ufffd') { + ret.push(exports.decodePacket(data.substr(i + 1).substr(0, length))); + i += Number(length) + 1; + length = ''; + } else { + length += data[i]; + } + } + + return ret; + } else { + return [exports.decodePacket(data)]; + } +}; diff --git a/node_modules/socket.io/lib/socket.io.js b/node_modules/socket.io/lib/socket.io.js new file mode 100644 index 0000000..342de20 --- /dev/null +++ b/node_modules/socket.io/lib/socket.io.js @@ -0,0 +1,136 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var client = require('socket.io-client'); + +/** + * Version. + */ + +exports.version = '0.8.7'; + +/** + * Supported protocol version. + */ + +exports.protocol = 1; + +/** + * Client that we serve. + */ + +exports.clientVersion = client.version; + +/** + * Attaches a manager + * + * @param {HTTPServer/Number} a HTTP/S server or a port number to listen on. + * @param {Object} opts to be passed to Manager and/or http server + * @param {Function} callback if a port is supplied + * @api public + */ + +exports.listen = function (server, options, fn) { + if ('function' == typeof options) { + fn = options; + options = {}; + } + + if ('undefined' == typeof server) { + // create a server that listens on port 80 + server = 80; + } + + if ('number' == typeof server) { + // if a port number is passed + var port = server; + + if (options && options.key) + server = require('https').createServer(options); + else + server = require('http').createServer(); + + // default response + server.on('request', function (req, res) { + res.writeHead(200); + res.end('Welcome to socket.io.'); + }); + + server.listen(port, fn); + } + + // otherwise assume a http/s server + return new exports.Manager(server, options); +}; + +/** + * Manager constructor. + * + * @api public + */ + +exports.Manager = require('./manager'); + +/** + * Transport constructor. + * + * @api public + */ + +exports.Transport = require('./transport'); + +/** + * Socket constructor. + * + * @api public + */ + +exports.Socket = require('./socket'); + +/** + * Static constructor. + * + * @api public + */ + +exports.Static = require('./static'); + +/** + * Store constructor. + * + * @api public + */ + +exports.Store = require('./store'); + +/** + * Memory Store constructor. + * + * @api public + */ + +exports.MemoryStore = require('./stores/memory'); + +/** + * Redis Store constructor. + * + * @api public + */ + +exports.RedisStore = require('./stores/redis'); + +/** + * Parser. + * + * @api public + */ + +exports.parser = require('./parser'); diff --git a/node_modules/socket.io/lib/socket.js b/node_modules/socket.io/lib/socket.js new file mode 100644 index 0000000..38542f4 --- /dev/null +++ b/node_modules/socket.io/lib/socket.js @@ -0,0 +1,362 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var parser = require('./parser') + , util = require('./util') + , EventEmitter = process.EventEmitter + +/** + * Export the constructor. + */ + +exports = module.exports = Socket; + +/** + * Default error event listener to prevent uncaught exceptions. + */ + +var defaultError = function () {}; + +/** + * Socket constructor. + * + * @param {Manager} manager instance + * @param {String} session id + * @param {Namespace} namespace the socket belongs to + * @param {Boolean} whether the + * @api public + */ + +function Socket (manager, id, nsp, readable) { + this.id = id; + this.namespace = nsp; + this.manager = manager; + this.disconnected = false; + this.ackPackets = 0; + this.acks = {}; + this.setFlags(); + this.readable = readable; + this.store = this.manager.store.client(this.id); + this.on('error', defaultError); +}; + +/** + * Inherits from EventEmitter. + */ + +Socket.prototype.__proto__ = EventEmitter.prototype; + +/** + * Accessor shortcut for the handshake data + * + * @api private + */ + +Socket.prototype.__defineGetter__('handshake', function () { + return this.manager.handshaken[this.id]; +}); + +/** + * Accessor shortcut for the transport type + * + * @api private + */ + +Socket.prototype.__defineGetter__('transport', function () { + return this.manager.transports[this.id].name; +}); + +/** + * Accessor shortcut for the logger. + * + * @api private + */ + +Socket.prototype.__defineGetter__('log', function () { + return this.manager.log; +}); + +/** + * JSON message flag. + * + * @api public + */ + +Socket.prototype.__defineGetter__('json', function () { + this.flags.json = true; + return this; +}); + +/** + * Volatile message flag. + * + * @api public + */ + +Socket.prototype.__defineGetter__('volatile', function () { + this.flags.volatile = true; + return this; +}); + +/** + * Broadcast message flag. + * + * @api public + */ + +Socket.prototype.__defineGetter__('broadcast', function () { + this.flags.broadcast = true; + return this; +}); + +/** + * Overrides the room to broadcast messages to (flag) + * + * @api public + */ + +Socket.prototype.to = Socket.prototype.in = function (room) { + this.flags.room = room; + return this; +}; + +/** + * Resets flags + * + * @api private + */ + +Socket.prototype.setFlags = function () { + this.flags = { + endpoint: this.namespace.name + , room: '' + }; + return this; +}; + +/** + * Triggered on disconnect + * + * @api private + */ + +Socket.prototype.onDisconnect = function (reason) { + if (!this.disconnected) { + this.$emit('disconnect', reason); + this.disconnected = true; + } +}; + +/** + * Joins a user to a room. + * + * @api public + */ + +Socket.prototype.join = function (name, fn) { + var nsp = this.namespace.name + , name = (nsp + '/') + name; + + this.manager.onJoin(this.id, name); + this.manager.store.publish('join', this.id, name); + + if (fn) { + this.log.warn('Client#join callback is deprecated'); + fn(); + } + + return this; +}; + +/** + * Un-joins a user from a room. + * + * @api public + */ + +Socket.prototype.leave = function (name, fn) { + var nsp = this.namespace.name + , name = (nsp + '/') + name; + + this.manager.onLeave(this.id, name); + this.manager.store.publish('leave', this.id, name); + + if (fn) { + this.log.warn('Client#leave callback is deprecated'); + fn(); + } + + return this; +}; + +/** + * Transmits a packet. + * + * @api private + */ + +Socket.prototype.packet = function (packet) { + if (this.flags.broadcast) { + this.log.debug('broadcasting packet'); + this.namespace.in(this.flags.room).except(this.id).packet(packet); + } else { + packet.endpoint = this.flags.endpoint; + packet = parser.encodePacket(packet); + + this.dispatch(packet, this.flags.volatile); + } + + this.setFlags(); + + return this; +}; + +/** + * Dispatches a packet + * + * @api private + */ + +Socket.prototype.dispatch = function (packet, volatile) { + if (this.manager.transports[this.id] && this.manager.transports[this.id].open) { + this.manager.transports[this.id].onDispatch(packet, volatile); + } else { + if (!volatile) { + this.manager.onClientDispatch(this.id, packet, volatile); + } + + this.manager.store.publish('dispatch:' + this.id, packet, volatile); + } +}; + +/** + * Stores data for the client. + * + * @api public + */ + +Socket.prototype.set = function (key, value, fn) { + this.store.set(key, value, fn); + return this; +}; + +/** + * Retrieves data for the client + * + * @api public + */ + +Socket.prototype.get = function (key, fn) { + this.store.get(key, fn); + return this; +}; + +/** + * Checks data for the client + * + * @api public + */ + +Socket.prototype.has = function (key, fn) { + this.store.has(key, fn); + return this; +}; + +/** + * Deletes data for the client + * + * @api public + */ + +Socket.prototype.del = function (key, fn) { + this.store.del(key, fn); + return this; +}; + +/** + * Kicks client + * + * @api public + */ + +Socket.prototype.disconnect = function () { + if (!this.disconnected) { + this.log.info('booting client'); + + if (this.manager.transports[this.id] && this.manager.transports[this.id].open) { + this.manager.transports[this.id].onForcedDisconnect(); + } else { + this.manager.onClientDisconnect(this.id); + this.manager.store.publish('disconnect:' + this.id); + } + } + + return this; +}; + +/** + * Send a message. + * + * @api public + */ + +Socket.prototype.send = function (data, fn) { + var packet = { + type: this.flags.json ? 'json' : 'message' + , data: data + }; + + if (fn) { + packet.id = ++this.ackPackets; + packet.ack = true; + this.acks[packet.id] = fn; + } + + return this.packet(packet); +}; + +/** + * Original emit function. + * + * @api private + */ + +Socket.prototype.$emit = EventEmitter.prototype.emit; + +/** + * Emit override for custom events. + * + * @api public + */ + +Socket.prototype.emit = function (ev) { + if (ev == 'newListener') { + return this.$emit.apply(this, arguments); + } + + var args = util.toArray(arguments).slice(1) + , lastArg = args[args.length - 1] + , packet = { + type: 'event' + , name: ev + }; + + if ('function' == typeof lastArg) { + packet.id = ++this.ackPackets; + packet.ack = lastArg.length ? 'data' : true; + this.acks[packet.id] = lastArg; + args = args.slice(0, args.length - 1); + } + + packet.args = args; + + return this.packet(packet); +}; diff --git a/node_modules/socket.io/lib/static.js b/node_modules/socket.io/lib/static.js new file mode 100644 index 0000000..e3117ed --- /dev/null +++ b/node_modules/socket.io/lib/static.js @@ -0,0 +1,395 @@ + +/*! +* socket.io-node +* Copyright(c) 2011 LearnBoost +* MIT Licensed +*/ + +/** + * Module dependencies. + */ + +var client = require('socket.io-client') + , cp = require('child_process') + , fs = require('fs') + , util = require('./util'); + +/** + * File type details. + * + * @api private + */ + +var mime = { + js: { + type: 'application/javascript' + , encoding: 'utf8' + , gzip: true + } + , swf: { + type: 'application/x-shockwave-flash' + , encoding: 'binary' + , gzip: false + } +}; + +/** + * Regexp for matching custom transport patterns. Users can configure their own + * socket.io bundle based on the url structure. Different transport names are + * concatinated using the `+` char. /socket.io/socket.io+websocket.js should + * create a bundle that only contains support for the websocket. + * + * @api private + */ + +var bundle = /\+((?:\+)?[\w\-]+)*(?:\.v\d+\.\d+\.\d+)?(?:\.js)$/ + , versioning = /\.v\d+\.\d+\.\d+(?:\.js)$/; + +/** + * Export the constructor + */ + +exports = module.exports = Static; + +/** + * Static constructor + * + * @api public + */ + +function Static (manager) { + this.manager = manager; + this.cache = {}; + this.paths = {}; + + this.init(); +} + +/** + * Initialize the Static by adding default file paths. + * + * @api public + */ + +Static.prototype.init = function () { + /** + * Generates a unique id based the supplied transports array + * + * @param {Array} transports The array with transport types + * @api private + */ + function id (transports) { + var id = transports.join('').split('').map(function (char) { + return ('' + char.charCodeAt(0)).split('').pop(); + }).reduce(function (char, id) { + return char +id; + }); + + return client.version + ':' + id; + } + + /** + * Generates a socket.io-client file based on the supplied transports. + * + * @param {Array} transports The array with transport types + * @param {Function} callback Callback for the static.write + * @api private + */ + + function build (transports, callback) { + client.builder(transports, { + minify: self.manager.enabled('browser client minification') + }, function (err, content) { + callback(err, content ? new Buffer(content) : null, id(transports)); + } + ); + } + + var self = this; + + // add our default static files + this.add('/static/flashsocket/WebSocketMain.swf', { + file: client.dist + '/WebSocketMain.swf' + }); + + this.add('/static/flashsocket/WebSocketMainInsecure.swf', { + file: client.dist + '/WebSocketMainInsecure.swf' + }); + + // generates dedicated build based on the available transports + this.add('/socket.io.js', function (path, callback) { + build(self.manager.get('transports'), callback); + }); + + this.add('/socket.io.v', { mime: mime.js }, function (path, callback) { + build(self.manager.get('transports'), callback); + }); + + // allow custom builds based on url paths + this.add('/socket.io+', { mime: mime.js }, function (path, callback) { + var available = self.manager.get('transports') + , matches = path.match(bundle) + , transports = []; + + if (!matches) return callback('No valid transports'); + + // make sure they valid transports + matches[0].split('.')[0].split('+').slice(1).forEach(function (transport) { + if (!!~available.indexOf(transport)) { + transports.push(transport); + } + }); + + if (!transports.length) return callback('No valid transports'); + build(transports, callback); + }); + + // clear cache when transports change + this.manager.on('set:transports', function (key, value) { + delete self.cache['/socket.io.js']; + Object.keys(self.cache).forEach(function (key) { + if (bundle.test(key)) { + delete self.cache[key]; + } + }); + }); +}; + +/** + * Gzip compress buffers. + * + * @param {Buffer} data The buffer that needs gzip compression + * @param {Function} callback + * @api public + */ + +Static.prototype.gzip = function (data, callback) { + var gzip = cp.spawn('gzip', ['-9', '-c', '-f', '-n']) + , encoding = Buffer.isBuffer(data) ? 'binary' : 'utf8' + , buffer = [] + , err; + + gzip.stdout.on('data', function (data) { + buffer.push(data); + }); + + gzip.stderr.on('data', function (data) { + err = data +''; + buffer.length = 0; + }); + + gzip.on('exit', function () { + if (err) return callback(err); + + var size = 0 + , index = 0 + , i = buffer.length + , content; + + while (i--) { + size += buffer[i].length; + } + + content = new Buffer(size); + i = buffer.length; + + buffer.forEach(function (buffer) { + var length = buffer.length; + + buffer.copy(content, index, 0, length); + index += length; + }); + + buffer.length = 0; + callback(null, content); + }); + + gzip.stdin.end(data, encoding); +}; + +/** + * Is the path a static file? + * + * @param {String} path The path that needs to be checked + * @api public + */ + +Static.prototype.has = function (path) { + // fast case + if (this.paths[path]) return this.paths[path]; + + var keys = Object.keys(this.paths) + , i = keys.length; + + while (i--) { + if (-~path.indexOf(keys[i])) return this.paths[keys[i]]; + } + + return false; +}; + +/** + * Add new paths new paths that can be served using the static provider. + * + * @param {String} path The path to respond to + * @param {Options} options Options for writing out the response + * @param {Function} [callback] Optional callback if no options.file is + * supplied this would be called instead. + * @api public + */ + +Static.prototype.add = function (path, options, callback) { + var extension = /(?:\.(\w{1,4}))$/.exec(path); + + if (!callback && typeof options == 'function') { + callback = options; + options = {}; + } + + options.mime = options.mime || (extension ? mime[extension[1]] : false); + + if (callback) options.callback = callback; + if (!(options.file || options.callback) || !options.mime) return false; + + this.paths[path] = options; + + return true; +}; + +/** + * Writes a static response. + * + * @param {String} path The path for the static content + * @param {HTTPRequest} req The request object + * @param {HTTPResponse} res The response object + * @api public + */ + +Static.prototype.write = function (path, req, res) { + /** + * Write a response without throwing errors because can throw error if the + * response is no longer writable etc. + * + * @api private + */ + + function write (status, headers, content, encoding) { + try { + res.writeHead(status, headers || undefined); + + // only write content if it's not a HEAD request and we actually have + // some content to write (304's doesn't have content). + res.end( + req.method !== 'HEAD' && content ? content : '' + , encoding || undefined + ); + } catch (e) {} + } + + /** + * Answers requests depending on the request properties and the reply object. + * + * @param {Object} reply The details and content to reply the response with + * @api private + */ + + function answer (reply) { + var cached = req.headers['if-none-match'] === reply.etag; + if (cached && self.manager.enabled('browser client etag')) { + return write(304); + } + + var accept = req.headers['accept-encoding'] || '' + , gzip = !!~accept.toLowerCase().indexOf('gzip') + , mime = reply.mime + , versioned = reply.versioned + , headers = { + 'Content-Type': mime.type + }; + + // check if we can add a etag + if (self.manager.enabled('browser client etag') && reply.etag && !versioned) { + headers['Etag'] = reply.etag; + } + + // see if we need to set Expire headers because the path is versioned + if (versioned) { + var expires = self.manager.get('browser client expires'); + headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires; + headers['Date'] = new Date().toUTCString(); + headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString(); + } + + if (gzip && reply.gzip) { + headers['Content-Length'] = reply.gzip.length; + headers['Content-Encoding'] = 'gzip'; + headers['Vary'] = 'Accept-Encoding'; + write(200, headers, reply.gzip.content, mime.encoding); + } else { + headers['Content-Length'] = reply.length; + write(200, headers, reply.content, mime.encoding); + } + + self.manager.log.debug('served static content ' + path); + } + + var self = this + , details; + + // most common case first + if (this.manager.enabled('browser client cache') && this.cache[path]) { + return answer(this.cache[path]); + } else if (this.manager.get('browser client handler')) { + return this.manager.get('browser client handler').call(this, req, res); + } else if ((details = this.has(path))) { + /** + * A small helper function that will let us deal with fs and dynamic files + * + * @param {Object} err Optional error + * @param {Buffer} content The data + * @api private + */ + + function ready (err, content, etag) { + if (err) { + self.manager.log.warn('Unable to serve file. ' + (err.message || err)); + return write(500, null, 'Error serving static ' + path); + } + + // store the result in the cache + var reply = self.cache[path] = { + content: content + , length: content.length + , mime: details.mime + , etag: etag || client.version + , versioned: versioning.test(path) + }; + + // check if gzip is enabled + if (details.mime.gzip && self.manager.enabled('browser client gzip')) { + self.gzip(content, function (err, content) { + if (!err) { + reply.gzip = { + content: content + , length: content.length + } + } + + answer(reply); + }); + } else { + answer(reply); + } + } + + if (details.file) { + fs.readFile(details.file, ready); + } else if(details.callback) { + details.callback.call(this, path, ready); + } else { + write(404, null, 'File handle not found'); + } + } else { + write(404, null, 'File not found'); + } +}; diff --git a/node_modules/socket.io/lib/store.js b/node_modules/socket.io/lib/store.js new file mode 100644 index 0000000..06c0389 --- /dev/null +++ b/node_modules/socket.io/lib/store.js @@ -0,0 +1,98 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Expose the constructor. + */ + +exports = module.exports = Store; + +/** + * Module dependencies. + */ + +var EventEmitter = process.EventEmitter; + +/** + * Store interface + * + * @api public + */ + +function Store (options) { + this.options = options; + this.clients = {}; +}; + +/** + * Inherit from EventEmitter. + */ + +Store.prototype.__proto__ = EventEmitter.prototype; + +/** + * Initializes a client store + * + * @param {String} id + * @api public + */ + +Store.prototype.client = function (id) { + if (!this.clients[id]) { + this.clients[id] = new (this.constructor.Client)(this, id); + } + + return this.clients[id]; +}; + +/** + * Destroys a client + * + * @api {String} sid + * @param {Number} number of seconds to expire client data + * @api private + */ + +Store.prototype.destroyClient = function (id, expiration) { + if (this.clients[id]) { + this.clients[id].destroy(expiration); + delete this.clients[id]; + } + + return this; +}; + +/** + * Destroys the store + * + * @param {Number} number of seconds to expire client data + * @api private + */ + +Store.prototype.destroy = function (clientExpiration) { + var keys = Object.keys(this.clients) + , count = keys.length; + + for (var i = 0, l = count; i < l; i++) { + this.destroyClient(keys[i], clientExpiration); + } + + this.clients = {}; + + return this; +}; + +/** + * Client. + * + * @api public + */ + +Store.Client = function (store, id) { + this.store = store; + this.id = id; +}; diff --git a/node_modules/socket.io/lib/stores/memory.js b/node_modules/socket.io/lib/stores/memory.js new file mode 100644 index 0000000..8b731a7 --- /dev/null +++ b/node_modules/socket.io/lib/stores/memory.js @@ -0,0 +1,143 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var crypto = require('crypto') + , Store = require('../store'); + +/** + * Exports the constructor. + */ + +exports = module.exports = Memory; +Memory.Client = Client; + +/** + * Memory store + * + * @api public + */ + +function Memory (opts) { + Store.call(this, opts); +}; + +/** + * Inherits from Store. + */ + +Memory.prototype.__proto__ = Store.prototype; + +/** + * Publishes a message. + * + * @api private + */ + +Memory.prototype.publish = function () { }; + +/** + * Subscribes to a channel + * + * @api private + */ + +Memory.prototype.subscribe = function () { }; + +/** + * Unsubscribes + * + * @api private + */ + +Memory.prototype.unsubscribe = function () { }; + +/** + * Client constructor + * + * @api private + */ + +function Client () { + Store.Client.apply(this, arguments); + this.data = {}; +}; + +/** + * Inherits from Store.Client + */ + +Client.prototype.__proto__ = Store.Client; + +/** + * Gets a key + * + * @api public + */ + +Client.prototype.get = function (key, fn) { + fn(null, this.data[key] === undefined ? null : this.data[key]); + return this; +}; + +/** + * Sets a key + * + * @api public + */ + +Client.prototype.set = function (key, value, fn) { + this.data[key] = value; + fn && fn(null); + return this; +}; + +/** + * Has a key + * + * @api public + */ + +Client.prototype.has = function (key, fn) { + fn(null, key in this.data); +}; + +/** + * Deletes a key + * + * @api public + */ + +Client.prototype.del = function (key, fn) { + delete this.data[key]; + fn && fn(null); + return this; +}; + +/** + * Destroys the client. + * + * @param {Number} number of seconds to expire data + * @api private + */ + +Client.prototype.destroy = function (expiration) { + if ('number' != typeof expiration) { + this.data = {}; + } else { + var self = this; + + setTimeout(function () { + self.data = {}; + }, expiration * 1000); + } + + return this; +}; diff --git a/node_modules/socket.io/lib/stores/redis.js b/node_modules/socket.io/lib/stores/redis.js new file mode 100644 index 0000000..8fea235 --- /dev/null +++ b/node_modules/socket.io/lib/stores/redis.js @@ -0,0 +1,269 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var crypto = require('crypto') + , Store = require('../store') + , assert = require('assert'); + +/** + * Exports the constructor. + */ + +exports = module.exports = Redis; +Redis.Client = Client; + +/** + * Redis store. + * Options: + * - nodeId (fn) gets an id that uniquely identifies this node + * - redis (fn) redis constructor, defaults to redis + * - redisPub (object) options to pass to the pub redis client + * - redisSub (object) options to pass to the sub redis client + * - redisClient (object) options to pass to the general redis client + * - pack (fn) custom packing, defaults to JSON or msgpack if installed + * - unpack (fn) custom packing, defaults to JSON or msgpack if installed + * + * @api public + */ + +function Redis (opts) { + opts = opts || {}; + + // node id to uniquely identify this node + var nodeId = opts.nodeId || function () { + // by default, we generate a random id + return Math.abs(Math.random() * Math.random() * Date.now() | 0); + }; + + this.nodeId = nodeId(); + + // packing / unpacking mechanism + if (opts.pack) { + this.pack = opts.pack; + this.unpack = opts.unpack; + } else { + try { + var msgpack = require('msgpack'); + this.pack = msgpack.pack; + this.unpack = msgpack.unpack; + } catch (e) { + this.pack = JSON.stringify; + this.unpack = JSON.parse; + } + } + + var redis = opts.redis || require('redis') + , RedisClient = redis.RedisClient; + + // initialize a pubsub client and a regular client + if (opts.redisPub instanceof RedisClient) { + this.pub = opts.redisPub; + } else { + opts.redisPub || (opts.redisPub = {}); + this.pub = redis.createClient(opts.redisPub.port, opts.redisPub.host, opts.redisPub); + } + if (opts.redisSub instanceof RedisClient) { + this.sub = opts.redisSub; + } else { + opts.redisSub || (opts.redisSub = {}); + this.sub = redis.createClient(opts.redisSub.port, opts.redisSub.host, opts.redisSub); + } + if (opts.redisClient instanceof RedisClient) { + this.cmd = opts.redisClient; + } else { + opts.redisClient || (opts.redisClient = {}); + this.cmd = redis.createClient(opts.redisClient.port, opts.redisClient.host, opts.redisClient); + } + + Store.call(this, opts); + + this.sub.setMaxListeners(0); + this.setMaxListeners(0); +}; + +/** + * Inherits from Store. + */ + +Redis.prototype.__proto__ = Store.prototype; + +/** + * Publishes a message. + * + * @api private + */ + +Redis.prototype.publish = function (name) { + var args = Array.prototype.slice.call(arguments, 1); + this.pub.publish(name, this.pack({ nodeId: this.nodeId, args: args })); + this.emit.apply(this, ['publish', name].concat(args)); +}; + +/** + * Subscribes to a channel + * + * @api private + */ + +Redis.prototype.subscribe = function (name, consumer, fn) { + this.sub.subscribe(name); + + if (consumer || fn) { + var self = this; + + self.sub.on('subscribe', function subscribe (ch) { + if (name == ch) { + function message (ch, msg) { + if (name == ch) { + msg = self.unpack(msg); + + // we check that the message consumed wasnt emitted by this node + if (self.nodeId != msg.nodeId) { + consumer.apply(null, msg.args); + } + } + }; + + self.sub.on('message', message); + + self.on('unsubscribe', function unsubscribe (ch) { + if (name == ch) { + self.sub.removeListener('message', message); + self.removeListener('unsubscribe', unsubscribe); + } + }); + + self.sub.removeListener('subscribe', subscribe); + + fn && fn(); + } + }); + } + + this.emit('subscribe', name, consumer, fn); +}; + +/** + * Unsubscribes + * + * @api private + */ + +Redis.prototype.unsubscribe = function (name, fn) { + this.sub.unsubscribe(name); + + if (fn) { + var client = this.sub; + + client.on('unsubscribe', function unsubscribe (ch) { + if (name == ch) { + fn(); + client.removeListener('unsubscribe', unsubscribe); + } + }); + } + + this.emit('unsubscribe', name, fn); +}; + +/** + * Destroys the store + * + * @api public + */ + +Redis.prototype.destroy = function () { + Store.prototype.destroy.call(this); + + this.pub.end(); + this.sub.end(); + this.cmd.end(); +}; + +/** + * Client constructor + * + * @api private + */ + +function Client (store, id) { + Store.Client.call(this, store, id); +}; + +/** + * Inherits from Store.Client + */ + +Client.prototype.__proto__ = Store.Client; + +/** + * Redis hash get + * + * @api private + */ + +Client.prototype.get = function (key, fn) { + this.store.cmd.hget(this.id, key, fn); + return this; +}; + +/** + * Redis hash set + * + * @api private + */ + +Client.prototype.set = function (key, value, fn) { + this.store.cmd.hset(this.id, key, value, fn); + return this; +}; + +/** + * Redis hash del + * + * @api private + */ + +Client.prototype.del = function (key, fn) { + this.store.cmd.hdel(this.id, key, fn); + return this; +}; + +/** + * Redis hash has + * + * @api private + */ + +Client.prototype.has = function (key, fn) { + this.store.cmd.hexists(this.id, key, function (err, has) { + if (err) return fn(err); + fn(null, !!has); + }); + return this; +}; + +/** + * Destroys client + * + * @param {Number} number of seconds to expire data + * @api private + */ + +Client.prototype.destroy = function (expiration) { + if ('number' != typeof expiration) { + this.store.cmd.del(this.id); + } else { + this.store.cmd.expire(this.id, expiration); + } + + return this; +}; diff --git a/node_modules/socket.io/lib/transport.js b/node_modules/socket.io/lib/transport.js new file mode 100644 index 0000000..61f456f --- /dev/null +++ b/node_modules/socket.io/lib/transport.js @@ -0,0 +1,534 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var parser = require('./parser'); + +/** + * Expose the constructor. + */ + +exports = module.exports = Transport; + +/** + * Transport constructor. + * + * @api public + */ + +function Transport (mng, data, req) { + this.manager = mng; + this.id = data.id; + this.disconnected = false; + this.drained = true; + this.handleRequest(req); +}; + +/** + * Access the logger. + * + * @api public + */ + +Transport.prototype.__defineGetter__('log', function () { + return this.manager.log; +}); + +/** + * Access the store. + * + * @api public + */ + +Transport.prototype.__defineGetter__('store', function () { + return this.manager.store; +}); + +/** + * Handles a request when it's set. + * + * @api private + */ + +Transport.prototype.handleRequest = function (req) { + this.log.debug('setting request', req.method, req.url); + this.req = req; + + if (req.method == 'GET') { + this.socket = req.socket; + this.open = true; + this.drained = true; + this.setHeartbeatInterval(); + + this.setHandlers(); + this.onSocketConnect(); + } +}; + +/** + * Called when a connection is first set. + * + * @api private + */ + +Transport.prototype.onSocketConnect = function () { }; + +/** + * Sets transport handlers + * + * @api private + */ + +Transport.prototype.setHandlers = function () { + var self = this; + + // we need to do this in a pub/sub way since the client can POST the message + // over a different socket (ie: different Transport instance) + this.store.subscribe('heartbeat-clear:' + this.id, function () { + self.onHeartbeatClear(); + }); + + this.store.subscribe('disconnect-force:' + this.id, function () { + self.onForcedDisconnect(); + }); + + this.store.subscribe('dispatch:' + this.id, function (packet, volatile) { + self.onDispatch(packet, volatile); + }); + + this.bound = { + end: this.onSocketEnd.bind(this) + , close: this.onSocketClose.bind(this) + , error: this.onSocketError.bind(this) + , drain: this.onSocketDrain.bind(this) + }; + + this.socket.on('end', this.bound.end); + this.socket.on('close', this.bound.close); + this.socket.on('error', this.bound.error); + this.socket.on('drain', this.bound.drain); + + this.handlersSet = true; +}; + +/** + * Removes transport handlers + * + * @api private + */ + +Transport.prototype.clearHandlers = function () { + if (this.handlersSet) { + this.store.unsubscribe('disconnect-force:' + this.id); + this.store.unsubscribe('heartbeat-clear:' + this.id); + this.store.unsubscribe('dispatch:' + this.id); + + this.socket.removeListener('end', this.bound.end); + this.socket.removeListener('close', this.bound.close); + this.socket.removeListener('error', this.bound.error); + this.socket.removeListener('drain', this.bound.drain); + } +}; + +/** + * Called when the connection dies + * + * @api private + */ + +Transport.prototype.onSocketEnd = function () { + this.end('socket end'); +}; + +/** + * Called when the connection dies + * + * @api private + */ + +Transport.prototype.onSocketClose = function (error) { + this.end(error ? 'socket error' : 'socket close'); +}; + +/** + * Called when the connection has an error. + * + * @api private + */ + +Transport.prototype.onSocketError = function (err) { + if (this.open) { + this.socket.destroy(); + this.onClose(); + } + + this.log.info('socket error ' + err.stack); +}; + +/** + * Called when the connection is drained. + * + * @api private + */ + +Transport.prototype.onSocketDrain = function () { + this.drained = true; +}; + +/** + * Called upon receiving a heartbeat packet. + * + * @api private + */ + +Transport.prototype.onHeartbeatClear = function () { + this.clearHeartbeatTimeout(); + this.setHeartbeatInterval(); +}; + +/** + * Called upon a forced disconnection. + * + * @api private + */ + +Transport.prototype.onForcedDisconnect = function () { + if (!this.disconnected) { + this.log.info('transport end by forced client disconnection'); + if (this.open) { + this.packet({ type: 'disconnect' }); + } + this.end('booted'); + } +}; + +/** + * Dispatches a packet. + * + * @api private + */ + +Transport.prototype.onDispatch = function (packet, volatile) { + if (volatile) { + this.writeVolatile(packet); + } else { + this.write(packet); + } +}; + +/** + * Sets the close timeout. + */ + +Transport.prototype.setCloseTimeout = function () { + if (!this.closeTimeout) { + var self = this; + + this.closeTimeout = setTimeout(function () { + self.log.debug('fired close timeout for client', self.id); + self.closeTimeout = null; + self.end('close timeout'); + }, this.manager.get('close timeout') * 1000); + + this.log.debug('set close timeout for client', this.id); + } +}; + +/** + * Clears the close timeout. + */ + +Transport.prototype.clearCloseTimeout = function () { + if (this.closeTimeout) { + clearTimeout(this.closeTimeout); + this.closeTimeout = null; + + this.log.debug('cleared close timeout for client', this.id); + } +}; + +/** + * Sets the heartbeat timeout + */ + +Transport.prototype.setHeartbeatTimeout = function () { + if (!this.heartbeatTimeout && this.manager.enabled('heartbeats')) { + var self = this; + + this.heartbeatTimeout = setTimeout(function () { + self.log.debug('fired heartbeat timeout for client', self.id); + self.heartbeatTimeout = null; + self.end('heartbeat timeout'); + }, this.manager.get('heartbeat timeout') * 1000); + + this.log.debug('set heartbeat timeout for client', this.id); + } +}; + +/** + * Clears the heartbeat timeout + * + * @param text + */ + +Transport.prototype.clearHeartbeatTimeout = function () { + if (this.heartbeatTimeout && this.manager.enabled('heartbeats')) { + clearTimeout(this.heartbeatTimeout); + this.heartbeatTimeout = null; + this.log.debug('cleared heartbeat timeout for client', this.id); + } +}; + +/** + * Sets the heartbeat interval. To be called when a connection opens and when + * a heartbeat is received. + * + * @api private + */ + +Transport.prototype.setHeartbeatInterval = function () { + if (!this.heartbeatInterval && this.manager.enabled('heartbeats')) { + var self = this; + + this.heartbeatInterval = setTimeout(function () { + self.heartbeat(); + self.heartbeatInterval = null; + }, this.manager.get('heartbeat interval') * 1000); + + this.log.debug('set heartbeat interval for client', this.id); + } +}; + +/** + * Clears all timeouts. + * + * @api private + */ + +Transport.prototype.clearTimeouts = function () { + this.clearCloseTimeout(); + this.clearHeartbeatTimeout(); + this.clearHeartbeatInterval(); +}; + +/** + * Sends a heartbeat + * + * @api private + */ + +Transport.prototype.heartbeat = function () { + if (this.open) { + this.log.debug('emitting heartbeat for client', this.id); + this.packet({ type: 'heartbeat' }); + this.setHeartbeatTimeout(); + } + + return this; +}; + +/** + * Handles a message. + * + * @param {Object} packet object + * @api private + */ + +Transport.prototype.onMessage = function (packet) { + var current = this.manager.transports[this.id]; + + if ('heartbeat' == packet.type) { + this.log.debug('got heartbeat packet'); + + if (current && current.open) { + current.onHeartbeatClear(); + } else { + this.store.publish('heartbeat-clear:' + this.id); + } + } else { + if ('disconnect' == packet.type && packet.endpoint == '') { + this.log.debug('got disconnection packet'); + + if (current) { + current.onForcedDisconnect(); + } else { + this.store.publish('disconnect-force:' + this.id); + } + + return; + } + + if (packet.id && packet.ack != 'data') { + this.log.debug('acknowledging packet automatically'); + + var ack = parser.encodePacket({ + type: 'ack' + , ackId: packet.id + , endpoint: packet.endpoint || '' + }); + + if (current && current.open) { + current.onDispatch(ack); + } else { + this.manager.onClientDispatch(this.id, ack); + this.store.publish('dispatch:' + this.id, ack); + } + } + + // handle packet locally or publish it + if (current) { + this.manager.onClientMessage(this.id, packet); + } else { + this.store.publish('message:' + this.id, packet); + } + } +}; + +/** + * Clears the heartbeat interval + * + * @api private + */ + +Transport.prototype.clearHeartbeatInterval = function () { + if (this.heartbeatInterval && this.manager.enabled('heartbeats')) { + clearTimeout(this.heartbeatInterval); + this.heartbeatInterval = null; + this.log.debug('cleared heartbeat interval for client', this.id); + } +}; + +/** + * Finishes the connection and makes sure client doesn't reopen + * + * @api private + */ + +Transport.prototype.disconnect = function (reason) { + this.packet({ type: 'disconnect' }); + this.end(reason); + + return this; +}; + +/** + * Closes the connection. + * + * @api private + */ + +Transport.prototype.close = function () { + if (this.open) { + this.doClose(); + this.onClose(); + } +}; + +/** + * Called upon a connection close. + * + * @api private + */ + +Transport.prototype.onClose = function () { + if (this.open) { + this.setCloseTimeout(); + this.clearHandlers(); + this.open = false; + this.manager.onClose(this.id); + this.store.publish('close', this.id); + } +}; + +/** + * Cleans up the connection, considers the client disconnected. + * + * @api private + */ + +Transport.prototype.end = function (reason) { + if (!this.disconnected) { + this.log.info('transport end'); + + var local = this.manager.transports[this.id]; + + this.close(); + this.clearTimeouts(); + this.disconnected = true; + + if (local) { + this.manager.onClientDisconnect(this.id, reason, true); + } else { + this.store.publish('disconnect:' + this.id, reason); + } + } +}; + +/** + * Signals that the transport should pause and buffer data. + * + * @api public + */ + +Transport.prototype.discard = function () { + this.log.debug('discarding transport'); + this.discarded = true; + this.clearTimeouts(); + this.clearHandlers(); + + return this; +}; + +/** + * Writes an error packet with the specified reason and advice. + * + * @param {Number} advice + * @param {Number} reason + * @api public + */ + +Transport.prototype.error = function (reason, advice) { + this.packet({ + type: 'error' + , reason: reason + , advice: advice + }); + + this.log.warn(reason, advice ? ('client should ' + advice) : ''); + this.end('error'); +}; + +/** + * Write a packet. + * + * @api public + */ + +Transport.prototype.packet = function (obj) { + return this.write(parser.encodePacket(obj)); +}; + +/** + * Writes a volatile message. + * + * @api private + */ + +Transport.prototype.writeVolatile = function (msg) { + if (this.open) { + if (this.drained) { + this.write(msg); + } else { + this.log.debug('ignoring volatile packet, buffer not drained'); + } + } else { + this.log.debug('ignoring volatile packet, transport not open'); + } +}; diff --git a/node_modules/socket.io/lib/transports/flashsocket.js b/node_modules/socket.io/lib/transports/flashsocket.js new file mode 100644 index 0000000..d79363d --- /dev/null +++ b/node_modules/socket.io/lib/transports/flashsocket.js @@ -0,0 +1,106 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ +var WebSocket = require('./websocket'); + +/** + * Export the constructor. + */ + +exports = module.exports = FlashSocket; + +/** + * The FlashSocket transport is just a proxy + * for WebSocket connections. + * + * @api public + */ + +function FlashSocket (mng, data, req) { + return WebSocket.call(this, mng, data, req); +} + +/** + * Inherits from WebSocket. + */ + +FlashSocket.prototype.__proto__ = WebSocket.prototype; + +/** + * Transport name + * + * @api public + */ + +FlashSocket.prototype.name = 'flashsocket'; + +/** + * Listens for new configuration changes of the Manager + * this way we can enable and disable the flash server. + * + * @param {Manager} Manager instance. + * @api private + */ + + +FlashSocket.init = function (manager) { + var server; + function create () { + server = require('policyfile').createServer({ + log: function(msg){ + manager.log.info(msg.toLowerCase()); + } + }, manager.get('origins')); + + server.on('close', function (e) { + server = null; + }); + + server.listen(manager.get('flash policy port'), manager.server); + + manager.flashPolicyServer = server; + } + + // listen for origin changes, so we can update the server + manager.on('set:origins', function (value, key) { + if (!server) return; + + // update the origins and compile a new response buffer + server.origins = Array.isArray(value) ? value : [value]; + server.compile(); + }); + + // destory the server and create a new server + manager.on('set:flash policy port', function (value, key) { + var transports = manager.get('transports'); + if (~transports.indexOf('flashsocket')) { + if (server) { + if (server.port === value) return; + // destroy the server and rebuild it on a new port + try { + server.close(); + } + catch (e) { /* ignore exception. could e.g. be that the server isn't started yet */ } + } + create(); + } + }); + + // only start the server + manager.on('set:transports', function (value, key){ + if (!server && ~manager.get('transports').indexOf('flashsocket')) { + create(); + } + }); + // check if we need to initialize at start + if (~manager.get('transports').indexOf('flashsocket')){ + create(); + } +}; diff --git a/node_modules/socket.io/lib/transports/htmlfile.js b/node_modules/socket.io/lib/transports/htmlfile.js new file mode 100644 index 0000000..e8709a3 --- /dev/null +++ b/node_modules/socket.io/lib/transports/htmlfile.js @@ -0,0 +1,82 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var HTTPTransport = require('./http'); + +/** + * Export the constructor. + */ + +exports = module.exports = HTMLFile; + +/** + * HTMLFile transport constructor. + * + * @api public + */ + +function HTMLFile (mng, data, req) { + HTTPTransport.call(this, mng, data, req); +}; + +/** + * Inherits from Transport. + */ + +HTMLFile.prototype.__proto__ = HTTPTransport.prototype; + +/** + * Transport name + * + * @api public + */ + +HTMLFile.prototype.name = 'htmlfile'; + +/** + * Handles the request. + * + * @api private + */ + +HTMLFile.prototype.handleRequest = function (req) { + HTTPTransport.prototype.handleRequest.call(this, req); + + if (req.method == 'GET') { + req.res.writeHead(200, { + 'Content-Type': 'text/html; charset=UTF-8' + , 'Connection': 'keep-alive' + , 'Transfer-Encoding': 'chunked' + }); + + req.res.write( + '' + + '' + + new Array(174).join(' ') + ); + } +}; + +/** + * Performs the write. + * + * @api private + */ + +HTMLFile.prototype.write = function (data) { + data = ''; + + if (this.response.write(data)) { + this.drained = true; + } + + this.log.debug(this.name + ' writing', data); +}; diff --git a/node_modules/socket.io/lib/transports/http-polling.js b/node_modules/socket.io/lib/transports/http-polling.js new file mode 100644 index 0000000..c71fc9c --- /dev/null +++ b/node_modules/socket.io/lib/transports/http-polling.js @@ -0,0 +1,135 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var HTTPTransport = require('./http'); + +/** + * Exports the constructor. + */ + +exports = module.exports = HTTPPolling; + +/** + * HTTP polling constructor. + * + * @api public. + */ + +function HTTPPolling (mng, data, req) { + HTTPTransport.call(this, mng, data, req); +}; + +/** + * Inherits from HTTPTransport. + * + * @api public. + */ + +HTTPPolling.prototype.__proto__ = HTTPTransport.prototype; + +/** + * Transport name + * + * @api public + */ + +HTTPPolling.prototype.name = 'httppolling'; + +/** + * Removes heartbeat timeouts for polling. + */ + +HTTPPolling.prototype.setHeartbeatInterval = function () { + return this; +}; + +/** + * Handles a request + * + * @api private + */ + +HTTPPolling.prototype.handleRequest = function (req) { + HTTPTransport.prototype.handleRequest.call(this, req); + + if (req.method == 'GET') { + var self = this; + + this.pollTimeout = setTimeout(function () { + self.packet({ type: 'noop' }); + self.log.debug(self.name + ' closed due to exceeded duration'); + }, this.manager.get('polling duration') * 1000); + + this.log.debug('setting poll timeout'); + } +}; + +/** + * Clears polling timeout + * + * @api private + */ + +HTTPPolling.prototype.clearPollTimeout = function () { + if (this.pollTimeout) { + clearTimeout(this.pollTimeout); + this.pollTimeout = null; + this.log.debug('clearing poll timeout'); + } + + return this; +}; + +/** + * Override clear timeouts to clear the poll timeout + * + * @api private + */ + +HTTPPolling.prototype.clearTimeouts = function () { + HTTPTransport.prototype.clearTimeouts.call(this); + + this.clearPollTimeout(); +}; + +/** + * doWrite to clear poll timeout + * + * @api private + */ + +HTTPPolling.prototype.doWrite = function () { + this.clearPollTimeout(); +}; + +/** + * Performs a write. + * + * @api private. + */ + +HTTPPolling.prototype.write = function (data, close) { + this.doWrite(data); + this.response.end(); + this.onClose(); +}; + +/** + * Override end. + * + * @api private + */ + +HTTPPolling.prototype.end = function () { + this.clearPollTimeout(); + return HTTPTransport.prototype.end.call(this); +}; + diff --git a/node_modules/socket.io/lib/transports/http.js b/node_modules/socket.io/lib/transports/http.js new file mode 100644 index 0000000..d5be006 --- /dev/null +++ b/node_modules/socket.io/lib/transports/http.js @@ -0,0 +1,111 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var Transport = require('../transport') + , parser = require('../parser') + , qs = require('querystring'); + +/** + * Export the constructor. + */ + +exports = module.exports = HTTPTransport; + +/** + * HTTP interface constructor. For all non-websocket transports. + * + * @api public + */ + +function HTTPTransport (mng, data, req) { + Transport.call(this, mng, data, req); +}; + +/** + * Inherits from Transport. + */ + +HTTPTransport.prototype.__proto__ = Transport.prototype; + +/** + * Handles a request. + * + * @api private + */ + +HTTPTransport.prototype.handleRequest = function (req) { + if (req.method == 'POST') { + var buffer = '' + , res = req.res + , origin = req.headers.origin + , headers = { 'Content-Length': 1 } + , self = this; + + req.on('data', function (data) { + buffer += data; + }); + + req.on('end', function () { + res.writeHead(200, headers); + res.end('1'); + + self.onData(self.postEncoded ? qs.parse(buffer).d : buffer); + }); + + if (origin) { + // https://developer.mozilla.org/En/HTTP_Access_Control + headers['Access-Control-Allow-Origin'] = '*'; + + if (req.headers.cookie) { + headers['Access-Control-Allow-Credentials'] = 'true'; + } + } + } else { + this.response = req.res; + + Transport.prototype.handleRequest.call(this, req); + } +}; + +/** + * Handles data payload. + * + * @api private + */ + +HTTPTransport.prototype.onData = function (data) { + var messages = parser.decodePayload(data); + this.log.debug(this.name + ' received data packet', data); + + for (var i = 0, l = messages.length; i < l; i++) { + this.onMessage(messages[i]); + } +}; + +/** + * Closes the request-response cycle + * + * @api private + */ + +HTTPTransport.prototype.doClose = function () { + this.response.end(); +}; + +/** + * Writes a payload of messages + * + * @api private + */ + +HTTPTransport.prototype.payload = function (msgs) { + this.write(parser.encodePayload(msgs)); +}; diff --git a/node_modules/socket.io/lib/transports/index.js b/node_modules/socket.io/lib/transports/index.js new file mode 100644 index 0000000..b865559 --- /dev/null +++ b/node_modules/socket.io/lib/transports/index.js @@ -0,0 +1,12 @@ + +/** + * Export transports. + */ + +module.exports = { + websocket: require('./websocket') + , flashsocket: require('./flashsocket') + , htmlfile: require('./htmlfile') + , 'xhr-polling': require('./xhr-polling') + , 'jsonp-polling': require('./jsonp-polling') +}; diff --git a/node_modules/socket.io/lib/transports/jsonp-polling.js b/node_modules/socket.io/lib/transports/jsonp-polling.js new file mode 100644 index 0000000..83d11b8 --- /dev/null +++ b/node_modules/socket.io/lib/transports/jsonp-polling.js @@ -0,0 +1,96 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var HTTPPolling = require('./http-polling'); + +/** + * Export the constructor. + */ + +exports = module.exports = JSONPPolling; + +/** + * JSON-P polling transport. + * + * @api public + */ + +function JSONPPolling (mng, data, req) { + HTTPPolling.call(this, mng, data, req); + + this.head = 'io.j[0]('; + this.foot = ');'; + + if (data.query.i) { + this.head = 'io.j[' + data.query.i + ']('; + } +}; + +/** + * Inherits from Transport. + */ + +JSONPPolling.prototype.__proto__ = HTTPPolling.prototype; + +/** + * Transport name + * + * @api public + */ + +JSONPPolling.prototype.name = 'jsonppolling'; + +/** + * Make sure POST are decoded. + */ + +JSONPPolling.prototype.postEncoded = true; + +/** + * Handles incoming data. + * Due to a bug in \n handling by browsers, we expect a JSONified string. + * + * @api private + */ + +JSONPPolling.prototype.onData = function (data) { + try { + data = JSON.parse(data); + } catch (e) { + this.error('parse', 'reconnect'); + return; + } + + HTTPPolling.prototype.onData.call(this, data); +}; + +/** + * Performs the write. + * + * @api private + */ + +JSONPPolling.prototype.doWrite = function (data) { + HTTPPolling.prototype.doWrite.call(this); + + var data = data === undefined + ? '' : this.head + JSON.stringify(data) + this.foot; + + this.response.writeHead(200, { + 'Content-Type': 'text/javascript; charset=UTF-8' + , 'Content-Length': Buffer.byteLength(data) + , 'Connection': 'Keep-Alive' + , 'X-XSS-Protection': '0' + }); + + this.response.write(data); + this.log.debug(this.name + ' writing', data); +}; diff --git a/node_modules/socket.io/lib/transports/websocket.js b/node_modules/socket.io/lib/transports/websocket.js new file mode 100644 index 0000000..78a4304 --- /dev/null +++ b/node_modules/socket.io/lib/transports/websocket.js @@ -0,0 +1,36 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var protocolVersions = require('./websocket/'); + +/** + * Export the constructor. + */ + +exports = module.exports = WebSocket; + +/** + * HTTP interface constructor. Interface compatible with all transports that + * depend on request-response cycles. + * + * @api public + */ + +function WebSocket (mng, data, req) { + var transport + , version = req.headers['sec-websocket-version']; + if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') { + transport = new protocolVersions[version](mng, data, req); + } + else transport = new protocolVersions['default'](mng, data, req); + if (typeof this.name !== 'undefined') transport.name = this.name; + return transport; +}; diff --git a/node_modules/socket.io/lib/transports/websocket/default.js b/node_modules/socket.io/lib/transports/websocket/default.js new file mode 100644 index 0000000..524e34e --- /dev/null +++ b/node_modules/socket.io/lib/transports/websocket/default.js @@ -0,0 +1,358 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var Transport = require('../../transport') + , EventEmitter = process.EventEmitter + , crypto = require('crypto') + , parser = require('../../parser'); + +/** + * Export the constructor. + */ + +exports = module.exports = WebSocket; + +/** + * HTTP interface constructor. Interface compatible with all transports that + * depend on request-response cycles. + * + * @api public + */ + +function WebSocket (mng, data, req) { + // parser + var self = this; + + this.parser = new Parser(); + this.parser.on('data', function (packet) { + self.log.debug(self.name + ' received data packet', packet); + self.onMessage(parser.decodePacket(packet)); + }); + this.parser.on('close', function () { + self.end(); + }); + this.parser.on('error', function () { + self.end(); + }); + + Transport.call(this, mng, data, req); +}; + +/** + * Inherits from Transport. + */ + +WebSocket.prototype.__proto__ = Transport.prototype; + +/** + * Transport name + * + * @api public + */ + +WebSocket.prototype.name = 'websocket'; + +/** + * Websocket draft version + * + * @api public + */ + +WebSocket.prototype.protocolVersion = 'hixie-76'; + +/** + * Called when the socket connects. + * + * @api private + */ + +WebSocket.prototype.onSocketConnect = function () { + var self = this; + + this.socket.setNoDelay(true); + + this.buffer = true; + this.buffered = []; + + if (this.req.headers.upgrade !== 'WebSocket') { + this.log.warn(this.name + ' connection invalid'); + this.end(); + return; + } + + var origin = this.req.headers.origin + , location = (this.socket.encrypted ? 'wss' : 'ws') + + '://' + this.req.headers.host + this.req.url + , waitingForNonce = false; + + if (this.req.headers['sec-websocket-key1']) { + // If we don't have the nonce yet, wait for it (HAProxy compatibility). + if (! (this.req.head && this.req.head.length >= 8)) { + waitingForNonce = true; + } + + var headers = [ + 'HTTP/1.1 101 WebSocket Protocol Handshake' + , 'Upgrade: WebSocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Origin: ' + origin + , 'Sec-WebSocket-Location: ' + location + ]; + + if (this.req.headers['sec-websocket-protocol']){ + headers.push('Sec-WebSocket-Protocol: ' + + this.req.headers['sec-websocket-protocol']); + } + } else { + var headers = [ + 'HTTP/1.1 101 Web Socket Protocol Handshake' + , 'Upgrade: WebSocket' + , 'Connection: Upgrade' + , 'WebSocket-Origin: ' + origin + , 'WebSocket-Location: ' + location + ]; + } + + try { + this.socket.write(headers.concat('', '').join('\r\n')); + this.socket.setTimeout(0); + this.socket.setNoDelay(true); + this.socket.setEncoding('utf8'); + } catch (e) { + this.end(); + return; + } + + if (waitingForNonce) { + this.socket.setEncoding('binary'); + } else if (this.proveReception(headers)) { + self.flush(); + } + + var headBuffer = ''; + + this.socket.on('data', function (data) { + if (waitingForNonce) { + headBuffer += data; + + if (headBuffer.length < 8) { + return; + } + + // Restore the connection to utf8 encoding after receiving the nonce + self.socket.setEncoding('utf8'); + waitingForNonce = false; + + // Stuff the nonce into the location where it's expected to be + self.req.head = headBuffer.substr(0, 8); + headBuffer = ''; + + if (self.proveReception(headers)) { + self.flush(); + } + + return; + } + + self.parser.add(data); + }); +}; + +/** + * Writes to the socket. + * + * @api private + */ + +WebSocket.prototype.write = function (data) { + if (this.open) { + this.drained = false; + + if (this.buffer) { + this.buffered.push(data); + return this; + } + + var length = Buffer.byteLength(data) + , buffer = new Buffer(2 + length); + + buffer.write('\x00', 'binary'); + buffer.write(data, 1, 'utf8'); + buffer.write('\xff', 1 + length, 'binary'); + + try { + if (this.socket.write(buffer)) { + this.drained = true; + } + } catch (e) { + this.end(); + } + + this.log.debug(this.name + ' writing', data); + } +}; + +/** + * Flushes the internal buffer + * + * @api private + */ + +WebSocket.prototype.flush = function () { + this.buffer = false; + + for (var i = 0, l = this.buffered.length; i < l; i++) { + this.write(this.buffered.splice(0, 1)[0]); + } +}; + +/** + * Finishes the handshake. + * + * @api private + */ + +WebSocket.prototype.proveReception = function (headers) { + var self = this + , k1 = this.req.headers['sec-websocket-key1'] + , k2 = this.req.headers['sec-websocket-key2']; + + if (k1 && k2){ + var md5 = crypto.createHash('md5'); + + [k1, k2].forEach(function (k) { + var n = parseInt(k.replace(/[^\d]/g, '')) + , spaces = k.replace(/[^ ]/g, '').length; + + if (spaces === 0 || n % spaces !== 0){ + self.log.warn('Invalid ' + self.name + ' key: "' + k + '".'); + self.end(); + return false; + } + + n /= spaces; + + md5.update(String.fromCharCode( + n >> 24 & 0xFF, + n >> 16 & 0xFF, + n >> 8 & 0xFF, + n & 0xFF)); + }); + + md5.update(this.req.head.toString('binary')); + + try { + this.socket.write(md5.digest('binary'), 'binary'); + } catch (e) { + this.end(); + } + } + + return true; +}; + +/** + * Writes a payload. + * + * @api private + */ + +WebSocket.prototype.payload = function (msgs) { + for (var i = 0, l = msgs.length; i < l; i++) { + this.write(msgs[i]); + } + + return this; +}; + +/** + * Closes the connection. + * + * @api private + */ + +WebSocket.prototype.doClose = function () { + this.socket.end(); +}; + +/** + * WebSocket parser + * + * @api public + */ + +function Parser () { + this.buffer = ''; + this.i = 0; +}; + +/** + * Inherits from EventEmitter. + */ + +Parser.prototype.__proto__ = EventEmitter.prototype; + +/** + * Adds data to the buffer. + * + * @api public + */ + +Parser.prototype.add = function (data) { + this.buffer += data; + this.parse(); +}; + +/** + * Parses the buffer. + * + * @api private + */ + +Parser.prototype.parse = function () { + for (var i = this.i, chr, l = this.buffer.length; i < l; i++){ + chr = this.buffer[i]; + + if (this.buffer.length == 2 && this.buffer[1] == '\u0000') { + this.emit('close'); + this.buffer = ''; + this.i = 0; + return; + } + + if (i === 0){ + if (chr != '\u0000') + this.error('Bad framing. Expected null byte as first frame'); + else + continue; + } + + if (chr == '\ufffd'){ + this.emit('data', this.buffer.substr(1, i - 1)); + this.buffer = this.buffer.substr(i + 1); + this.i = 0; + return this.parse(); + } + } +}; + +/** + * Handles an error + * + * @api private + */ + +Parser.prototype.error = function (reason) { + this.buffer = ''; + this.i = 0; + this.emit('error', reason); + return this; +}; diff --git a/node_modules/socket.io/lib/transports/websocket/hybi-07-12.js b/node_modules/socket.io/lib/transports/websocket/hybi-07-12.js new file mode 100644 index 0000000..ade99dc --- /dev/null +++ b/node_modules/socket.io/lib/transports/websocket/hybi-07-12.js @@ -0,0 +1,617 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var Transport = require('../../transport') + , EventEmitter = process.EventEmitter + , crypto = require('crypto') + , url = require('url') + , parser = require('../../parser') + , util = require('../../util'); + +/** + * Export the constructor. + */ + +exports = module.exports = WebSocket; +exports.Parser = Parser; + +/** + * HTTP interface constructor. Interface compatible with all transports that + * depend on request-response cycles. + * + * @api public + */ + +function WebSocket (mng, data, req) { + // parser + var self = this; + + this.manager = mng; + this.parser = new Parser(); + this.parser.on('data', function (packet) { + self.onMessage(parser.decodePacket(packet)); + }); + this.parser.on('ping', function () { + // version 8 ping => pong + try { + self.socket.write('\u008a\u0000'); + } + catch (e) { + self.end(); + return; + } + }); + this.parser.on('close', function () { + self.end(); + }); + this.parser.on('error', function (reason) { + self.log.warn(self.name + ' parser error: ' + reason); + self.end(); + }); + + Transport.call(this, mng, data, req); +}; + +/** + * Inherits from Transport. + */ + +WebSocket.prototype.__proto__ = Transport.prototype; + +/** + * Transport name + * + * @api public + */ + +WebSocket.prototype.name = 'websocket'; + +/** + * Websocket draft version + * + * @api public + */ + +WebSocket.prototype.protocolVersion = '07-12'; + +/** + * Called when the socket connects. + * + * @api private + */ + +WebSocket.prototype.onSocketConnect = function () { + var self = this; + + if (typeof this.req.headers.upgrade === 'undefined' || + this.req.headers.upgrade.toLowerCase() !== 'websocket') { + this.log.warn(this.name + ' connection invalid'); + this.end(); + return; + } + + var origin = this.req.headers['sec-websocket-origin'] + , location = (this.socket.encrypted ? 'wss' : 'ws') + + '://' + this.req.headers.host + this.req.url; + + if (!this.verifyOrigin(origin)) { + this.log.warn(this.name + ' connection invalid: origin mismatch'); + this.end(); + return; + } + + if (!this.req.headers['sec-websocket-key']) { + this.log.warn(this.name + ' connection invalid: received no key'); + this.end(); + return; + } + + // calc key + var key = this.req.headers['sec-websocket-key']; + var shasum = crypto.createHash('sha1'); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + key = shasum.digest('base64'); + + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: websocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Accept: ' + key + ]; + + try { + this.socket.write(headers.concat('', '').join('\r\n')); + this.socket.setTimeout(0); + this.socket.setNoDelay(true); + } catch (e) { + this.end(); + return; + } + + this.socket.on('data', function (data) { + self.parser.add(data); + }); +}; + +/** + * Verifies the origin of a request. + * + * @api private + */ + +WebSocket.prototype.verifyOrigin = function (origin) { + var origins = this.manager.get('origins'); + + if (origin === 'null') origin = '*'; + + if (origins.indexOf('*:*') !== -1) { + return true; + } + + if (origin) { + try { + var parts = url.parse(origin); + var ok = + ~origins.indexOf(parts.hostname + ':' + parts.port) || + ~origins.indexOf(parts.hostname + ':*') || + ~origins.indexOf('*:' + parts.port); + if (!ok) this.log.warn('illegal origin: ' + origin); + return ok; + } catch (ex) { + this.log.warn('error parsing origin'); + } + } + else { + this.log.warn('origin missing from websocket call, yet required by config'); + } + return false; +}; + +/** + * Writes to the socket. + * + * @api private + */ + +WebSocket.prototype.write = function (data) { + if (this.open) { + var buf = this.frame(0x81, data); + try { + this.socket.write(buf, 'binary'); + } + catch (e) { + this.end(); + return; + } + this.log.debug(this.name + ' writing', data); + } +}; + +/** + * Writes a payload. + * + * @api private + */ + +WebSocket.prototype.payload = function (msgs) { + for (var i = 0, l = msgs.length; i < l; i++) { + this.write(msgs[i]); + } + + return this; +}; + +/** + * Frame server-to-client output as a text packet. + * + * @api private + */ + +WebSocket.prototype.frame = function (opcode, str) { + var dataBuffer = new Buffer(str) + , dataLength = dataBuffer.length + , startOffset = 2 + , secondByte = dataLength; + if (dataLength > 65536) { + startOffset = 10; + secondByte = 127; + } + else if (dataLength > 125) { + startOffset = 4; + secondByte = 126; + } + var outputBuffer = new Buffer(dataLength + startOffset); + outputBuffer[0] = opcode; + outputBuffer[1] = secondByte; + dataBuffer.copy(outputBuffer, startOffset); + switch (secondByte) { + case 126: + outputBuffer[2] = dataLength >>> 8; + outputBuffer[3] = dataLength % 256; + break; + case 127: + var l = dataLength; + for (var i = 1; i <= 8; ++i) { + outputBuffer[startOffset - i] = l & 0xff; + l >>>= 8; + } + } + return outputBuffer; +}; + +/** + * Closes the connection. + * + * @api private + */ + +WebSocket.prototype.doClose = function () { + this.socket.end(); +}; + +/** + * WebSocket parser + * + * @api public + */ + +function Parser () { + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0 + }; + this.overflow = null; + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.currentMessage = ''; + + var self = this; + this.opcodeHandlers = { + // text + '1': function(data) { + var finish = function(mask, data) { + self.currentMessage += self.unmask(mask, data); + if (self.state.lastFragment) { + self.emit('data', self.currentMessage); + self.currentMessage = ''; + } + self.endPacket(); + } + + var expectData = function(length) { + if (self.state.masked) { + self.expect('Mask', 4, function(data) { + var mask = data; + self.expect('Data', length, function(data) { + finish(mask, data); + }); + }); + } + else { + self.expect('Data', length, function(data) { + finish(null, data); + }); + } + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + expectData(firstLength); + } + else if (firstLength == 126) { + self.expect('Length', 2, function(data) { + expectData(util.unpack(data)); + }); + } + else if (firstLength == 127) { + self.expect('Length', 8, function(data) { + if (util.unpack(data.slice(0, 4)) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported'); + return; + } + var lengthBytes = data.slice(4); // note: cap to 32 bit length + expectData(util.unpack(data)); + }); + } + }, + // binary + '2': function(data) { + var finish = function(mask, data) { + if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list + self.currentMessage.push(self.unmask(mask, data, true)); + if (self.state.lastFragment) { + self.emit('binary', self.concatBuffers(self.currentMessage)); + self.currentMessage = ''; + } + self.endPacket(); + } + + var expectData = function(length) { + if (self.state.masked) { + self.expect('Mask', 4, function(data) { + var mask = data; + self.expect('Data', length, function(data) { + finish(mask, data); + }); + }); + } + else { + self.expect('Data', length, function(data) { + finish(null, data); + }); + } + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + expectData(firstLength); + } + else if (firstLength == 126) { + self.expect('Length', 2, function(data) { + expectData(util.unpack(data)); + }); + } + else if (firstLength == 127) { + self.expect('Length', 8, function(data) { + if (util.unpack(data.slice(0, 4)) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported'); + return; + } + var lengthBytes = data.slice(4); // note: cap to 32 bit length + expectData(util.unpack(data)); + }); + } + }, + // close + '8': function(data) { + self.emit('close'); + self.reset(); + }, + // ping + '9': function(data) { + if (self.state.lastFragment == false) { + self.error('fragmented ping is not supported'); + return; + } + + var finish = function(mask, data) { + self.emit('ping', self.unmask(mask, data)); + self.endPacket(); + } + + var expectData = function(length) { + if (self.state.masked) { + self.expect('Mask', 4, function(data) { + var mask = data; + self.expect('Data', length, function(data) { + finish(mask, data); + }); + }); + } + else { + self.expect('Data', length, function(data) { + finish(null, data); + }); + } + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength == 0) { + finish(null, null); + } + else if (firstLength < 126) { + expectData(firstLength); + } + else if (firstLength == 126) { + self.expect('Length', 2, function(data) { + expectData(util.unpack(data)); + }); + } + else if (firstLength == 127) { + self.expect('Length', 8, function(data) { + expectData(util.unpack(data)); + }); + } + } + } + + this.expect('Opcode', 2, this.processPacket); +}; + +/** + * Inherits from EventEmitter. + */ + +Parser.prototype.__proto__ = EventEmitter.prototype; + +/** + * Add new data to the parser. + * + * @api public + */ + +Parser.prototype.add = function(data) { + if (this.expectBuffer == null) { + this.addToOverflow(data); + return; + } + var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset); + data.copy(this.expectBuffer, this.expectOffset, 0, toRead); + this.expectOffset += toRead; + if (toRead < data.length) { + // at this point the overflow buffer shouldn't at all exist + this.overflow = new Buffer(data.length - toRead); + data.copy(this.overflow, 0, toRead, toRead + this.overflow.length); + } + if (this.expectOffset == this.expectBuffer.length) { + var bufferForHandler = this.expectBuffer; + this.expectBuffer = null; + this.expectOffset = 0; + this.expectHandler.call(this, bufferForHandler); + } +} + +/** + * Adds a piece of data to the overflow. + * + * @api private + */ + +Parser.prototype.addToOverflow = function(data) { + if (this.overflow == null) this.overflow = data; + else { + var prevOverflow = this.overflow; + this.overflow = new Buffer(this.overflow.length + data.length); + prevOverflow.copy(this.overflow, 0); + data.copy(this.overflow, prevOverflow.length); + } +} + +/** + * Waits for a certain amount of bytes to be available, then fires a callback. + * + * @api private + */ + +Parser.prototype.expect = function(what, length, handler) { + this.expectBuffer = new Buffer(length); + this.expectOffset = 0; + this.expectHandler = handler; + if (this.overflow != null) { + var toOverflow = this.overflow; + this.overflow = null; + this.add(toOverflow); + } +} + +/** + * Start processing a new packet. + * + * @api private + */ + +Parser.prototype.processPacket = function (data) { + if ((data[0] & 0x70) != 0) this.error('reserved fields must be empty'); + this.state.lastFragment = (data[0] & 0x80) == 0x80; + this.state.masked = (data[1] & 0x80) == 0x80; + var opcode = data[0] & 0xf; + if (opcode == 0) { + // continuation frame + this.state.opcode = this.state.activeFragmentedOperation; + if (!(this.state.opcode == 1 || this.state.opcode == 2)) { + this.error('continuation frame cannot follow current opcode') + return; + } + } + else { + this.state.opcode = opcode; + if (this.state.lastFragment === false) { + this.state.activeFragmentedOperation = opcode; + } + } + var handler = this.opcodeHandlers[this.state.opcode]; + if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode); + else handler(data); +} + +/** + * Endprocessing a packet. + * + * @api private + */ + +Parser.prototype.endPacket = function() { + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) { + // end current fragmented operation + this.state.activeFragmentedOperation = null; + } + this.state.lastFragment = false; + this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; + this.state.masked = false; + this.expect('Opcode', 2, this.processPacket); +} + +/** + * Reset the parser state. + * + * @api private + */ + +Parser.prototype.reset = function() { + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0 + }; + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.overflow = null; + this.currentMessage = ''; +} + +/** + * Unmask received data. + * + * @api private + */ + +Parser.prototype.unmask = function (mask, buf, binary) { + if (mask != null) { + for (var i = 0, ll = buf.length; i < ll; i++) { + buf[i] ^= mask[i % 4]; + } + } + if (binary) return buf; + return buf != null ? buf.toString('utf8') : ''; +} + +/** + * Concatenates a list of buffers. + * + * @api private + */ + +Parser.prototype.concatBuffers = function(buffers) { + var length = 0; + for (var i = 0, l = buffers.length; i < l; ++i) { + length += buffers[i].length; + } + var mergedBuffer = new Buffer(length); + var offset = 0; + for (var i = 0, l = buffers.length; i < l; ++i) { + buffers[i].copy(mergedBuffer, offset); + offset += buffers[i].length; + } + return mergedBuffer; +} + +/** + * Handles an error + * + * @api private + */ + +Parser.prototype.error = function (reason) { + this.reset(); + this.emit('error', reason); + return this; +}; diff --git a/node_modules/socket.io/lib/transports/websocket/hybi-16.js b/node_modules/socket.io/lib/transports/websocket/hybi-16.js new file mode 100644 index 0000000..03b0183 --- /dev/null +++ b/node_modules/socket.io/lib/transports/websocket/hybi-16.js @@ -0,0 +1,617 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var Transport = require('../../transport') + , EventEmitter = process.EventEmitter + , crypto = require('crypto') + , url = require('url') + , parser = require('../../parser') + , util = require('../../util'); + +/** + * Export the constructor. + */ + +exports = module.exports = WebSocket; +exports.Parser = Parser; + +/** + * HTTP interface constructor. Interface compatible with all transports that + * depend on request-response cycles. + * + * @api public + */ + +function WebSocket (mng, data, req) { + // parser + var self = this; + + this.manager = mng; + this.parser = new Parser(); + this.parser.on('data', function (packet) { + self.onMessage(parser.decodePacket(packet)); + }); + this.parser.on('ping', function () { + // version 8 ping => pong + try { + self.socket.write('\u008a\u0000'); + } + catch (e) { + self.end(); + return; + } + }); + this.parser.on('close', function () { + self.end(); + }); + this.parser.on('error', function (reason) { + self.log.warn(self.name + ' parser error: ' + reason); + self.end(); + }); + + Transport.call(this, mng, data, req); +}; + +/** + * Inherits from Transport. + */ + +WebSocket.prototype.__proto__ = Transport.prototype; + +/** + * Transport name + * + * @api public + */ + +WebSocket.prototype.name = 'websocket'; + +/** + * Websocket draft version + * + * @api public + */ + +WebSocket.prototype.protocolVersion = '16'; + +/** + * Called when the socket connects. + * + * @api private + */ + +WebSocket.prototype.onSocketConnect = function () { + var self = this; + + if (typeof this.req.headers.upgrade === 'undefined' || + this.req.headers.upgrade.toLowerCase() !== 'websocket') { + this.log.warn(this.name + ' connection invalid'); + this.end(); + return; + } + + var origin = this.req.headers['origin'] + , location = (this.socket.encrypted ? 'wss' : 'ws') + + '://' + this.req.headers.host + this.req.url; + + if (!this.verifyOrigin(origin)) { + this.log.warn(this.name + ' connection invalid: origin mismatch'); + this.end(); + return; + } + + if (!this.req.headers['sec-websocket-key']) { + this.log.warn(this.name + ' connection invalid: received no key'); + this.end(); + return; + } + + // calc key + var key = this.req.headers['sec-websocket-key']; + var shasum = crypto.createHash('sha1'); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + key = shasum.digest('base64'); + + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: websocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Accept: ' + key + ]; + + try { + this.socket.write(headers.concat('', '').join('\r\n')); + this.socket.setTimeout(0); + this.socket.setNoDelay(true); + } catch (e) { + this.end(); + return; + } + + this.socket.on('data', function (data) { + self.parser.add(data); + }); +}; + +/** + * Verifies the origin of a request. + * + * @api private + */ + +WebSocket.prototype.verifyOrigin = function (origin) { + var origins = this.manager.get('origins'); + + if (origin === 'null') origin = '*'; + + if (origins.indexOf('*:*') !== -1) { + return true; + } + + if (origin) { + try { + var parts = url.parse(origin); + var ok = + ~origins.indexOf(parts.hostname + ':' + parts.port) || + ~origins.indexOf(parts.hostname + ':*') || + ~origins.indexOf('*:' + parts.port); + if (!ok) this.log.warn('illegal origin: ' + origin); + return ok; + } catch (ex) { + this.log.warn('error parsing origin'); + } + } + else { + this.log.warn('origin missing from websocket call, yet required by config'); + } + return false; +}; + +/** + * Writes to the socket. + * + * @api private + */ + +WebSocket.prototype.write = function (data) { + if (this.open) { + var buf = this.frame(0x81, data); + try { + this.socket.write(buf, 'binary'); + } + catch (e) { + this.end(); + return; + } + this.log.debug(this.name + ' writing', data); + } +}; + +/** + * Writes a payload. + * + * @api private + */ + +WebSocket.prototype.payload = function (msgs) { + for (var i = 0, l = msgs.length; i < l; i++) { + this.write(msgs[i]); + } + + return this; +}; + +/** + * Frame server-to-client output as a text packet. + * + * @api private + */ + +WebSocket.prototype.frame = function (opcode, str) { + var dataBuffer = new Buffer(str) + , dataLength = dataBuffer.length + , startOffset = 2 + , secondByte = dataLength; + if (dataLength > 65536) { + startOffset = 10; + secondByte = 127; + } + else if (dataLength > 125) { + startOffset = 4; + secondByte = 126; + } + var outputBuffer = new Buffer(dataLength + startOffset); + outputBuffer[0] = opcode; + outputBuffer[1] = secondByte; + dataBuffer.copy(outputBuffer, startOffset); + switch (secondByte) { + case 126: + outputBuffer[2] = dataLength >>> 8; + outputBuffer[3] = dataLength % 256; + break; + case 127: + var l = dataLength; + for (var i = 1; i <= 8; ++i) { + outputBuffer[startOffset - i] = l & 0xff; + l >>>= 8; + } + } + return outputBuffer; +}; + +/** + * Closes the connection. + * + * @api private + */ + +WebSocket.prototype.doClose = function () { + this.socket.end(); +}; + +/** + * WebSocket parser + * + * @api public + */ + +function Parser () { + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0 + }; + this.overflow = null; + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.currentMessage = ''; + + var self = this; + this.opcodeHandlers = { + // text + '1': function(data) { + var finish = function(mask, data) { + self.currentMessage += self.unmask(mask, data); + if (self.state.lastFragment) { + self.emit('data', self.currentMessage); + self.currentMessage = ''; + } + self.endPacket(); + } + + var expectData = function(length) { + if (self.state.masked) { + self.expect('Mask', 4, function(data) { + var mask = data; + self.expect('Data', length, function(data) { + finish(mask, data); + }); + }); + } + else { + self.expect('Data', length, function(data) { + finish(null, data); + }); + } + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + expectData(firstLength); + } + else if (firstLength == 126) { + self.expect('Length', 2, function(data) { + expectData(util.unpack(data)); + }); + } + else if (firstLength == 127) { + self.expect('Length', 8, function(data) { + if (util.unpack(data.slice(0, 4)) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported'); + return; + } + var lengthBytes = data.slice(4); // note: cap to 32 bit length + expectData(util.unpack(data)); + }); + } + }, + // binary + '2': function(data) { + var finish = function(mask, data) { + if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list + self.currentMessage.push(self.unmask(mask, data, true)); + if (self.state.lastFragment) { + self.emit('binary', self.concatBuffers(self.currentMessage)); + self.currentMessage = ''; + } + self.endPacket(); + } + + var expectData = function(length) { + if (self.state.masked) { + self.expect('Mask', 4, function(data) { + var mask = data; + self.expect('Data', length, function(data) { + finish(mask, data); + }); + }); + } + else { + self.expect('Data', length, function(data) { + finish(null, data); + }); + } + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + expectData(firstLength); + } + else if (firstLength == 126) { + self.expect('Length', 2, function(data) { + expectData(util.unpack(data)); + }); + } + else if (firstLength == 127) { + self.expect('Length', 8, function(data) { + if (util.unpack(data.slice(0, 4)) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported'); + return; + } + var lengthBytes = data.slice(4); // note: cap to 32 bit length + expectData(util.unpack(data)); + }); + } + }, + // close + '8': function(data) { + self.emit('close'); + self.reset(); + }, + // ping + '9': function(data) { + if (self.state.lastFragment == false) { + self.error('fragmented ping is not supported'); + return; + } + + var finish = function(mask, data) { + self.emit('ping', self.unmask(mask, data)); + self.endPacket(); + } + + var expectData = function(length) { + if (self.state.masked) { + self.expect('Mask', 4, function(data) { + var mask = data; + self.expect('Data', length, function(data) { + finish(mask, data); + }); + }); + } + else { + self.expect('Data', length, function(data) { + finish(null, data); + }); + } + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength == 0) { + finish(null, null); + } + else if (firstLength < 126) { + expectData(firstLength); + } + else if (firstLength == 126) { + self.expect('Length', 2, function(data) { + expectData(util.unpack(data)); + }); + } + else if (firstLength == 127) { + self.expect('Length', 8, function(data) { + expectData(util.unpack(data)); + }); + } + } + } + + this.expect('Opcode', 2, this.processPacket); +}; + +/** + * Inherits from EventEmitter. + */ + +Parser.prototype.__proto__ = EventEmitter.prototype; + +/** + * Add new data to the parser. + * + * @api public + */ + +Parser.prototype.add = function(data) { + if (this.expectBuffer == null) { + this.addToOverflow(data); + return; + } + var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset); + data.copy(this.expectBuffer, this.expectOffset, 0, toRead); + this.expectOffset += toRead; + if (toRead < data.length) { + // at this point the overflow buffer shouldn't at all exist + this.overflow = new Buffer(data.length - toRead); + data.copy(this.overflow, 0, toRead, toRead + this.overflow.length); + } + if (this.expectOffset == this.expectBuffer.length) { + var bufferForHandler = this.expectBuffer; + this.expectBuffer = null; + this.expectOffset = 0; + this.expectHandler.call(this, bufferForHandler); + } +} + +/** + * Adds a piece of data to the overflow. + * + * @api private + */ + +Parser.prototype.addToOverflow = function(data) { + if (this.overflow == null) this.overflow = data; + else { + var prevOverflow = this.overflow; + this.overflow = new Buffer(this.overflow.length + data.length); + prevOverflow.copy(this.overflow, 0); + data.copy(this.overflow, prevOverflow.length); + } +} + +/** + * Waits for a certain amount of bytes to be available, then fires a callback. + * + * @api private + */ + +Parser.prototype.expect = function(what, length, handler) { + this.expectBuffer = new Buffer(length); + this.expectOffset = 0; + this.expectHandler = handler; + if (this.overflow != null) { + var toOverflow = this.overflow; + this.overflow = null; + this.add(toOverflow); + } +} + +/** + * Start processing a new packet. + * + * @api private + */ + +Parser.prototype.processPacket = function (data) { + if ((data[0] & 0x70) != 0) this.error('reserved fields must be empty'); + this.state.lastFragment = (data[0] & 0x80) == 0x80; + this.state.masked = (data[1] & 0x80) == 0x80; + var opcode = data[0] & 0xf; + if (opcode == 0) { + // continuation frame + this.state.opcode = this.state.activeFragmentedOperation; + if (!(this.state.opcode == 1 || this.state.opcode == 2)) { + this.error('continuation frame cannot follow current opcode') + return; + } + } + else { + this.state.opcode = opcode; + if (this.state.lastFragment === false) { + this.state.activeFragmentedOperation = opcode; + } + } + var handler = this.opcodeHandlers[this.state.opcode]; + if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode); + else handler(data); +} + +/** + * Endprocessing a packet. + * + * @api private + */ + +Parser.prototype.endPacket = function() { + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) { + // end current fragmented operation + this.state.activeFragmentedOperation = null; + } + this.state.lastFragment = false; + this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; + this.state.masked = false; + this.expect('Opcode', 2, this.processPacket); +} + +/** + * Reset the parser state. + * + * @api private + */ + +Parser.prototype.reset = function() { + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0 + }; + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.overflow = null; + this.currentMessage = ''; +} + +/** + * Unmask received data. + * + * @api private + */ + +Parser.prototype.unmask = function (mask, buf, binary) { + if (mask != null) { + for (var i = 0, ll = buf.length; i < ll; i++) { + buf[i] ^= mask[i % 4]; + } + } + if (binary) return buf; + return buf != null ? buf.toString('utf8') : ''; +} + +/** + * Concatenates a list of buffers. + * + * @api private + */ + +Parser.prototype.concatBuffers = function(buffers) { + var length = 0; + for (var i = 0, l = buffers.length; i < l; ++i) { + length += buffers[i].length; + } + var mergedBuffer = new Buffer(length); + var offset = 0; + for (var i = 0, l = buffers.length; i < l; ++i) { + buffers[i].copy(mergedBuffer, offset); + offset += buffers[i].length; + } + return mergedBuffer; +} + +/** + * Handles an error + * + * @api private + */ + +Parser.prototype.error = function (reason) { + this.reset(); + this.emit('error', reason); + return this; +}; diff --git a/node_modules/socket.io/lib/transports/websocket/hybi-17.js b/node_modules/socket.io/lib/transports/websocket/hybi-17.js new file mode 100644 index 0000000..8865a3d --- /dev/null +++ b/node_modules/socket.io/lib/transports/websocket/hybi-17.js @@ -0,0 +1,604 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var Transport = require('../../transport') + , EventEmitter = process.EventEmitter + , crypto = require('crypto') + , url = require('url') + , parser = require('../../parser') + , util = require('../../util'); + +/** + * Export the constructor. + */ + +exports = module.exports = WebSocket; +exports.Parser = Parser; + +/** + * HTTP interface constructor. Interface compatible with all transports that + * depend on request-response cycles. + * + * @api public + */ + +function WebSocket (mng, data, req) { + // parser + var self = this; + + this.manager = mng; + this.parser = new Parser(); + this.parser.on('data', function (packet) { + self.onMessage(parser.decodePacket(packet)); + }); + this.parser.on('ping', function () { + // version 8 ping => pong + self.socket.write('\u008a\u0000'); + }); + this.parser.on('close', function () { + self.end(); + }); + this.parser.on('error', function (reason) { + self.log.warn(self.name + ' parser error: ' + reason); + self.end(); + }); + + Transport.call(this, mng, data, req); +}; + +/** + * Inherits from Transport. + */ + +WebSocket.prototype.__proto__ = Transport.prototype; + +/** + * Transport name + * + * @api public + */ + +WebSocket.prototype.name = 'websocket'; + +/** + * Websocket draft version + * + * @api public + */ + +WebSocket.prototype.protocolVersion = '16'; + +/** + * Called when the socket connects. + * + * @api private + */ + +WebSocket.prototype.onSocketConnect = function () { + var self = this; + + if (this.req.headers.upgrade !== 'websocket') { + this.log.warn(this.name + ' connection invalid'); + this.end(); + return; + } + + var origin = this.req.headers['origin'] + , location = (this.socket.encrypted ? 'wss' : 'ws') + + '://' + this.req.headers.host + this.req.url; + + if (!this.verifyOrigin(origin)) { + this.log.warn(this.name + ' connection invalid: origin mismatch'); + this.end(); + return; + } + + if (!this.req.headers['sec-websocket-key']) { + this.log.warn(this.name + ' connection invalid: received no key'); + this.end(); + return; + } + + // calc key + var key = this.req.headers['sec-websocket-key']; + var shasum = crypto.createHash('sha1'); + shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + key = shasum.digest('base64'); + + var headers = [ + 'HTTP/1.1 101 Switching Protocols' + , 'Upgrade: websocket' + , 'Connection: Upgrade' + , 'Sec-WebSocket-Accept: ' + key + ]; + + try { + this.socket.write(headers.concat('', '').join('\r\n')); + this.socket.setTimeout(0); + this.socket.setNoDelay(true); + } catch (e) { + this.end(); + return; + } + + this.socket.on('data', function (data) { + self.parser.add(data); + }); +}; + +/** + * Verifies the origin of a request. + * + * @api private + */ + +WebSocket.prototype.verifyOrigin = function (origin) { + var origins = this.manager.get('origins'); + + if (origin === 'null') origin = '*'; + + if (origins.indexOf('*:*') !== -1) { + return true; + } + + if (origin) { + try { + var parts = url.parse(origin); + var ok = + ~origins.indexOf(parts.hostname + ':' + parts.port) || + ~origins.indexOf(parts.hostname + ':*') || + ~origins.indexOf('*:' + parts.port); + if (!ok) this.log.warn('illegal origin: ' + origin); + return ok; + } catch (ex) { + this.log.warn('error parsing origin'); + } + } + else { + this.log.warn('origin missing from websocket call, yet required by config'); + } + return false; +}; + +/** + * Writes to the socket. + * + * @api private + */ + +WebSocket.prototype.write = function (data) { + if (this.open) { + var buf = this.frame(0x81, data); + this.socket.write(buf, 'binary'); + this.log.debug(this.name + ' writing', data); + } +}; + +/** + * Writes a payload. + * + * @api private + */ + +WebSocket.prototype.payload = function (msgs) { + for (var i = 0, l = msgs.length; i < l; i++) { + this.write(msgs[i]); + } + + return this; +}; + +/** + * Frame server-to-client output as a text packet. + * + * @api private + */ + +WebSocket.prototype.frame = function (opcode, str) { + var dataBuffer = new Buffer(str) + , dataLength = dataBuffer.length + , startOffset = 2 + , secondByte = dataLength; + if (dataLength > 65536) { + startOffset = 10; + secondByte = 127; + } + else if (dataLength > 125) { + startOffset = 4; + secondByte = 126; + } + var outputBuffer = new Buffer(dataLength + startOffset); + outputBuffer[0] = opcode; + outputBuffer[1] = secondByte; + dataBuffer.copy(outputBuffer, startOffset); + switch (secondByte) { + case 126: + outputBuffer[2] = dataLength >>> 8; + outputBuffer[3] = dataLength % 256; + break; + case 127: + var l = dataLength; + for (var i = 1; i <= 8; ++i) { + outputBuffer[startOffset - i] = l & 0xff; + l >>>= 8; + } + } + return outputBuffer; +}; + +/** + * Closes the connection. + * + * @api private + */ + +WebSocket.prototype.doClose = function () { + this.socket.end(); +}; + +/** + * WebSocket parser + * + * @api public + */ + +function Parser () { + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0 + }; + this.overflow = null; + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.currentMessage = ''; + + var self = this; + this.opcodeHandlers = { + // text + '1': function(data) { + var finish = function(mask, data) { + self.currentMessage += self.unmask(mask, data); + if (self.state.lastFragment) { + self.emit('data', self.currentMessage); + self.currentMessage = ''; + } + self.endPacket(); + } + + var expectData = function(length) { + if (self.state.masked) { + self.expect('Mask', 4, function(data) { + var mask = data; + self.expect('Data', length, function(data) { + finish(mask, data); + }); + }); + } + else { + self.expect('Data', length, function(data) { + finish(null, data); + }); + } + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + expectData(firstLength); + } + else if (firstLength == 126) { + self.expect('Length', 2, function(data) { + expectData(util.unpack(data)); + }); + } + else if (firstLength == 127) { + self.expect('Length', 8, function(data) { + if (util.unpack(data.slice(0, 4)) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported'); + return; + } + var lengthBytes = data.slice(4); // note: cap to 32 bit length + expectData(util.unpack(data)); + }); + } + }, + // binary + '2': function(data) { + var finish = function(mask, data) { + if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list + self.currentMessage.push(self.unmask(mask, data, true)); + if (self.state.lastFragment) { + self.emit('binary', self.concatBuffers(self.currentMessage)); + self.currentMessage = ''; + } + self.endPacket(); + } + + var expectData = function(length) { + if (self.state.masked) { + self.expect('Mask', 4, function(data) { + var mask = data; + self.expect('Data', length, function(data) { + finish(mask, data); + }); + }); + } + else { + self.expect('Data', length, function(data) { + finish(null, data); + }); + } + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength < 126) { + expectData(firstLength); + } + else if (firstLength == 126) { + self.expect('Length', 2, function(data) { + expectData(util.unpack(data)); + }); + } + else if (firstLength == 127) { + self.expect('Length', 8, function(data) { + if (util.unpack(data.slice(0, 4)) != 0) { + self.error('packets with length spanning more than 32 bit is currently not supported'); + return; + } + var lengthBytes = data.slice(4); // note: cap to 32 bit length + expectData(util.unpack(data)); + }); + } + }, + // close + '8': function(data) { + self.emit('close'); + self.reset(); + }, + // ping + '9': function(data) { + if (self.state.lastFragment == false) { + self.error('fragmented ping is not supported'); + return; + } + + var finish = function(mask, data) { + self.emit('ping', self.unmask(mask, data)); + self.endPacket(); + } + + var expectData = function(length) { + if (self.state.masked) { + self.expect('Mask', 4, function(data) { + var mask = data; + self.expect('Data', length, function(data) { + finish(mask, data); + }); + }); + } + else { + self.expect('Data', length, function(data) { + finish(null, data); + }); + } + } + + // decode length + var firstLength = data[1] & 0x7f; + if (firstLength == 0) { + finish(null, null); + } + else if (firstLength < 126) { + expectData(firstLength); + } + else if (firstLength == 126) { + self.expect('Length', 2, function(data) { + expectData(util.unpack(data)); + }); + } + else if (firstLength == 127) { + self.expect('Length', 8, function(data) { + expectData(util.unpack(data)); + }); + } + } + } + + this.expect('Opcode', 2, this.processPacket); +}; + +/** + * Inherits from EventEmitter. + */ + +Parser.prototype.__proto__ = EventEmitter.prototype; + +/** + * Add new data to the parser. + * + * @api public + */ + +Parser.prototype.add = function(data) { + if (this.expectBuffer == null) { + this.addToOverflow(data); + return; + } + var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset); + data.copy(this.expectBuffer, this.expectOffset, 0, toRead); + this.expectOffset += toRead; + if (toRead < data.length) { + // at this point the overflow buffer shouldn't at all exist + this.overflow = new Buffer(data.length - toRead); + data.copy(this.overflow, 0, toRead, toRead + this.overflow.length); + } + if (this.expectOffset == this.expectBuffer.length) { + var bufferForHandler = this.expectBuffer; + this.expectBuffer = null; + this.expectOffset = 0; + this.expectHandler.call(this, bufferForHandler); + } +} + +/** + * Adds a piece of data to the overflow. + * + * @api private + */ + +Parser.prototype.addToOverflow = function(data) { + if (this.overflow == null) this.overflow = data; + else { + var prevOverflow = this.overflow; + this.overflow = new Buffer(this.overflow.length + data.length); + prevOverflow.copy(this.overflow, 0); + data.copy(this.overflow, prevOverflow.length); + } +} + +/** + * Waits for a certain amount of bytes to be available, then fires a callback. + * + * @api private + */ + +Parser.prototype.expect = function(what, length, handler) { + this.expectBuffer = new Buffer(length); + this.expectOffset = 0; + this.expectHandler = handler; + if (this.overflow != null) { + var toOverflow = this.overflow; + this.overflow = null; + this.add(toOverflow); + } +} + +/** + * Start processing a new packet. + * + * @api private + */ + +Parser.prototype.processPacket = function (data) { + if ((data[0] & 0x70) != 0) this.error('reserved fields must be empty'); + this.state.lastFragment = (data[0] & 0x80) == 0x80; + this.state.masked = (data[1] & 0x80) == 0x80; + var opcode = data[0] & 0xf; + if (opcode == 0) { + // continuation frame + this.state.opcode = this.state.activeFragmentedOperation; + if (!(this.state.opcode == 1 || this.state.opcode == 2)) { + this.error('continuation frame cannot follow current opcode') + return; + } + } + else { + this.state.opcode = opcode; + if (this.state.lastFragment === false) { + this.state.activeFragmentedOperation = opcode; + } + } + var handler = this.opcodeHandlers[this.state.opcode]; + if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode); + else handler(data); +} + +/** + * Endprocessing a packet. + * + * @api private + */ + +Parser.prototype.endPacket = function() { + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) { + // end current fragmented operation + this.state.activeFragmentedOperation = null; + } + this.state.lastFragment = false; + this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; + this.state.masked = false; + this.expect('Opcode', 2, this.processPacket); +} + +/** + * Reset the parser state. + * + * @api private + */ + +Parser.prototype.reset = function() { + this.state = { + activeFragmentedOperation: null, + lastFragment: false, + masked: false, + opcode: 0 + }; + this.expectOffset = 0; + this.expectBuffer = null; + this.expectHandler = null; + this.overflow = null; + this.currentMessage = ''; +} + +/** + * Unmask received data. + * + * @api private + */ + +Parser.prototype.unmask = function (mask, buf, binary) { + if (mask != null) { + for (var i = 0, ll = buf.length; i < ll; i++) { + buf[i] ^= mask[i % 4]; + } + } + if (binary) return buf; + return buf != null ? buf.toString('utf8') : ''; +} + +/** + * Concatenates a list of buffers. + * + * @api private + */ + +Parser.prototype.concatBuffers = function(buffers) { + var length = 0; + for (var i = 0, l = buffers.length; i < l; ++i) { + length += buffers[i].length; + } + var mergedBuffer = new Buffer(length); + var offset = 0; + for (var i = 0, l = buffers.length; i < l; ++i) { + buffers[i].copy(mergedBuffer, offset); + offset += buffers[i].length; + } + return mergedBuffer; +} + +/** + * Handles an error + * + * @api private + */ + +Parser.prototype.error = function (reason) { + this.reset(); + this.emit('error', reason); + return this; +}; diff --git a/node_modules/socket.io/lib/transports/websocket/index.js b/node_modules/socket.io/lib/transports/websocket/index.js new file mode 100644 index 0000000..3a952b7 --- /dev/null +++ b/node_modules/socket.io/lib/transports/websocket/index.js @@ -0,0 +1,11 @@ + +/** + * Export websocket versions. + */ + +module.exports = { + 7: require('./hybi-07-12'), + 8: require('./hybi-07-12'), + 13: require('./hybi-16'), + default: require('./default') +}; diff --git a/node_modules/socket.io/lib/transports/xhr-polling.js b/node_modules/socket.io/lib/transports/xhr-polling.js new file mode 100644 index 0000000..fee2438 --- /dev/null +++ b/node_modules/socket.io/lib/transports/xhr-polling.js @@ -0,0 +1,72 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module requirements. + */ + +var HTTPPolling = require('./http-polling'); + +/** + * Export the constructor. + */ + +exports = module.exports = XHRPolling; + +/** + * Ajax polling transport. + * + * @api public + */ + +function XHRPolling (mng, data, req) { + HTTPPolling.call(this, mng, data, req); +}; + +/** + * Inherits from Transport. + */ + +XHRPolling.prototype.__proto__ = HTTPPolling.prototype; + +/** + * Transport name + * + * @api public + */ + +XHRPolling.prototype.name = 'xhr-polling'; + +/** + * Frames data prior to write. + * + * @api private + */ + +XHRPolling.prototype.doWrite = function (data) { + HTTPPolling.prototype.doWrite.call(this); + + var origin = this.req.headers.origin + , headers = { + 'Content-Type': 'text/plain; charset=UTF-8' + , 'Content-Length': data === undefined ? 0 : Buffer.byteLength(data) + , 'Connection': 'Keep-Alive' + }; + + if (origin) { + // https://developer.mozilla.org/En/HTTP_Access_Control + headers['Access-Control-Allow-Origin'] = '*'; + + if (this.req.headers.cookie) { + headers['Access-Control-Allow-Credentials'] = 'true'; + } + } + + this.response.writeHead(200, headers); + this.response.write(data); + this.log.debug(this.name + ' writing', data); +}; diff --git a/node_modules/socket.io/lib/util.js b/node_modules/socket.io/lib/util.js new file mode 100644 index 0000000..f7d9f2b --- /dev/null +++ b/node_modules/socket.io/lib/util.js @@ -0,0 +1,50 @@ + +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +/** + * Converts an enumerable to an array. + * + * @api public + */ + +exports.toArray = function (enu) { + var arr = []; + + for (var i = 0, l = enu.length; i < l; i++) + arr.push(enu[i]); + + return arr; +}; + +/** + * Unpacks a buffer to a number. + * + * @api public + */ + +exports.unpack = function (buffer) { + var n = 0; + for (var i = 0; i < buffer.length; ++i) { + n = (i == 0) ? buffer[i] : (n * 256) + buffer[i]; + } + return n; +} + +/** + * Left pads a string. + * + * @api public + */ + +exports.padl = function (s,n,c) { + return new Array(1 + n - s.length).join(c) + s; +} + diff --git a/node_modules/socket.io/node_modules/policyfile/.npmignore b/node_modules/socket.io/node_modules/policyfile/.npmignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/.npmignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/policyfile/LICENSE b/node_modules/socket.io/node_modules/policyfile/LICENSE new file mode 100644 index 0000000..bdb8f61 --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011 Arnout Kazemier,3rd-Eden + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/policyfile/Makefile b/node_modules/socket.io/node_modules/policyfile/Makefile new file mode 100644 index 0000000..1362d66 --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/Makefile @@ -0,0 +1,7 @@ +doc: + dox --title "FlashPolicyFileServer" lib/* > doc/index.html + +test: + expresso -I lib $(TESTFLAGS) tests/*.test.js + +.PHONY: test doc \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/policyfile/README.md b/node_modules/socket.io/node_modules/policyfile/README.md new file mode 100644 index 0000000..527921e --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/README.md @@ -0,0 +1,98 @@ +## LOL, WUT? +It basically allows you to allow or disallow Flash Player sockets from accessing your site. + +## Installation + +```bash +npm install policyfile +``` +## Usage + +The server is based on the regular and know `net` and `http` server patterns. So it you can just listen +for all the events that a `net` based server emits etc. But there is one extra event, the `connect_failed` +event. This event is triggered when we are unable to listen on the supplied port number. + +### createServer +Creates a new server instance and accepts 2 optional arguments: + +- `options` **Object** Options to configure the server instance + - `log` **Boolean** Enable logging to STDOUT and STDERR (defaults to true) +- `origins` **Array** An Array of origins that are allowed by the server (defaults to *:*) + +```js +var pf = require('policyfile'); +pf.createServer(); +pf.listen(); +``` + +#### server.listen +Start listening on the server and it takes 3 optional arguments + +- `port` **Number** On which port number should we listen? (defaults to 843, which is the first port number the FlashPlayer checks) +- `server` **Server** A http server, if we are unable to accept requests or run the server we can also answer the policy requests inline over the supplied HTTP server. +- `callback` **Function** A callback function that is called when listening to the server was successful. + +```js +var pf = require('policyfile'); +pf.createServer(); +pf.listen(1337, function(){ + console.log(':3 yay') +}); +``` + +Changing port numbers can be handy if you do not want to run your server as root and have port 843 forward to a non root port number (aka a number above 1024). + +```js +var pf = require('policyfile') + , http = require('http'); + +server = http.createServer(function(q,r){r.writeHead(200);r.end('hello world')}); +server.listen(80); + +pf.createServer(); +pf.listen(1337, server, function(){ + console.log(':3 yay') +}); +``` + +Support for serving inline requests over a existing HTTP connection as the FlashPlayer will first check port 843, but if it's unable to get a response there it will send a policy file request over port 80, which is usually your http server. + +#### server.add +Adds more origins to the policy file you can add as many arguments as you like. + +```js +var pf = require('policyfile'); +pf.createServer(['google.com:80']); +pf.listen(); +pf.add('blog.3rd-Eden.com:80', 'blog.3rd-Eden.com:8080'); // now has 3 origins +``` + +#### server.add +Adds more origins to the policy file you can add as many arguments as you like. + +```js +var pf = require('policyfile'); +pf.createServer(['blog.3rd-Eden.com:80', 'blog.3rd-Eden.com:8080']); +pf.listen(); +pf.remove('blog.3rd-Eden.com:8080'); // only contains the :80 version now +``` + +#### server.close +Shuts down the server + +```js +var pf = require('policyfile'); +pf.createServer(); +pf.listen(); +pf.close(); // OH NVM. +``` + +## API +http://3rd-eden.com/FlashPolicyFileServer/ + +## Examples +See https://github.com/3rd-Eden/FlashPolicyFileServer/tree/master/examples for examples + +## Licence + +MIT see LICENSE file in the repository \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/policyfile/doc/index.html b/node_modules/socket.io/node_modules/policyfile/doc/index.html new file mode 100644 index 0000000..743fcda --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/doc/index.html @@ -0,0 +1,375 @@ + + + FlashPolicyFileServer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    FlashPolicyFileServer

    server

    lib/server.js
    +

    Module dependencies and cached references. +

    +
    +
    var slice = Array.prototype.slice
    +  , net = require('net');
    +
    +

    The server that does the Policy File severing

    + +

    Options

    + +
    • log false or a function that can output log information, defaults to console.log?
    + +

    + +
    • param: Object options Options to customize the servers functionality.

    • param: Array origins The origins that are allowed on this server, defaults to *:*.

    • api: public

    +
    +
    function Server(options, origins){
    +  var me = this;
    +  
    +  this.origins = origins || ['*:*'];
    +  this.port = 843;
    +  this.log = console.log;
    +  
    +  // merge `this` with the options
    +  Object.keys(options).forEach(function(key){
    +    me[key] &amp;&amp; (me[key] = options[key])
    +  });
    +  
    +  // create the net server
    +  this.socket = net.createServer(function createServer(socket){
    +    socket.on('error', function socketError(){ me.responder.call(me, socket) });
    +    me.responder.call(me, socket);
    +  });
    +  
    +  // Listen for errors as the port might be blocked because we do not have root priv.
    +  this.socket.on('error', function serverError(err){
    +    // Special and common case error handling
    +    if (err.errno == 13){
    +      me.log &amp;&amp; me.log(
    +        'Unable to listen to port `' + me.port + '` as your Node.js instance does not have root privileges. ' +
    +        (
    +          me.server
    +          ? 'The Flash Policy file will now be served inline over the supplied HTTP server, Flash Policy files request will suffer.'
    +          : 'No fallback server supplied.'
    +        )
    +      );
    +      
    +      me.socket.removeAllListeners();
    +      delete me.socket;
    +
    +      me.emit('connect_failed', err);
    +    } else {
    +      me.log &amp;&amp; me.log('FlashPolicyFileServer received a error event:\n' + (err.message ? err.message : err));
    +    }
    +  });
    +  
    +  this.socket.on('timeout', function serverTimeout(){});
    +  this.socket.on('close', function serverClosed(err){
    +    err &amp;&amp; me.log &amp;&amp; me.log('Server closing due to an error: \n' + (err.message ? err.message : err));
    +    
    +    if (me.server){
    +      // not online anymore
    +      delete me.server.online;
    +      
    +      // Remove the inline policy listener if we close down
    +      // but only when the server was `online` (see listen prototype)
    +      if( me.server['@'] &amp;&amp; me.server.online){
    +        me.server.removeListener('connection', me.server['@']);
    +      }
    +    }
    +    me.log &amp;&amp; me.log('Shutting down FlashPolicyFileServer');
    +  });
    +  
    +  // Compile the initial `buffer`
    +  this.compile();
    +}
    +
    +

    Start listening for requests

    + +

    + +
    • param: Number port The port number it should be listening to.

    • param: Server server A HTTP server instance, this will be used to listen for inline requests

    • param: Function cb The callback needs to be called once server is ready

    • api: public

    +
    +
    Server.prototype.listen = function listen(port, server, cb){
    +  var me = this
    +    , args = slice.call(arguments, 0)
    +    , callback;
    +  
    +  // assign the correct vars, for flexible arguments
    +  args.forEach(function args(arg){
    +    var type = typeof arg;
    +    
    +    if (type === 'number') me.port = arg;
    +    if (type === 'function') callback = arg;
    +    if (type === 'object') me.server = arg;
    +  });
    +  
    +  if (this.server){
    +    
    +    // no one in their right mind would ever create a `@` prototype, so Im just gonna store
    +    // my function on the server, so I can remove it later again once the server(s) closes
    +    this.server['@'] = function connection(socket){
    +      socket.once('data', function requestData(data){
    +        // if it's a Flash policy request, and we can write to the 
    +        if (
    +             data
    +          &amp;&amp; data[0] === 60
    +          &amp;&amp; data.toString() === '<policy-file-request/>\0'
    +          &amp;&amp; socket
    +          &amp;&amp; (socket.readyState === 'open' || socket.readyState === 'writeOnly')
    +        ){
    +          // send the buffer
    +          socket.end(me.buffer);
    +        }
    +      });
    +    };
    +    // attach it
    +    this.server.on('connection', this.server['@']);
    +  }
    +  
    +  // We add a callback method, so we can set a flag for when the server is `enabled` or `online`.
    +  // this flag is needed because if a error occurs and the we cannot boot up the server the
    +  // fallback functionality should not be removed during the `close` event
    +  this.socket.listen(this.port, function serverListening(){
    +   me.socket.online = true;
    +   
    +   if (callback) callback(), callback = undefined;
    +   
    +  });
    +  
    +  return this;
    +};
    +
    +

    Adds a new origin to the Flash Policy File.

    + +

    + +
    • param: Arguments The origins that need to be added.

    • api: public

    +
    +
    Server.prototype.add = function add(){
    +  var args = slice.call(arguments, 0)
    +    , i = args.length;
    +  
    +  // flag duplicates
    +  while (i--){
    +    if (this.origins.indexOf(args[i]) &gt;= 0){
    +      args[i] = null;
    +    }
    +  }
    +  
    +  // Add all the arguments to the array
    +  // but first we want to remove all `falsy` values from the args
    +  Array.prototype.push.apply(
    +    this.origins
    +  , args.filter(function(value){ return !!value })
    +  );
    +  
    +  this.compile();
    +  return this;
    +};
    +
    +

    Removes a origin from the Flash Policy File.

    + +

    + +
    • param: String origin The origin that needs to be removed from the server

    • api: public

    +
    +
    Server.prototype.remove = function remove(origin){
    +  var position = this.origins.indexOf(origin);
    +  
    +  // only remove and recompile if we have a match
    +  if (position &gt; 0){
    +    this.origins.splice(position,1);
    +    this.compile();
    +  }
    +  
    +  return this;
    +};
    +
    +

    Closes and cleans up the server

    + +
    • api: public

    +
    +
    Server.prototype.close = function close(){
    +  this.socket.removeAllListeners();
    +  this.socket.close();
    +  
    +  return this;
    +};
    +
    +

    Proxy the event listener requests to the created Net server +

    +
    +
    Object.keys(process.EventEmitter.prototype).forEach(function proxy(key){
    +  Server.prototype[key] = Server.prototype[key] || function (){
    +    if (this.socket) this.socket[key].apply(this.socket, arguments);
    +    return this;
    +  };
    +});
    +
    +

    Creates a new server instance.

    + +

    + +
    • param: Object options A options object to override the default config

    • param: Array origins The origins that should be allowed by the server

    • api: public

    +
    +
    exports.createServer = function createServer(options, origins){
    +  origins = Array.isArray(origins) ? origins : (Array.isArray(options) ? options : false);
    +  options = !Array.isArray(options) &amp;&amp; options ? options : {};
    +  
    +  return new Server(options, origins);
    +};
    +
    +

    Provide a hook to the original server, so it can be extended if needed. +

    +
    +
    exports.Server = Server;
    +
    +

    Module version +

    +
    +
    exports.version = '0.0.2';
    +
    +
    \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/policyfile/examples/basic.fallback.js b/node_modules/socket.io/node_modules/policyfile/examples/basic.fallback.js new file mode 100644 index 0000000..b439449 --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/examples/basic.fallback.js @@ -0,0 +1,8 @@ +var http = require('http') + , fspfs = require('../'); + +var server = http.createServer(function(q,r){ r.writeHead(200); r.end(':3') }) + , flash = fspfs.createServer(); + +server.listen(8080); +flash.listen(8081,server); \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/policyfile/examples/basic.js b/node_modules/socket.io/node_modules/policyfile/examples/basic.js new file mode 100644 index 0000000..5e2290f --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/examples/basic.js @@ -0,0 +1,5 @@ +var http = require('http') + , fspfs = require('../'); + +var flash = fspfs.createServer(); +flash.listen(); \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/policyfile/index.js b/node_modules/socket.io/node_modules/policyfile/index.js new file mode 100644 index 0000000..60cf298 --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/server.js'); \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/policyfile/lib/server.js b/node_modules/socket.io/node_modules/policyfile/lib/server.js new file mode 100644 index 0000000..a525772 --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/lib/server.js @@ -0,0 +1,289 @@ +/** + * Module dependencies and cached references. + */ + +var slice = Array.prototype.slice + , net = require('net'); + +/** + * The server that does the Policy File severing + * + * Options: + * - `log` false or a function that can output log information, defaults to console.log? + * + * @param {Object} options Options to customize the servers functionality. + * @param {Array} origins The origins that are allowed on this server, defaults to `*:*`. + * @api public + */ + +function Server (options, origins) { + var me = this; + + this.origins = origins || ['*:*']; + this.port = 843; + this.log = console.log; + + // merge `this` with the options + Object.keys(options).forEach(function (key) { + me[key] && (me[key] = options[key]) + }); + + // create the net server + this.socket = net.createServer(function createServer (socket) { + socket.on('error', function socketError () { + me.responder.call(me, socket); + }); + + me.responder.call(me, socket); + }); + + // Listen for errors as the port might be blocked because we do not have root priv. + this.socket.on('error', function serverError (err) { + // Special and common case error handling + if (err.errno == 13) { + me.log && me.log( + 'Unable to listen to port `' + me.port + '` as your Node.js instance does not have root privileges. ' + + ( + me.server + ? 'The Flash Policy File requests will only be served inline over the supplied HTTP server. Inline serving is slower than a dedicated server instance.' + : 'No fallback server supplied, we will be unable to answer Flash Policy File requests.' + ) + ); + + me.emit('connect_failed', err); + me.socket.removeAllListeners(); + delete me.socket; + } else { + me.log && me.log('FlashPolicyFileServer received an error event:\n' + (err.message ? err.message : err)); + } + }); + + this.socket.on('timeout', function serverTimeout () {}); + this.socket.on('close', function serverClosed (err) { + err && me.log && me.log('Server closing due to an error: \n' + (err.message ? err.message : err)); + + if (me.server) { + // Remove the inline policy listener if we close down + // but only when the server was `online` (see listen prototype) + if (me.server['@'] && me.server.online) { + me.server.removeListener('connection', me.server['@']); + } + + // not online anymore + delete me.server.online; + } + }); + + // Compile the initial `buffer` + this.compile(); +} + +/** + * Start listening for requests + * + * @param {Number} port The port number it should be listening to. + * @param {Server} server A HTTP server instance, this will be used to listen for inline requests + * @param {Function} cb The callback needs to be called once server is ready + * @api public + */ + +Server.prototype.listen = function listen (port, server, cb){ + var me = this + , args = slice.call(arguments, 0) + , callback; + + // assign the correct vars, for flexible arguments + args.forEach(function args (arg){ + var type = typeof arg; + + if (type === 'number') me.port = arg; + if (type === 'function') callback = arg; + if (type === 'object') me.server = arg; + }); + + if (this.server) { + + // no one in their right mind would ever create a `@` prototype, so Im just gonna store + // my function on the server, so I can remove it later again once the server(s) closes + this.server['@'] = function connection (socket) { + socket.once('data', function requestData (data) { + // if it's a Flash policy request, and we can write to the + if ( + data + && data[0] === 60 + && data.toString() === '\0' + && socket + && (socket.readyState === 'open' || socket.readyState === 'writeOnly') + ){ + // send the buffer + try { + socket.end(me.buffer); + } catch (e) {} + } + }); + }; + + // attach it + this.server.on('connection', this.server['@']); + } + + // We add a callback method, so we can set a flag for when the server is `enabled` or `online`. + // this flag is needed because if a error occurs and the we cannot boot up the server the + // fallback functionality should not be removed during the `close` event + this.port >= 0 && this.socket.listen(this.port, function serverListening () { + me.socket.online = true; + if (callback) { + callback.call(me); + callback = undefined; + } + }); + + return this; +}; + +/** + * Responds to socket connects and writes the compile policy file. + * + * @param {net.Socket} socket The socket that needs to receive the message + * @api private + */ + +Server.prototype.responder = function responder (socket){ + if (socket && socket.readyState == 'open' && socket.end) { + try { + socket.end(this.buffer); + } catch (e) {} + } +}; + +/** + * Compiles the supplied origins to a Flash Policy File format and stores it in a Node.js Buffer + * this way it can be send over the wire without any performance loss. + * + * @api private + */ + +Server.prototype.compile = function compile (){ + var xml = [ + '' + , '' + , '' + ]; + + // add the allow access element + this.origins.forEach(function origin (origin){ + var parts = origin.split(':'); + xml.push(''); + }); + + xml.push(''); + + // store the result in a buffer so we don't have to re-generate it all the time + this.buffer = new Buffer(xml.join(''), 'utf8'); + + return this; +}; + +/** + * Adds a new origin to the Flash Policy File. + * + * @param {Arguments} The origins that need to be added. + * @api public + */ + +Server.prototype.add = function add(){ + var args = slice.call(arguments, 0) + , i = args.length; + + // flag duplicates + while (i--) { + if (this.origins.indexOf(args[i]) >= 0){ + args[i] = null; + } + } + + // Add all the arguments to the array + // but first we want to remove all `falsy` values from the args + Array.prototype.push.apply( + this.origins + , args.filter(function filter (value) { + return !!value; + }) + ); + + this.compile(); + return this; +}; + +/** + * Removes a origin from the Flash Policy File. + * + * @param {String} origin The origin that needs to be removed from the server + * @api public + */ + +Server.prototype.remove = function remove (origin){ + var position = this.origins.indexOf(origin); + + // only remove and recompile if we have a match + if (position > 0) { + this.origins.splice(position,1); + this.compile(); + } + + return this; +}; + +/** + * Closes and cleans up the server + * + * @api public + */ + +Server.prototype.close = function close () { + this.socket.removeAllListeners(); + this.socket.close(); + + return this; +}; + +/** + * Proxy the event listener requests to the created Net server + */ + +Object.keys(process.EventEmitter.prototype).forEach(function proxy (key){ + Server.prototype[key] = Server.prototype[key] || function () { + if (this.socket) { + this.socket[key].apply(this.socket, arguments); + } + + return this; + }; +}); + +/** + * Creates a new server instance. + * + * @param {Object} options A options object to override the default config + * @param {Array} origins The origins that should be allowed by the server + * @api public + */ + +exports.createServer = function createServer(options, origins){ + origins = Array.isArray(origins) ? origins : (Array.isArray(options) ? options : false); + options = !Array.isArray(options) && options ? options : {}; + + return new Server(options, origins); +}; + +/** + * Provide a hook to the original server, so it can be extended if needed. + */ + +exports.Server = Server; + +/** + * Module version + */ + +exports.version = '0.0.4'; diff --git a/node_modules/socket.io/node_modules/policyfile/package.json b/node_modules/socket.io/node_modules/policyfile/package.json new file mode 100644 index 0000000..78526c5 --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/package.json @@ -0,0 +1,32 @@ +{ + "name": "policyfile" + , "version": "0.0.4" + , "author": "Arnout Kazemier" + , "description": "Flash Socket Policy File Server. A server to respond to Flash Socket Policy requests, both inline and through a dedicated server instance." + , "main": "index" + , "keywords":[ + "flash" + , "socket" + , "policy" + , "file" + , "server" + , "Flash Socket Policy File Server" + , "cross domain" + ] + , "directories": { + "lib": "./lib" + } + , "maintainers": [{ + "name":"Arnout Kazemier" + , "email":"info@3rd-Eden.com" + , "web":"http://blog.3rd-Eden.com" + }] + , "licenses": [{ + "type": "MIT" + , "url": "https://github.com/3rd-Eden/FlashPolicyFileServer/blob/master/LICENSE" + }] + , "repositories": [{ + "type": "git" + , "url" : "https://github.com/3rd-Eden/FlashPolicyFileServer.git" + }] +} diff --git a/node_modules/socket.io/node_modules/policyfile/tests/ssl/ssl.crt b/node_modules/socket.io/node_modules/policyfile/tests/ssl/ssl.crt new file mode 100644 index 0000000..5883cd4 --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/tests/ssl/ssl.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAMUSOvlaeyQHMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTAxMTE2MDkzMjQ5WhcNMTMxMTE1MDkzMjQ5WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEVwfPQQp4X +wtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+1FAE0c5o +exPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404WthquTqg +S7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy25IyBK3QJ +c+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWAQsqW+COL +0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABo1AwTjAdBgNVHQ4EFgQUDnV4d6mD +tOnluLoCjkUHTX/n4agwHwYDVR0jBBgwFoAUDnV4d6mDtOnluLoCjkUHTX/n4agw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAFwV4MQfTo+qMv9JMiyno +IEiqfOz4RgtmBqRnXUffcjS2dhc7/z+FPZnM79Kej8eLHoVfxCyWRHFlzm93vEdv +wxOCrD13EDOi08OOZfxWyIlCa6Bg8cMAKqQzd2OvQOWqlRWBTThBJIhWflU33izX +Qn5GdmYqhfpc+9ZHHGhvXNydtRQkdxVK2dZNzLBvBlLlRmtoClU7xm3A+/5dddeP +AQHEPtyFlUw49VYtZ3ru6KqPms7MKvcRhYLsy9rwSfuuniMlx4d0bDR7TOkw0QQS +A0N8MGQRQpzl4mw4jLzyM5d5QtuGBh2P6hPGa0YQxtI3RPT/p6ENzzBiAKXiSfzo +xw== +-----END CERTIFICATE----- diff --git a/node_modules/socket.io/node_modules/policyfile/tests/ssl/ssl.private.key b/node_modules/socket.io/node_modules/policyfile/tests/ssl/ssl.private.key new file mode 100644 index 0000000..f31ff3d --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/tests/ssl/ssl.private.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEV +wfPQQp4XwtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+ +1FAE0c5oexPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404 +WthquTqgS7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy2 +5IyBK3QJc+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWA +QsqW+COL0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABAoIBAGe4+9VqZfJN+dsq +8Osyuz01uQ8OmC0sAWTIqUlQgENIyf9rCJsUBlYmwR5BT6Z69XP6QhHdpSK+TiAR +XUz0EqG9HYzcxHIBaACP7j6iRoQ8R4kbbiWKo0z3WqQGIOqFjvD/mKEuQdE5mEYw +eOUCG6BnX1WY2Yr8WKd2AA/tp0/Y4d8z04u9eodMpSTbHTzYMJb5SbBN1vo6FY7q +8zSuO0BMzXlAxUsCwHsk1GQHFr8Oh3zIR7bQGtMBouI+6Lhh7sjFYsfxJboqMTBV +IKaA216M6ggHG7MU1/jeKcMGDmEfqQLQoyWp29rMK6TklUgipME2L3UD7vTyAVzz +xbVOpZkCgYEA8CXW4sZBBrSSrLR5SB+Ubu9qNTggLowOsC/kVKB2WJ4+xooc5HQo +mFhq1v/WxPQoWIxdYsfg2odlL+JclK5Qcy6vXmRSdAQ5lK9gBDKxZSYc3NwAw2HA +zyHCTK+I0n8PBYQ+yGcrxu0WqTGnlLW+Otk4CejO34WlgHwbH9bbY5UCgYEA3ZvT +C4+OoMHXlmICSt29zUrYiL33IWsR3/MaONxTEDuvgkOSXXQOl/8Ebd6Nu+3WbsSN +bjiPC/JyL1YCVmijdvFpl4gjtgvfJifs4G+QHvO6YfsYoVANk4u6g6rUuBIOwNK4 +RwYxwDc0oysp+g7tPxoSgDHReEVKJNzGBe9NGGsCgYEA4O4QP4gCEA3B9BF2J5+s +n9uPVxmiyvZUK6Iv8zP4pThTBBMIzNIf09G9AHPQ7djikU2nioY8jXKTzC3xGTHM +GJZ5m6fLsu7iH+nDvSreDSeNkTBfZqGAvoGYQ8uGE+L+ZuRfCcXYsxIOT5s6o4c3 +Dle2rVFpsuKzCY00urW796ECgYBn3go75+xEwrYGQSer6WR1nTgCV29GVYXKPooy +zmmMOT1Yw80NSkEw0pFD4cTyqVYREsTrPU0mn1sPfrOXxnGfZSVFpcR/Je9QVfQ7 +eW7GYxwfom335aqHVj10SxRqteP+UoWWnHujCPz94VRKZMakBddYCIGSan+G6YdS +7sdmwwKBgBc2qj0wvGXDF2kCLwSGfWoMf8CS1+5fIiUIdT1e/+7MfDdbmLMIFVjF +QKS3zVViXCbrG5SY6wS9hxoc57f6E2A8vcaX6zy2xkZlGHQCpWRtEM5R01OWJQaH +HsHMmQZGUQVoDm1oRkDhrTFK4K3ukc3rAxzeTZ96utOQN8/KJsTv +-----END RSA PRIVATE KEY----- diff --git a/node_modules/socket.io/node_modules/policyfile/tests/unit.test.js b/node_modules/socket.io/node_modules/policyfile/tests/unit.test.js new file mode 100644 index 0000000..932b3c1 --- /dev/null +++ b/node_modules/socket.io/node_modules/policyfile/tests/unit.test.js @@ -0,0 +1,231 @@ +var fspfs = require('../') + , fs = require('fs') + , http = require('http') + , https = require('https') + , net = require('net') + , should = require('should') + , assert = require('assert'); + +module.exports = { + // Library version should be Semver compatible + 'Library version': function(){ + fspfs.version.should.match(/^\d+\.\d+\.\d+$/); + } + + // Creating a server instace should not cause any problems + // either using the new Server or createServer method. +, 'Create Server instance': function(){ + var server = fspfs.createServer() + , server2 = new fspfs.Server({log:false}, ['blog.3rd-Eden.com:1337']); + + // server 2 options test + server2.log.should.be.false; + server2.origins.length.should.equal(1); + server2.origins[0].should.equal('blog.3rd-Eden.com:1337'); + + // server defaults + (typeof server.log).should.be.equal('function'); + server.origins.length.should.equal(1); + server.origins[0].should.equal('*:*'); + + // instance checking, sanity check + assert.ok(server instanceof fspfs.Server); + assert.ok(!!server.buffer); + + // more options testing + server = fspfs.createServer(['blog.3rd-Eden.com:80']); + server.origins.length.should.equal(1); + server.origins[0].should.equal('blog.3rd-Eden.com:80'); + + server = fspfs.createServer({log:false},['blog.3rd-Eden.com:80']); + server.log.should.be.false; + server.origins.length.should.equal(1); + server.origins[0].should.equal('blog.3rd-Eden.com:80'); + + } + +, 'Add origin': function(){ + var server = fspfs.createServer(); + server.add('google.com:80', 'blog.3rd-Eden.com:1337'); + + server.origins.length.should.equal(3); + server.origins.indexOf('google.com:80').should.be.above(0); + + // don't allow duplicates + server.add('google.com:80', 'google.com:80'); + + var i = server.origins.length + , count = 0; + + while(i--){ + if (server.origins[i] === 'google.com:80'){ + count++; + } + } + + count.should.equal(1); + } + +, 'Remove origin': function(){ + var server = fspfs.createServer(); + server.add('google.com:80', 'blog.3rd-Eden.com:1337'); + server.origins.length.should.equal(3); + + server.remove('google.com:80'); + server.origins.length.should.equal(2); + server.origins.indexOf('google.com:80').should.equal(-1); + } + +, 'Buffer': function(){ + var server = fspfs.createServer(); + + Buffer.isBuffer(server.buffer).should.be.true; + server.buffer.toString().indexOf('to-ports="*"').should.be.above(0); + server.buffer.toString().indexOf('domain="*"').should.be.above(0); + server.buffer.toString().indexOf('domain="google.com"').should.equal(-1); + + // The buffers should be rebuild when new origins are added + server.add('google.com:80'); + server.buffer.toString().indexOf('to-ports="80"').should.be.above(0); + server.buffer.toString().indexOf('domain="google.com"').should.be.above(0); + + server.remove('google.com:80'); + server.buffer.toString().indexOf('to-ports="80"').should.equal(-1); + server.buffer.toString().indexOf('domain="google.com"').should.equal(-1); + } + +, 'Responder': function(){ + var server = fspfs.createServer() + , calls = 0 + // dummy socket to emulate a `real` socket + , dummySocket = { + readyState: 'open' + , end: function(buffer){ + calls++; + Buffer.isBuffer(buffer).should.be.true; + buffer.toString().should.equal(server.buffer.toString()); + } + }; + + server.responder(dummySocket); + calls.should.equal(1); + } + +, 'Event proxy': function(){ + var server = fspfs.createServer() + , calls = 0; + + Object.keys(process.EventEmitter.prototype).forEach(function proxy(key){ + assert.ok(!!server[key] && typeof server[key] === 'function'); + }); + + // test if it works by calling a none default event + server.on('pew', function(){ + calls++; + }); + + server.emit('pew'); + calls.should.equal(1); + } + +, 'inline response http': function(){ + var port = 1335 + , httpserver = http.createServer(function(q,r){r.writeHead(200);r.end(':3')}) + , server = fspfs.createServer(); + + httpserver.listen(port, function(){ + server.listen(port + 1, httpserver, function(){ + var client = net.createConnection(port); + client.write('\0'); + client.on('error', function(err){ + assert.ok(!err, err) + }); + client.on('data', function(data){ + + var response = data.toString(); + console.log(response); + + response.indexOf('to-ports="*"').should.be.above(0); + response.indexOf('domain="*"').should.be.above(0); + response.indexOf('domain="google.com"').should.equal(-1); + + // clean up + client.destroy(); + server.close(); + httpserver.close(); + }); + }); + }); + } + +, 'server response': function(){ + var port = 1340 + , server = fspfs.createServer(); + + server.listen(port, function(){ + var client = net.createConnection(port); + client.write('\0'); + client.on('error', function(err){ + assert.ok(!err, err) + }); + client.on('data', function(data){ + + var response = data.toString(); + + response.indexOf('to-ports="*"').should.be.above(0); + response.indexOf('domain="*"').should.be.above(0); + response.indexOf('domain="google.com"').should.equal(-1); + + // clean up + client.destroy(); + server.close(); + }); + }); + } + +, 'inline response https': function(){ + var port = 1345 + , ssl = { + key: fs.readFileSync(__dirname + '/ssl/ssl.private.key').toString() + , cert: fs.readFileSync(__dirname + '/ssl/ssl.crt').toString() + } + , httpserver = https.createServer(ssl, function(q,r){r.writeHead(200);r.end(':3')}) + , server = fspfs.createServer(); + + httpserver.listen(port, function(){ + server.listen(port + 1, httpserver, function(){ + var client = net.createConnection(port); + client.write('\0'); + client.on('error', function(err){ + assert.ok(!err, err) + }); + client.on('data', function(data){ + + var response = data.toString(); + + response.indexOf('to-ports="*"').should.be.above(0); + response.indexOf('domain="*"').should.be.above(0); + response.indexOf('domain="google.com"').should.equal(-1); + + // clean up + client.destroy(); + server.close(); + httpserver.close(); + }); + }); + }); + } + +, 'connect_failed': function(){ + var server = fspfs.createServer(); + + server.on('connect_failed', function(){ + assert.ok(true); + }); + + server.listen(function(){ + assert.ok(false, 'Run this test without root access'); + server.close(); + }); + } +}; \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/redis/README.md b/node_modules/socket.io/node_modules/redis/README.md new file mode 100644 index 0000000..02a873b --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/README.md @@ -0,0 +1,567 @@ +redis - a node.js redis client +=========================== + +This is a complete Redis client for node.js. It supports all Redis commands, including many recently added commands like EVAL from +experimental Redis server branches. + + +Install with: + + npm install redis + +Pieter Noordhuis has provided a binding to the official `hiredis` C library, which is non-blocking and fast. To use `hiredis`, do: + + npm install hiredis redis + +If `hiredis` is installed, `node_redis` will use it by default. Otherwise, a pure JavaScript parser will be used. + +If you use `hiredis`, be sure to rebuild it whenever you upgrade your version of node. There are mysterious failures that can +happen between node and native code modules after a node upgrade. + + +## Usage + +Simple example, included as `examples/simple.js`: + + var redis = require("redis"), + client = redis.createClient(); + + client.on("error", function (err) { + console.log("Error " + err); + }); + + client.set("string key", "string val", redis.print); + client.hset("hash key", "hashtest 1", "some value", redis.print); + client.hset(["hash key", "hashtest 2", "some other value"], redis.print); + client.hkeys("hash key", function (err, replies) { + console.log(replies.length + " replies:"); + replies.forEach(function (reply, i) { + console.log(" " + i + ": " + reply); + }); + client.quit(); + }); + +This will display: + + mjr:~/work/node_redis (master)$ node example.js + Reply: OK + Reply: 0 + Reply: 0 + 2 replies: + 0: hashtest 1 + 1: hashtest 2 + mjr:~/work/node_redis (master)$ + + +## Performance + +Here are typical results of `multi_bench.js` which is similar to `redis-benchmark` from the Redis distribution. +It uses 50 concurrent connections with no pipelining. + +JavaScript parser: + + PING: 20000 ops 42283.30 ops/sec 0/5/1.182 + SET: 20000 ops 32948.93 ops/sec 1/7/1.515 + GET: 20000 ops 28694.40 ops/sec 0/9/1.740 + INCR: 20000 ops 39370.08 ops/sec 0/8/1.269 + LPUSH: 20000 ops 36429.87 ops/sec 0/8/1.370 + LRANGE (10 elements): 20000 ops 9891.20 ops/sec 1/9/5.048 + LRANGE (100 elements): 20000 ops 1384.56 ops/sec 10/91/36.072 + +hiredis parser: + + PING: 20000 ops 46189.38 ops/sec 1/4/1.082 + SET: 20000 ops 41237.11 ops/sec 0/6/1.210 + GET: 20000 ops 39682.54 ops/sec 1/7/1.257 + INCR: 20000 ops 40080.16 ops/sec 0/8/1.242 + LPUSH: 20000 ops 41152.26 ops/sec 0/3/1.212 + LRANGE (10 elements): 20000 ops 36563.07 ops/sec 1/8/1.363 + LRANGE (100 elements): 20000 ops 21834.06 ops/sec 0/9/2.287 + +The performance of `node_redis` improves dramatically with pipelining, which happens automatically in most normal programs. + + +### Sending Commands + +Each Redis command is exposed as a function on the `client` object. +All functions take either take either an `args` Array plus optional `callback` Function or +a variable number of individual arguments followed by an optional callback. +Here is an example of passing an array of arguments and a callback: + + client.mset(["test keys 1", "test val 1", "test keys 2", "test val 2"], function (err, res) {}); + +Here is that same call in the second style: + + client.mset("test keys 1", "test val 1", "test keys 2", "test val 2", function (err, res) {}); + +Note that in either form the `callback` is optional: + + client.set("some key", "some val"); + client.set(["some other key", "some val"]); + +For a list of Redis commands, see [Redis Command Reference](http://redis.io/commands) + +The commands can be specified in uppercase or lowercase for convenience. `client.get()` is the same as `client.GET()`. + +Minimal parsing is done on the replies. Commands that return a single line reply return JavaScript Strings, +integer replies return JavaScript Numbers, "bulk" replies return node Buffers, and "multi bulk" replies return a +JavaScript Array of node Buffers. `HGETALL` returns an Object with Buffers keyed by the hash keys. + +# API + +## Connection Events + +`client` will emit some events about the state of the connection to the Redis server. + +### "ready" + +`client` will emit `ready` a connection is established to the Redis server and the server reports +that it is ready to receive commands. Commands issued before the `ready` event are queued, +then replayed just before this event is emitted. + +### "connect" + +`client` will emit `connect` at the same time as it emits `ready` unless `client.options.no_ready_check` +is set. If this options is set, `connect` will be emitted when the stream is connected, and then +you are free to try to send commands. + +### "error" + +`client` will emit `error` when encountering an error connecting to the Redis server. + +Note that "error" is a special event type in node. If there are no listeners for an +"error" event, node will exit. This is usually what you want, but it can lead to some +cryptic error messages like this: + + mjr:~/work/node_redis (master)$ node example.js + + node.js:50 + throw e; + ^ + Error: ECONNREFUSED, Connection refused + at IOWatcher.callback (net:870:22) + at node.js:607:9 + +Not very useful in diagnosing the problem, but if your program isn't ready to handle this, +it is probably the right thing to just exit. + +`client` will also emit `error` if an exception is thrown inside of `node_redis` for whatever reason. +It would be nice to distinguish these two cases. + +### "end" + +`client` will emit `end` when an established Redis server connection has closed. + +### "drain" + +`client` will emit `drain` when the TCP connection to the Redis server has been buffering, but is now +writable. This event can be used to stream commands in to Redis and adapt to backpressure. Right now, +you need to check `client.command_queue.length` to decide when to reduce your send rate. Then you can +resume sending when you get `drain`. + +### "idle" + +`client` will emit `idle` when there are no outstanding commands that are awaiting a response. + +## redis.createClient(port, host, options) + +Create a new client connection. `port` defaults to `6379` and `host` defaults +to `127.0.0.1`. If you have `redis-server` running on the same computer as node, then the defaults for +port and host are probably fine. `options` in an object with the following possible properties: + +* `parser`: which Redis protocol reply parser to use. Defaults to `hiredis` if that module is installed. +This may also be set to `javascript`. +* `return_buffers`: defaults to false. If set to `true`, then bulk data replies will be returned as node Buffer +objects instead of JavaScript Strings. + +`createClient()` returns a `RedisClient` object that is named `client` in all of the examples here. + +## client.auth(password, callback) + +When connecting to Redis servers that require authentication, the `AUTH` command must be sent as the +first command after connecting. This can be tricky to coordinate with reconnections, the ready check, +etc. To make this easier, `client.auth()` stashes `password` and will send it after each connection, +including reconnections. `callback` is invoked only once, after the response to the very first +`AUTH` command sent. + +## client.end() + +Forcibly close the connection to the Redis server. Note that this does not wait until all replies have been parsed. +If you want to exit cleanly, call `client.quit()` to send the `QUIT` command after you have handled all replies. + +This example closes the connection to the Redis server before the replies have been read. You probably don't +want to do this: + + var redis = require("redis"), + client = redis.createClient(); + + client.set("foo_rand000000000000", "some fantastic value"); + client.get("foo_rand000000000000", function (err, reply) { + console.log(reply.toString()); + }); + client.end(); + +`client.end()` is useful for timeout cases where something is stuck or taking too long and you want +to start over. + +## Friendlier hash commands + +Most Redis commands take a single String or an Array of Strings as arguments, and replies are sent back as a single String or an Array of Strings. When dealing with hash values, there are a couple of useful exceptions to this. + +### client.hgetall(hash) + +The reply from an HGETALL command will be converted into a JavaScript Object by `node_redis`. That way you can interact +with the responses using JavaScript syntax. + +Example: + + client.hmset("hosts", "mjr", "1", "another", "23", "home", "1234"); + client.hgetall("hosts", function (err, obj) { + console.dir(obj); + }); + +Output: + + { mjr: '1', another: '23', home: '1234' } + +### client.hmset(hash, obj, [callback]) + +Multiple values in a hash can be set by supplying an object: + + client.HMSET(key2, { + "0123456789": "abcdefghij", + "some manner of key": "a type of value" + }); + +The properties and values of this Object will be set as keys and values in the Redis hash. + +### client.hmset(hash, key1, val1, ... keyn, valn, [callback]) + +Multiple values may also be set by supplying a list: + + client.HMSET(key1, "0123456789", "abcdefghij", "some manner of key", "a type of value"); + + +## Publish / Subscribe + +Here is a simple example of the API for publish / subscribe. This program opens two +client connections, subscribes to a channel on one of them, and publishes to that +channel on the other: + + var redis = require("redis"), + client1 = redis.createClient(), client2 = redis.createClient(), + msg_count = 0; + + client1.on("subscribe", function (channel, count) { + client2.publish("a nice channel", "I am sending a message."); + client2.publish("a nice channel", "I am sending a second message."); + client2.publish("a nice channel", "I am sending my last message."); + }); + + client1.on("message", function (channel, message) { + console.log("client1 channel " + channel + ": " + message); + msg_count += 1; + if (msg_count === 3) { + client1.unsubscribe(); + client1.end(); + client2.end(); + } + }); + + client1.incr("did a thing"); + client1.subscribe("a nice channel"); + +When a client issues a `SUBSCRIBE` or `PSUBSCRIBE`, that connection is put into "pub/sub" mode. +At that point, only commands that modify the subscription set are valid. When the subscription +set is empty, the connection is put back into regular mode. + +If you need to send regular commands to Redis while in pub/sub mode, just open another connection. + +## Pub / Sub Events + +If a client has subscriptions active, it may emit these events: + +### "message" (channel, message) + +Client will emit `message` for every message received that matches an active subscription. +Listeners are passed the channel name as `channel` and the message Buffer as `message`. + +### "pmessage" (pattern, channel, message) + +Client will emit `pmessage` for every message received that matches an active subscription pattern. +Listeners are passed the original pattern used with `PSUBSCRIBE` as `pattern`, the sending channel +name as `channel`, and the message Buffer as `message`. + +### "subscribe" (channel, count) + +Client will emit `subscribe` in response to a `SUBSCRIBE` command. Listeners are passed the +channel name as `channel` and the new count of subscriptions for this client as `count`. + +### "psubscribe" (pattern, count) + +Client will emit `psubscribe` in response to a `PSUBSCRIBE` command. Listeners are passed the +original pattern as `pattern`, and the new count of subscriptions for this client as `count`. + +### "unsubscribe" (channel, count) + +Client will emit `unsubscribe` in response to a `UNSUBSCRIBE` command. Listeners are passed the +channel name as `channel` and the new count of subscriptions for this client as `count`. When +`count` is 0, this client has left pub/sub mode and no more pub/sub events will be emitted. + +### "punsubscribe" (pattern, count) + +Client will emit `punsubscribe` in response to a `PUNSUBSCRIBE` command. Listeners are passed the +channel name as `channel` and the new count of subscriptions for this client as `count`. When +`count` is 0, this client has left pub/sub mode and no more pub/sub events will be emitted. + +## client.multi([commands]) + +`MULTI` commands are queued up until an `EXEC` is issued, and then all commands are run atomically by +Redis. The interface in `node_redis` is to return an individual `Multi` object by calling `client.multi()`. + + var redis = require("./index"), + client = redis.createClient(), set_size = 20; + + client.sadd("bigset", "a member"); + client.sadd("bigset", "another member"); + + while (set_size > 0) { + client.sadd("bigset", "member " + set_size); + set_size -= 1; + } + + // multi chain with an individual callback + client.multi() + .scard("bigset") + .smembers("bigset") + .keys("*", function (err, replies) { + client.mget(replies, redis.print); + }) + .dbsize() + .exec(function (err, replies) { + console.log("MULTI got " + replies.length + " replies"); + replies.forEach(function (reply, index) { + console.log("Reply " + index + ": " + reply.toString()); + }); + }); + +`client.multi()` is a constructor that returns a `Multi` object. `Multi` objects share all of the +same command methods as `client` objects do. Commands are queued up inside the `Multi` object +until `Multi.exec()` is invoked. + +You can either chain together `MULTI` commands as in the above example, or you can queue individual +commands while still sending regular client command as in this example: + + var redis = require("redis"), + client = redis.createClient(), multi; + + // start a separate multi command queue + multi = client.multi(); + multi.incr("incr thing", redis.print); + multi.incr("incr other thing", redis.print); + + // runs immediately + client.mset("incr thing", 100, "incr other thing", 1, redis.print); + + // drains multi queue and runs atomically + multi.exec(function (err, replies) { + console.log(replies); // 101, 2 + }); + + // you can re-run the same transaction if you like + multi.exec(function (err, replies) { + console.log(replies); // 102, 3 + client.quit(); + }); + +In addition to adding commands to the `MULTI` queue individually, you can also pass an array +of commands and arguments to the constructor: + + var redis = require("redis"), + client = redis.createClient(), multi; + + client.multi([ + ["mget", "multifoo", "multibar", redis.print], + ["incr", "multifoo"], + ["incr", "multibar"] + ]).exec(function (err, replies) { + console.log(replies); + }); + + +## Monitor mode + +Redis supports the `MONITOR` command, which lets you see all commands received by the Redis server +across all client connections, including from other client libraries and other computers. + +After you send the `MONITOR` command, no other commands are valid on that connection. `node_redis` +will emit a `monitor` event for every new monitor message that comes across. The callback for the +`monitor` event takes a timestamp from the Redis server and an array of command arguments. + +Here is a simple example: + + var client = require("redis").createClient(), + util = require("util"); + + client.monitor(function (err, res) { + console.log("Entering monitoring mode."); + }); + + client.on("monitor", function (time, args) { + console.log(time + ": " + util.inspect(args)); + }); + + +# Extras + +Some other things you might like to know about. + +## client.server_info + +After the ready probe completes, the results from the INFO command are saved in the `client.server_info` +object. + +The `versions` key contains an array of the elements of the version string for easy comparison. + + > client.server_info.redis_version + '2.3.0' + > client.server_info.versions + [ 2, 3, 0 ] + +## redis.print() + +A handy callback function for displaying return values when testing. Example: + + var redis = require("redis"), + client = redis.createClient(); + + client.on("connect", function () { + client.set("foo_rand000000000000", "some fantastic value", redis.print); + client.get("foo_rand000000000000", redis.print); + }); + +This will print: + + Reply: OK + Reply: some fantastic value + +Note that this program will not exit cleanly because the client is still connected. + +## redis.debug_mode + +Boolean to enable debug mode and protocol tracing. + + var redis = require("redis"), + client = redis.createClient(); + + redis.debug_mode = true; + + client.on("connect", function () { + client.set("foo_rand000000000000", "some fantastic value"); + }); + +This will display: + + mjr:~/work/node_redis (master)$ node ~/example.js + send command: *3 + $3 + SET + $20 + foo_rand000000000000 + $20 + some fantastic value + + on_data: +OK + +`send command` is data sent into Redis and `on_data` is data received from Redis. + +## client.send_command(command_name, args, callback) + +Used internally to send commands to Redis. For convenience, nearly all commands that are published on the Redis +Wiki have been added to the `client` object. However, if I missed any, or if new commands are introduced before +this library is updated, you can use `send_command()` to send arbitrary commands to Redis. + +All commands are sent as multi-bulk commands. `args` can either be an Array of arguments, or individual arguments, +or omitted completely. + +## client.connected + +Boolean tracking the state of the connection to the Redis server. + +## client.command_queue.length + +The number of commands that have been sent to the Redis server but not yet replied to. You can use this to +enforce some kind of maximum queue depth for commands while connected. + +Don't mess with `client.command_queue` though unless you really know what you are doing. + +## client.offline_queue.length + +The number of commands that have been queued up for a future connection. You can use this to enforce +some kind of maximum queue depth for pre-connection commands. + +## client.retry_delay + +Current delay in milliseconds before a connection retry will be attempted. This starts at `250`. + +## client.retry_backoff + +Multiplier for future retry timeouts. This should be larger than 1 to add more time between retries. +Defaults to 1.7. The default initial connection retry is 250, so the second retry will be 425, followed by 723.5, etc. + + +## TODO + +Better tests for monitor mode, auth, disconnect/reconnect, and all combinations thereof. + +Stream large set/get values into and out of Redis. Otherwise the entire value must be in node's memory. + +Performance can be better for very large values. + +I think there are more performance improvements left in there for smaller values, especially for large lists of small values. + +## Contributors + +Some people have have added features and fixed bugs in `node_redis` other than me. + +In order of first contribution, they are: + +* [Tim Smart](https://github.com/Tim-Smart) +* [TJ Holowaychuk](https://github.com/visionmedia) +* [Rick Olson](https://github.com/technoweenie) +* [Orion Henry](https://github.com/orionz) +* [Hank Sims](https://github.com/hanksims) +* [Aivo Paas](https://github.com/aivopaas) +* [Paul Carey](https://github.com/paulcarey) +* [Pieter Noordhuis](https://github.com/pietern) +* [Vladimir Dronnikov](https://github.com/dvv) +* [Dave Hoover](https://github.com/redsquirrel) + +Thanks. + +## LICENSE - "MIT License" + +Copyright (c) 2010 Matthew Ranney, http://ranney.com/ + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +![spacer](http://ranney.com/1px.gif) diff --git a/node_modules/socket.io/node_modules/redis/changelog.md b/node_modules/socket.io/node_modules/redis/changelog.md new file mode 100644 index 0000000..f9a0b20 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/changelog.md @@ -0,0 +1,184 @@ +Changelog +========= + +## v0.6.7 - July 30, 2011 + +(accidentally skipped v0.6.6) + +Fix and test for [GH-123] + +Passing an Array as as the last argument should expand as users +expect. The old behavior was to coerce the arguments into Strings, +which did surprising things with Arrays. + +## v0.6.5 - July 6, 2011 + +Contributed changes: + +* Support SlowBuffers (Umair Siddique) +* Add Multi to exports (Louis-Philippe Perron) +* Fix for drain event calculation (Vladimir Dronnikov) + +Thanks! + +## v0.6.4 - June 30, 2011 + +Fix bug with optional callbacks for hmset. + +## v0.6.2 - June 30, 2011 + +Bugs fixed: + +* authentication retry while server is loading db (danmaz74) [GH-101] +* command arguments processing issue with arrays + +New features: + +* Auto update of new commands from redis.io (Dave Hoover) +* Performance improvements and backpressure controls. +* Commands now return the true/false value from the underlying socket write(s). +* Implement command_queue high water and low water for more better control of queueing. + +See `examples/backpressure_drain.js` for more information. + +## v0.6.1 - June 29, 2011 + +Add support and tests for Redis scripting through EXEC command. + +Bug fix for monitor mode. (forddg) + +Auto update of new commands from redis.io (Dave Hoover) + +## v0.6.0 - April 21, 2011 + +Lots of bugs fixed. + +* connection error did not properly trigger reconnection logic [GH-85] +* client.hmget(key, [val1, val2]) was not expanding properly [GH-66] +* client.quit() while in pub/sub mode would throw an error [GH-87] +* client.multi(['hmset', 'key', {foo: 'bar'}]) fails [GH-92] +* unsubscribe before subscribe would make things very confused [GH-88] +* Add BRPOPLPUSH [GH-79] + +## v0.5.11 - April 7, 2011 + +Added DISCARD + +I originally didn't think DISCARD would do anything here because of the clever MULTI interface, but somebody +pointed out to me that DISCARD can be used to flush the WATCH set. + +## v0.5.10 - April 6, 2011 + +Added HVALS + +## v0.5.9 - March 14, 2011 + +Fix bug with empty Array arguments - Andy Ray + +## v0.5.8 - March 14, 2011 + +Add `MONITOR` command and special monitor command reply parsing. + +## v0.5.7 - February 27, 2011 + +Add magical auth command. + +Authentication is now remembered by the client and will be automatically sent to the server +on every connection, including any reconnections. + +## v0.5.6 - February 22, 2011 + +Fix bug in ready check with `return_buffers` set to `true`. + +Thanks to Dean Mao and Austin Chau. + +## v0.5.5 - February 16, 2011 + +Add probe for server readiness. + +When a Redis server starts up, it might take a while to load the dataset into memory. +During this time, the server will accept connections, but will return errors for all non-INFO +commands. Now node_redis will send an INFO command whenever it connects to a server. +If the info command indicates that the server is not ready, the client will keep trying until +the server is ready. Once it is ready, the client will emit a "ready" event as well as the +"connect" event. The client will queue up all commands sent before the server is ready, just +like it did before. When the server is ready, all offline/non-ready commands will be replayed. +This should be backward compatible with previous versions. + +To disable this ready check behavior, set `options.no_ready_check` when creating the client. + +As a side effect of this change, the key/val params from the info command are available as +`client.server_options`. Further, the version string is decomposed into individual elements +in `client.server_options.versions`. + +## v0.5.4 - February 11, 2011 + +Fix excess memory consumption from Queue backing store. + +Thanks to Gustaf Sjöberg. + +## v0.5.3 - February 5, 2011 + +Fix multi/exec error reply callback logic. + +Thanks to Stella Laurenzo. + +## v0.5.2 - January 18, 2011 + +Fix bug where unhandled error replies confuse the parser. + +## v0.5.1 - January 18, 2011 + +Fix bug where subscribe commands would not handle redis-server startup error properly. + +## v0.5.0 - December 29, 2010 + +Some bug fixes: + +* An important bug fix in reconnection logic. Previously, reply callbacks would be invoked twice after + a reconnect. +* Changed error callback argument to be an actual Error object. + +New feature: + +* Add friendly syntax for HMSET using an object. + +## v0.4.1 - December 8, 2010 + +Remove warning about missing hiredis. You probably do want it though. + +## v0.4.0 - December 5, 2010 + +Support for multiple response parsers and hiredis C library from Pieter Noordhuis. +Return Strings instead of Buffers by default. +Empty nested mb reply bug fix. + +## v0.3.9 - November 30, 2010 + +Fix parser bug on failed EXECs. + +## v0.3.8 - November 10, 2010 + +Fix for null MULTI response when WATCH condition fails. + +## v0.3.7 - November 9, 2010 + +Add "drain" and "idle" events. + +## v0.3.6 - November 3, 2010 + +Add all known Redis commands from Redis master, even ones that are coming in 2.2 and beyond. + +Send a friendlier "error" event message on stream errors like connection refused / reset. + +## v0.3.5 - October 21, 2010 + +A few bug fixes. + +* Fixed bug with `nil` multi-bulk reply lengths that showed up with `BLPOP` timeouts. +* Only emit `end` once when connection goes away. +* Fixed bug in `test.js` where driver finished before all tests completed. + +## unversioned wasteland + +See the git history for what happened before. diff --git a/node_modules/socket.io/node_modules/redis/eval_test.js b/node_modules/socket.io/node_modules/redis/eval_test.js new file mode 100644 index 0000000..c1fbf8a --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/eval_test.js @@ -0,0 +1,9 @@ +var redis = require("./index"), + client = redis.createClient(); + +redis.debug_mode = true; + +client.eval("return 100.5", 0, function (err, res) { + console.dir(err); + console.dir(res); +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/auth.js b/node_modules/socket.io/node_modules/redis/examples/auth.js new file mode 100644 index 0000000..6c0a563 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/auth.js @@ -0,0 +1,5 @@ +var redis = require("redis"), + client = redis.createClient(); + +// This command is magical. Client stashes the password and will issue on every connect. +client.auth("somepass"); diff --git a/node_modules/socket.io/node_modules/redis/examples/backpressure_drain.js b/node_modules/socket.io/node_modules/redis/examples/backpressure_drain.js new file mode 100644 index 0000000..3488ef4 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/backpressure_drain.js @@ -0,0 +1,33 @@ +var redis = require("../index"), + client = redis.createClient(null, null, { + command_queue_high_water: 5, + command_queue_low_water: 1 + }), + remaining_ops = 100000, paused = false; + +function op() { + if (remaining_ops <= 0) { + console.error("Finished."); + process.exit(0); + } + + remaining_ops--; + if (client.hset("test hash", "val " + remaining_ops, remaining_ops) === false) { + console.log("Pausing at " + remaining_ops); + paused = true; + } else { + process.nextTick(op); + } +} + +client.on("drain", function () { + if (paused) { + console.log("Resuming at " + remaining_ops); + paused = false; + process.nextTick(op); + } else { + console.log("Got drain while not paused at " + remaining_ops); + } +}); + +op(); diff --git a/node_modules/socket.io/node_modules/redis/examples/extend.js b/node_modules/socket.io/node_modules/redis/examples/extend.js new file mode 100644 index 0000000..488b8c2 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/extend.js @@ -0,0 +1,24 @@ +var redis = require("redis"), + client = redis.createClient(); + +// Extend the RedisClient prototype to add a custom method +// This one converts the results from "INFO" into a JavaScript Object + +redis.RedisClient.prototype.parse_info = function (callback) { + this.info(function (err, res) { + var lines = res.toString().split("\r\n").sort(); + var obj = {}; + lines.forEach(function (line) { + var parts = line.split(':'); + if (parts[1]) { + obj[parts[0]] = parts[1]; + } + }); + callback(obj) + }); +}; + +client.parse_info(function (info) { + console.dir(info); + client.quit(); +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/file.js b/node_modules/socket.io/node_modules/redis/examples/file.js new file mode 100644 index 0000000..4d2b5d1 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/file.js @@ -0,0 +1,32 @@ +// Read a file from disk, store it in Redis, then read it back from Redis. + +var redis = require("redis"), + client = redis.createClient(), + fs = require("fs"), + filename = "kids_in_cart.jpg"; + +// Get the file I use for testing like this: +// curl http://ranney.com/kids_in_cart.jpg -o kids_in_cart.jpg +// or just use your own file. + +// Read a file from fs, store it in Redis, get it back from Redis, write it back to fs. +fs.readFile(filename, function (err, data) { + if (err) throw err + console.log("Read " + data.length + " bytes from filesystem."); + + client.set(filename, data, redis.print); // set entire file + client.get(filename, function (err, reply) { // get entire file + if (err) { + console.log("Get error: " + err); + } else { + fs.writeFile("duplicate_" + filename, reply, function (err) { + if (err) { + console.log("Error on write: " + err) + } else { + console.log("File written."); + } + client.end(); + }); + } + }); +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/mget.js b/node_modules/socket.io/node_modules/redis/examples/mget.js new file mode 100644 index 0000000..936740d --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/mget.js @@ -0,0 +1,5 @@ +var client = require("redis").createClient(); + +client.mget(["sessions started", "sessions started", "foo"], function (err, res) { + console.dir(res); +}); \ No newline at end of file diff --git a/node_modules/socket.io/node_modules/redis/examples/monitor.js b/node_modules/socket.io/node_modules/redis/examples/monitor.js new file mode 100644 index 0000000..2cb6a4e --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/monitor.js @@ -0,0 +1,10 @@ +var client = require("../index").createClient(), + util = require("util"); + +client.monitor(function (err, res) { + console.log("Entering monitoring mode."); +}); + +client.on("monitor", function (time, args) { + console.log(time + ": " + util.inspect(args)); +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/multi.js b/node_modules/socket.io/node_modules/redis/examples/multi.js new file mode 100644 index 0000000..35c08e1 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/multi.js @@ -0,0 +1,46 @@ +var redis = require("redis"), + client = redis.createClient(), set_size = 20; + +client.sadd("bigset", "a member"); +client.sadd("bigset", "another member"); + +while (set_size > 0) { + client.sadd("bigset", "member " + set_size); + set_size -= 1; +} + +// multi chain with an individual callback +client.multi() + .scard("bigset") + .smembers("bigset") + .keys("*", function (err, replies) { + client.mget(replies, redis.print); + }) + .dbsize() + .exec(function (err, replies) { + console.log("MULTI got " + replies.length + " replies"); + replies.forEach(function (reply, index) { + console.log("Reply " + index + ": " + reply.toString()); + }); + }); + +client.mset("incr thing", 100, "incr other thing", 1, redis.print); + +// start a separate multi command queue +var multi = client.multi(); +multi.incr("incr thing", redis.print); +multi.incr("incr other thing", redis.print); + +// runs immediately +client.get("incr thing", redis.print); // 100 + +// drains multi queue and runs atomically +multi.exec(function (err, replies) { + console.log(replies); // 101, 2 +}); + +// you can re-run the same transaction if you like +multi.exec(function (err, replies) { + console.log(replies); // 102, 3 + client.quit(); +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/multi2.js b/node_modules/socket.io/node_modules/redis/examples/multi2.js new file mode 100644 index 0000000..8be4d73 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/multi2.js @@ -0,0 +1,29 @@ +var redis = require("redis"), + client = redis.createClient(), multi; + +// start a separate command queue for multi +multi = client.multi(); +multi.incr("incr thing", redis.print); +multi.incr("incr other thing", redis.print); + +// runs immediately +client.mset("incr thing", 100, "incr other thing", 1, redis.print); + +// drains multi queue and runs atomically +multi.exec(function (err, replies) { + console.log(replies); // 101, 2 +}); + +// you can re-run the same transaction if you like +multi.exec(function (err, replies) { + console.log(replies); // 102, 3 + client.quit(); +}); + +client.multi([ + ["mget", "multifoo", "multibar", redis.print], + ["incr", "multifoo"], + ["incr", "multibar"] +]).exec(function (err, replies) { + console.log(replies.toString()); +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/psubscribe.js b/node_modules/socket.io/node_modules/redis/examples/psubscribe.js new file mode 100644 index 0000000..c57117b --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/psubscribe.js @@ -0,0 +1,33 @@ +var redis = require("redis"), + client1 = redis.createClient(), + client2 = redis.createClient(), + client3 = redis.createClient(), + client4 = redis.createClient(), + msg_count = 0; + +redis.debug_mode = false; + +client1.on("psubscribe", function (pattern, count) { + console.log("client1 psubscribed to " + pattern + ", " + count + " total subscriptions"); + client2.publish("channeltwo", "Me!"); + client3.publish("channelthree", "Me too!"); + client4.publish("channelfour", "And me too!"); +}); + +client1.on("punsubscribe", function (pattern, count) { + console.log("client1 punsubscribed from " + pattern + ", " + count + " total subscriptions"); + client4.end(); + client3.end(); + client2.end(); + client1.end(); +}); + +client1.on("pmessage", function (pattern, channel, message) { + console.log("("+ pattern +")" + " client1 received message on " + channel + ": " + message); + msg_count += 1; + if (msg_count === 3) { + client1.punsubscribe(); + } +}); + +client1.psubscribe("channel*"); diff --git a/node_modules/socket.io/node_modules/redis/examples/pub_sub.js b/node_modules/socket.io/node_modules/redis/examples/pub_sub.js new file mode 100644 index 0000000..aa508d6 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/pub_sub.js @@ -0,0 +1,41 @@ +var redis = require("redis"), + client1 = redis.createClient(), msg_count = 0, + client2 = redis.createClient(); + +redis.debug_mode = false; + +// Most clients probably don't do much on "subscribe". This example uses it to coordinate things within one program. +client1.on("subscribe", function (channel, count) { + console.log("client1 subscribed to " + channel + ", " + count + " total subscriptions"); + if (count === 2) { + client2.publish("a nice channel", "I am sending a message."); + client2.publish("another one", "I am sending a second message."); + client2.publish("a nice channel", "I am sending my last message."); + } +}); + +client1.on("unsubscribe", function (channel, count) { + console.log("client1 unsubscribed from " + channel + ", " + count + " total subscriptions"); + if (count === 0) { + client2.end(); + client1.end(); + } +}); + +client1.on("message", function (channel, message) { + console.log("client1 channel " + channel + ": " + message); + msg_count += 1; + if (msg_count === 3) { + client1.unsubscribe(); + } +}); + +client1.on("ready", function () { + // if you need auth, do it here + client1.incr("did a thing"); + client1.subscribe("a nice channel", "another one"); +}); + +client2.on("ready", function () { + // if you need auth, do it here +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/simple.js b/node_modules/socket.io/node_modules/redis/examples/simple.js new file mode 100644 index 0000000..b93c557 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/simple.js @@ -0,0 +1,17 @@ +var redis = require("redis"), + client = redis.createClient(); + +client.on("error", function (err) { + console.log("Redis connection error to " + client.host + ":" + client.port + " - " + err); +}); + +client.set("string key", "string val", redis.print); +client.hset("hash key", "hashtest 1", "some value", redis.print); +client.hset(["hash key", "hashtest 2", "some other value"], redis.print); +client.hkeys("hash key", function (err, replies) { + console.log(replies.length + " replies:"); + replies.forEach(function (reply, i) { + console.log(" " + i + ": " + reply); + }); + client.quit(); +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/subqueries.js b/node_modules/socket.io/node_modules/redis/examples/subqueries.js new file mode 100644 index 0000000..560db24 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/subqueries.js @@ -0,0 +1,15 @@ +// Sending commands in response to other commands. +// This example runs "type" against every key in the database +// +var client = require("redis").createClient(); + +client.keys("*", function (err, keys) { + keys.forEach(function (key, pos) { + client.type(key, function (err, keytype) { + console.log(key + " is " + keytype); + if (pos === (keys.length - 1)) { + client.quit(); + } + }); + }); +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/subquery.js b/node_modules/socket.io/node_modules/redis/examples/subquery.js new file mode 100644 index 0000000..861657e --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/subquery.js @@ -0,0 +1,19 @@ +var client = require("redis").createClient(); + +function print_results(obj) { + console.dir(obj); +} + +// build a map of all keys and their types +client.keys("*", function (err, all_keys) { + var key_types = {}; + + all_keys.forEach(function (key, pos) { // use second arg of forEach to get pos + client.type(key, function (err, type) { + key_types[key] = type; + if (pos === all_keys.length - 1) { // callbacks all run in order + print_results(key_types); + } + }); + }); +}); diff --git a/node_modules/socket.io/node_modules/redis/examples/unix_socket.js b/node_modules/socket.io/node_modules/redis/examples/unix_socket.js new file mode 100644 index 0000000..4a5e0bb --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/unix_socket.js @@ -0,0 +1,29 @@ +var redis = require("redis"), + client = redis.createClient("/tmp/redis.sock"), + profiler = require("v8-profiler"); + +client.on("connect", function () { + console.log("Got Unix socket connection.") +}); + +client.on("error", function (err) { + console.log(err.message); +}); + +client.set("space chars", "space value"); + +setInterval(function () { + client.get("space chars"); +}, 100); + +function done() { + client.info(function (err, reply) { + console.log(reply.toString()); + client.quit(); + }); +} + +setTimeout(function () { + console.log("Taking snapshot."); + var snap = profiler.takeSnapshot(); +}, 5000); diff --git a/node_modules/socket.io/node_modules/redis/examples/web_server.js b/node_modules/socket.io/node_modules/redis/examples/web_server.js new file mode 100644 index 0000000..9fd8592 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/examples/web_server.js @@ -0,0 +1,31 @@ +// A simple web server that generates dyanmic content based on responses from Redis + +var http = require("http"), server, + redis_client = require("redis").createClient(); + +server = http.createServer(function (request, response) { + response.writeHead(200, { + "Content-Type": "text/plain" + }); + + var redis_info, total_requests; + + redis_client.info(function (err, reply) { + redis_info = reply; // stash response in outer scope + }); + redis_client.incr("requests", function (err, reply) { + total_requests = reply; // stash response in outer scope + }); + redis_client.hincrby("ip", request.connection.remoteAddress, 1); + redis_client.hgetall("ip", function (err, reply) { + // This is the last reply, so all of the previous replies must have completed already + response.write("This page was generated after talking to redis.\n\n" + + "Redis info:\n" + redis_info + "\n" + + "Total requests: " + total_requests + "\n\n" + + "IP count: \n"); + Object.keys(reply).forEach(function (ip) { + response.write(" " + ip + ": " + reply[ip] + "\n"); + }); + response.end(); + }); +}).listen(80); diff --git a/node_modules/socket.io/node_modules/redis/generate_commands.js b/node_modules/socket.io/node_modules/redis/generate_commands.js new file mode 100644 index 0000000..e94d74e --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/generate_commands.js @@ -0,0 +1,40 @@ +var http = require("http"), + sys = require("sys"), + fs = require("fs"); + +function prettyCurrentTime() { + var date = new Date(); + return date.toLocaleString(); +} + +function write_file(commands, path) { + var file_contents, out_commands; + + console.log("Writing " + Object.keys(commands).length + " commands to " + path); + + file_contents = "// This file was generated by ./generate_commands.js on " + prettyCurrentTime() + "\n"; + + out_commands = Object.keys(commands).map(function (key) { + return key.toLowerCase(); + }); + + file_contents += "module.exports = " + JSON.stringify(out_commands, null, " ") + ";\n"; + + fs.writeFile(path, file_contents); +} + +http.get({host: "redis.io", path: "/commands.json"}, function (res) { + var body = ""; + + console.log("Response from redis.io/commands.json: " + res.statusCode); + + res.on('data', function (chunk) { + body += chunk; + }); + + res.on('end', function () { + write_file(JSON.parse(body), "lib/commands.js"); + }); +}).on('error', function (e) { + console.log("Error fetching command list from redis.io: " + e.message); +}); diff --git a/node_modules/socket.io/node_modules/redis/index.js b/node_modules/socket.io/node_modules/redis/index.js new file mode 100644 index 0000000..8728cea --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/index.js @@ -0,0 +1,860 @@ +/*global Buffer require exports console setTimeout */ + +var net = require("net"), + util = require("./lib/util").util, + Queue = require("./lib/queue").Queue, + to_array = require("./lib/to_array"), + events = require("events"), + parsers = [], commands, + default_port = 6379, + default_host = "127.0.0.1"; + +// can set this to true to enable for all connections +exports.debug_mode = false; + +// hiredis might not be installed +try { + require("./lib/parser/hiredis"); + parsers.push(require("./lib/parser/hiredis")); +} catch (err) { + if (exports.debug_mode) { + console.log("hiredis parser not installed."); + } +} + +parsers.push(require("./lib/parser/javascript")); + +function RedisClient(stream, options) { + this.stream = stream; + this.options = options || {}; + + this.connected = false; + this.ready = false; + this.connections = 0; + this.attempts = 1; + this.should_buffer = false; + this.command_queue_high_water = this.options.command_queue_high_water || 1000; + this.command_queue_low_water = this.options.command_queue_low_water || 0; + this.command_queue = new Queue(); // holds sent commands to de-pipeline them + this.offline_queue = new Queue(); // holds commands issued but not able to be sent + this.commands_sent = 0; + this.retry_delay = 250; // inital reconnection delay + this.current_retry_delay = this.retry_delay; + this.retry_backoff = 1.7; // each retry waits current delay * retry_backoff + this.subscriptions = false; + this.monitoring = false; + this.closing = false; + this.server_info = {}; + this.auth_pass = null; + + var parser_module, self = this; + + if (self.options.parser) { + if (! parsers.some(function (parser) { + if (parser.name === self.options.parser) { + parser_module = parser; + if (exports.debug_mode) { + console.log("Using parser module: " + parser_module.name); + } + return true; + } + })) { + throw new Error("Couldn't find named parser " + self.options.parser + " on this system"); + } + } else { + if (exports.debug_mode) { + console.log("Using default parser module: " + parsers[0].name); + } + parser_module = parsers[0]; + } + + parser_module.debug_mode = exports.debug_mode; + this.reply_parser = new parser_module.Parser({ + return_buffers: self.options.return_buffers || false + }); + + // "reply error" is an error sent back by Redis + this.reply_parser.on("reply error", function (reply) { + self.return_error(new Error(reply)); + }); + this.reply_parser.on("reply", function (reply) { + self.return_reply(reply); + }); + // "error" is bad. Somehow the parser got confused. It'll try to reset and continue. + this.reply_parser.on("error", function (err) { + self.emit("error", new Error("Redis reply parser error: " + err.stack)); + }); + + this.stream.on("connect", function () { + self.on_connect(); + }); + + this.stream.on("data", function (buffer_from_socket) { + self.on_data(buffer_from_socket); + }); + + this.stream.on("error", function (msg) { + if (this.closing) { + return; + } + + var message = "Redis connection to " + self.host + ":" + self.port + " failed - " + msg.message; + + if (exports.debug_mode) { + console.warn(message); + } + self.offline_queue.forEach(function (args) { + if (typeof args[2] === "function") { + args[2](message); + } + }); + self.offline_queue = new Queue(); + + self.command_queue.forEach(function (args) { + if (typeof args[2] === "function") { + args[2](message); + } + }); + self.command_queue = new Queue(); + + self.connected = false; + self.ready = false; + + self.emit("error", new Error(message)); + // "error" events get turned into exceptions if they aren't listened for. If the user handled this error + // then we should try to reconnect. + self.connection_gone("error"); + }); + + this.stream.on("close", function () { + self.connection_gone("close"); + }); + + this.stream.on("end", function () { + self.connection_gone("end"); + }); + + this.stream.on("drain", function () { + self.should_buffer = false; + self.emit("drain"); + }); + + events.EventEmitter.call(this); +} +util.inherits(RedisClient, events.EventEmitter); +exports.RedisClient = RedisClient; + +RedisClient.prototype.do_auth = function () { + var self = this; + + if (exports.debug_mode) { + console.log("Sending auth to " + self.host + ":" + self.port + " fd " + self.stream.fd); + } + self.send_anyway = true; + self.send_command("auth", [this.auth_pass], function (err, res) { + if (err) { + if (err.toString().match("LOADING")) { + // if redis is still loading the db, it will not authenticate and everything else will fail + console.log("Redis still loading, trying to authenticate later"); + setTimeout(function () { + self.do_auth(); + }, 2000); // TODO - magic number alert + return; + } else { + return self.emit("error", "Auth error: " + err); + } + } + if (res.toString() !== "OK") { + return self.emit("error", "Auth failed: " + res.toString()); + } + if (exports.debug_mode) { + console.log("Auth succeeded " + self.host + ":" + self.port + " fd " + self.stream.fd); + } + if (self.auth_callback) { + self.auth_callback(err, res); + self.auth_callback = null; + } + + // now we are really connected + self.emit("connect"); + if (self.options.no_ready_check) { + self.ready = true; + self.send_offline_queue(); + } else { + self.ready_check(); + } + }); + self.send_anyway = false; +}; + +RedisClient.prototype.on_connect = function () { + if (exports.debug_mode) { + console.log("Stream connected " + this.host + ":" + this.port + " fd " + this.stream.fd); + } + var self = this; + + this.connected = true; + this.ready = false; + this.attempts = 0; + this.connections += 1; + this.command_queue = new Queue(); + this.emitted_end = false; + this.retry_timer = null; + this.current_retry_delay = this.retry_time; + this.stream.setNoDelay(); + this.stream.setTimeout(0); + + if (this.auth_pass) { + this.do_auth(); + } else { + this.emit("connect"); + + if (this.options.no_ready_check) { + this.ready = true; + this.send_offline_queue(); + } else { + this.ready_check(); + } + } +}; + +RedisClient.prototype.ready_check = function () { + var self = this; + + function send_info_cmd() { + if (exports.debug_mode) { + console.log("checking server ready state..."); + } + + self.send_anyway = true; // secret flag to send_command to send something even if not "ready" + self.info(function (err, res) { + if (err) { + return self.emit("error", "Ready check failed: " + err); + } + + var lines = res.toString().split("\r\n"), obj = {}, retry_time; + + lines.forEach(function (line) { + var parts = line.split(':'); + if (parts[1]) { + obj[parts[0]] = parts[1]; + } + }); + + obj.versions = []; + obj.redis_version.split('.').forEach(function (num) { + obj.versions.push(+num); + }); + + // expose info key/vals to users + self.server_info = obj; + + if (!obj.loading || (obj.loading && obj.loading === "0")) { + if (exports.debug_mode) { + console.log("Redis server ready."); + } + self.ready = true; + + self.send_offline_queue(); + self.emit("ready"); + } else { + retry_time = obj.loading_eta_seconds * 1000; + if (retry_time > 1000) { + retry_time = 1000; + } + if (exports.debug_mode) { + console.log("Redis server still loading, trying again in " + retry_time); + } + setTimeout(send_info_cmd, retry_time); + } + }); + self.send_anyway = false; + } + + send_info_cmd(); +}; + +RedisClient.prototype.send_offline_queue = function () { + var command_obj, buffered_writes = 0; + while (this.offline_queue.length > 0) { + command_obj = this.offline_queue.shift(); + if (exports.debug_mode) { + console.log("Sending offline command: " + command_obj.command); + } + buffered_writes += !this.send_command(command_obj.command, command_obj.args, command_obj.callback); + } + this.offline_queue = new Queue(); + // Even though items were shifted off, Queue backing store still uses memory until next add, so just get a new Queue + + if (!buffered_writes) { + this.should_buffer = false; + this.emit("drain"); + } +}; + +RedisClient.prototype.connection_gone = function (why) { + var self = this; + + // If a retry is already in progress, just let that happen + if (this.retry_timer) { + return; + } + + // Note that this may trigger another "close" or "end" event + this.stream.destroy(); + + if (exports.debug_mode) { + console.warn("Redis connection is gone from " + why + " event."); + } + this.connected = false; + this.ready = false; + this.subscriptions = false; + this.monitoring = false; + + // since we are collapsing end and close, users don't expect to be called twice + if (! this.emitted_end) { + this.emit("end"); + this.emitted_end = true; + } + + this.command_queue.forEach(function (args) { + if (typeof args[2] === "function") { + args[2]("Server connection closed"); + } + }); + this.command_queue = new Queue(); + + // If this is a requested shutdown, then don't retry + if (this.closing) { + this.retry_timer = null; + return; + } + + this.current_retry_delay = this.retry_delay * this.retry_backoff; + + if (exports.debug_mode) { + console.log("Retry connection in " + this.current_retry_delay + " ms"); + } + this.attempts += 1; + this.emit("reconnecting", { + delay: this.current_retry_delay, + attempt: this.attempts + }); + this.retry_timer = setTimeout(function () { + if (exports.debug_mode) { + console.log("Retrying connection..."); + } + self.stream.connect(self.port, self.host); + self.retry_timer = null; + }, this.current_retry_delay); +}; + +RedisClient.prototype.on_data = function (data) { + if (exports.debug_mode) { + console.log("net read " + this.host + ":" + this.port + " fd " + this.stream.fd + ": " + data.toString()); + } + + try { + this.reply_parser.execute(data); + } catch (err) { + // This is an unexpected parser problem, an exception that came from the parser code itself. + // Parser should emit "error" events if it notices things are out of whack. + // Callbacks that throw exceptions will land in return_reply(), below. + // TODO - it might be nice to have a different "error" event for different types of errors + this.emit("error", err); + } +}; + +RedisClient.prototype.return_error = function (err) { + var command_obj = this.command_queue.shift(), queue_len = this.command_queue.getLength(); + + if (this.subscriptions === false && queue_len === 0) { + this.emit("idle"); + this.command_queue = new Queue(); + } + if (this.should_buffer && queue_len <= this.command_queue_low_water) { + this.emit("drain"); + this.should_buffer = false; + } + + if (command_obj && typeof command_obj.callback === "function") { + try { + command_obj.callback(err); + } catch (callback_err) { + // if a callback throws an exception, re-throw it on a new stack so the parser can keep going + process.nextTick(function () { + throw callback_err; + }); + } + } else { + console.log("node_redis: no callback to send error: " + err.message); + // this will probably not make it anywhere useful, but we might as well throw + process.nextTick(function () { + throw err; + }); + } +}; + +RedisClient.prototype.return_reply = function (reply) { + var command_obj = this.command_queue.shift(), + obj, i, len, key, val, type, timestamp, args, queue_len = this.command_queue.getLength(); + + if (this.subscriptions === false && queue_len === 0) { + this.emit("idle"); + this.command_queue = new Queue(); // explicitly reclaim storage from old Queue + } + if (this.should_buffer && queue_len <= this.command_queue_low_water) { + this.emit("drain"); + this.should_buffer = false; + } + + if (command_obj && !command_obj.sub_command) { + if (typeof command_obj.callback === "function") { + // HGETALL special case replies with keyed Buffers + if (reply && 'hgetall' === command_obj.command.toLowerCase()) { + obj = {}; + for (i = 0, len = reply.length; i < len; i += 2) { + key = reply[i].toString(); + val = reply[i + 1]; + obj[key] = val; + } + reply = obj; + } + + try { + command_obj.callback(null, reply); + } catch (err) { + // if a callback throws an exception, re-throw it on a new stack so the parser can keep going + process.nextTick(function () { + throw err; + }); + } + } else if (exports.debug_mode) { + console.log("no callback for reply: " + (reply && reply.toString && reply.toString())); + } + } else if (this.subscriptions || (command_obj && command_obj.sub_command)) { + if (Array.isArray(reply)) { + type = reply[0].toString(); + + if (type === "message") { + this.emit("message", reply[1].toString(), reply[2]); // channel, message + } else if (type === "pmessage") { + this.emit("pmessage", reply[1].toString(), reply[2].toString(), reply[3]); // pattern, channel, message + } else if (type === "subscribe" || type === "unsubscribe" || type === "psubscribe" || type === "punsubscribe") { + if (reply[2] === 0) { + this.subscriptions = false; + if (this.debug_mode) { + console.log("All subscriptions removed, exiting pub/sub mode"); + } + } + this.emit(type, reply[1].toString(), reply[2]); // channel, count + } else { + throw new Error("subscriptions are active but got unknown reply type " + type); + } + } else if (! this.closing) { + throw new Error("subscriptions are active but got an invalid reply: " + reply); + } + } else if (this.monitoring) { + len = reply.indexOf(" "); + timestamp = reply.slice(0, len); + // TODO - this de-quoting doesn't work correctly if you put JSON strings in your values. + args = reply.slice(len + 1).match(/"[^"]+"/g).map(function (elem) { + return elem.replace(/"/g, ""); + }); + this.emit("monitor", timestamp, args); + } else { + throw new Error("node_redis command queue state error. If you can reproduce this, please report it."); + } +}; + +// This Command constructor is ever so slightly faster than using an object literal +function Command(command, args, sub_command, callback) { + this.command = command; + this.args = args; + this.sub_command = sub_command; + this.callback = callback; +} + +RedisClient.prototype.send_command = function (command, args, callback) { + var arg, this_args, command_obj, i, il, elem_count, stream = this.stream, buffer_args, command_str = "", buffered_writes = 0; + + if (typeof command !== "string") { + throw new Error("First argument to send_command must be the command name string, not " + typeof command); + } + + if (Array.isArray(args)) { + if (typeof callback === "function") { + // probably the fastest way: + // client.command([arg1, arg2], cb); (straight passthrough) + // send_command(command, [arg1, arg2], cb); + } else if (! callback) { + // most people find this variable argument length form more convenient, but it uses arguments, which is slower + // client.command(arg1, arg2, cb); (wraps up arguments into an array) + // send_command(command, [arg1, arg2, cb]); + // client.command(arg1, arg2); (callback is optional) + // send_command(command, [arg1, arg2]); + if (typeof args[args.length - 1] === "function") { + callback = args[args.length - 1]; + args.length -= 1; + } + } else { + throw new Error("send_command: last argument must be a callback or undefined"); + } + } else { + throw new Error("send_command: second argument must be an array"); + } + + // if the last argument is an array, expand it out. This allows commands like this: + // client.command(arg1, [arg2, arg3, arg4], cb); + // and converts to: + // client.command(arg1, arg2, arg3, arg4, cb); + // which is convenient for some things like sadd + if (Array.isArray(args[args.length - 1])) { + args = args.slice(0, -1).concat(args[args.length - 1]); + } + + command_obj = new Command(command, args, false, callback); + + if ((!this.ready && !this.send_anyway) || !stream.writable) { + if (exports.debug_mode) { + if (!stream.writable) { + console.log("send command: stream is not writeable."); + } + + console.log("Queueing " + command + " for next server connection."); + } + this.offline_queue.push(command_obj); + this.should_buffer = true; + return false; + } + + if (command === "subscribe" || command === "psubscribe" || command === "unsubscribe" || command === "punsubscribe") { + if (this.subscriptions === false && exports.debug_mode) { + console.log("Entering pub/sub mode from " + command); + } + command_obj.sub_command = true; + this.subscriptions = true; + } else if (command === "monitor") { + this.monitoring = true; + } else if (command === "quit") { + this.closing = true; + } else if (this.subscriptions === true) { + throw new Error("Connection in pub/sub mode, only pub/sub commands may be used"); + } + this.command_queue.push(command_obj); + this.commands_sent += 1; + + elem_count = 1; + buffer_args = false; + + elem_count += args.length; + + // Always use "Multi bulk commands", but if passed any Buffer args, then do multiple writes, one for each arg + // This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer. + // Also, why am I putting user documentation in the library source code? + + command_str = "*" + elem_count + "\r\n$" + command.length + "\r\n" + command + "\r\n"; + + for (i = 0, il = args.length, arg; i < il; i += 1) { + if (Buffer.isBuffer(args[i])) { + buffer_args = true; + } + } + + if (! buffer_args) { // Build up a string and send entire command in one write + for (i = 0, il = args.length, arg; i < il; i += 1) { + arg = args[i]; + if (typeof arg !== "string") { + arg = String(arg); + } + command_str += "$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n"; + } + if (exports.debug_mode) { + console.log("send " + this.host + ":" + this.port + " fd " + this.stream.fd + ": " + command_str); + } + buffered_writes += !stream.write(command_str); + } else { + if (exports.debug_mode) { + console.log("send command (" + command_str + ") has Buffer arguments"); + } + buffered_writes += !stream.write(command_str); + + for (i = 0, il = args.length, arg; i < il; i += 1) { + arg = args[i]; + if (!(Buffer.isBuffer(arg) || arg instanceof String)) { + arg = String(arg); + } + + if (Buffer.isBuffer(arg)) { + if (arg.length === 0) { + if (exports.debug_mode) { + console.log("send_command: using empty string for 0 length buffer"); + } + buffered_writes += !stream.write("$0\r\n\r\n"); + } else { + buffered_writes += !stream.write("$" + arg.length + "\r\n"); + buffered_writes += !stream.write(arg); + buffered_writes += !stream.write("\r\n"); + if (exports.debug_mode) { + console.log("send_command: buffer send " + arg.length + " bytes"); + } + } + } else { + if (exports.debug_mode) { + console.log("send_command: string send " + Buffer.byteLength(arg) + " bytes: " + arg); + } + buffered_writes += !stream.write("$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n"); + } + } + } + if (exports.debug_mode) { + console.log("send_command buffered_writes: " + buffered_writes, " should_buffer: " + this.should_buffer); + } + if (buffered_writes || this.command_queue.getLength() >= this.command_queue_high_water) { + this.should_buffer = true; + } + return !this.should_buffer; +}; + +RedisClient.prototype.end = function () { + this.stream._events = {}; + this.connected = false; + this.ready = false; + return this.stream.end(); +}; + +function Multi(client, args) { + this.client = client; + this.queue = [["MULTI"]]; + if (Array.isArray(args)) { + this.queue = this.queue.concat(args); + } +} + +exports.Multi = Multi; + +// take 2 arrays and return the union of their elements +function set_union(seta, setb) { + var obj = {}; + + seta.forEach(function (val) { + obj[val] = true; + }); + setb.forEach(function (val) { + obj[val] = true; + }); + return Object.keys(obj); +} + +// This static list of commands is updated from time to time. ./lib/commands.js can be updated with generate_commands.js +commands = set_union(["get", "set", "setnx", "setex", "append", "strlen", "del", "exists", "setbit", "getbit", "setrange", "getrange", "substr", + "incr", "decr", "mget", "rpush", "lpush", "rpushx", "lpushx", "linsert", "rpop", "lpop", "brpop", "brpoplpush", "blpop", "llen", "lindex", + "lset", "lrange", "ltrim", "lrem", "rpoplpush", "sadd", "srem", "smove", "sismember", "scard", "spop", "srandmember", "sinter", "sinterstore", + "sunion", "sunionstore", "sdiff", "sdiffstore", "smembers", "zadd", "zincrby", "zrem", "zremrangebyscore", "zremrangebyrank", "zunionstore", + "zinterstore", "zrange", "zrangebyscore", "zrevrangebyscore", "zcount", "zrevrange", "zcard", "zscore", "zrank", "zrevrank", "hset", "hsetnx", + "hget", "hmset", "hmget", "hincrby", "hdel", "hlen", "hkeys", "hvals", "hgetall", "hexists", "incrby", "decrby", "getset", "mset", "msetnx", + "randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "auth", "ping", "echo", "save", "bgsave", + "bgrewriteaof", "shutdown", "lastsave", "type", "multi", "exec", "discard", "sync", "flushdb", "flushall", "sort", "info", "monitor", "ttl", + "persist", "slaveof", "debug", "config", "subscribe", "unsubscribe", "psubscribe", "punsubscribe", "publish", "watch", "unwatch", "cluster", + "restore", "migrate", "dump", "object", "client", "eval", "evalsha"], require("./lib/commands")); + +commands.forEach(function (command) { + RedisClient.prototype[command] = function (args, callback) { + if (Array.isArray(args) && typeof callback === "function") { + return this.send_command(command, args, callback); + } else { + return this.send_command(command, to_array(arguments)); + } + }; + RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command]; + + Multi.prototype[command] = function () { + this.queue.push([command].concat(to_array(arguments))); + return this; + }; + Multi.prototype[command.toUpperCase()] = Multi.prototype[command]; +}); + +// Stash auth for connect and reconnect. Send immediately if already connected. +RedisClient.prototype.auth = function () { + var args = to_array(arguments); + this.auth_pass = args[0]; + this.auth_callback = args[1]; + if (exports.debug_mode) { + console.log("Saving auth as " + this.auth_pass); + } + + if (this.connected) { + this.send_command("auth", args); + } +}; +RedisClient.prototype.AUTH = RedisClient.prototype.auth; + +RedisClient.prototype.hmget = function (arg1, arg2, arg3) { + if (Array.isArray(arg2) && typeof arg3 === "function") { + return this.send_command("hmget", [arg1].concat(arg2), arg3); + } else if (Array.isArray(arg1) && typeof arg2 === "function") { + return this.send_command("hmget", arg1, arg2); + } else { + return this.send_command("hmget", to_array(arguments)); + } +}; +RedisClient.prototype.HMGET = RedisClient.prototype.hmget; + +RedisClient.prototype.hmset = function (args, callback) { + var tmp_args, tmp_keys, i, il, key; + + if (Array.isArray(args) && typeof callback === "function") { + return this.send_command("hmset", args, callback); + } + + args = to_array(arguments); + if (typeof args[args.length - 1] === "function") { + callback = args[args.length - 1]; + args.length -= 1; + } else { + callback = null; + } + + if (args.length === 2 && typeof args[0] === "string" && typeof args[1] === "object") { + // User does: client.hmset(key, {key1: val1, key2: val2}) + tmp_args = [ args[0] ]; + tmp_keys = Object.keys(args[1]); + for (i = 0, il = tmp_keys.length; i < il ; i++) { + key = tmp_keys[i]; + tmp_args.push(key); + tmp_args.push(args[1][key]); + } + args = tmp_args; + } + + return this.send_command("hmset", args, callback); +}; +RedisClient.prototype.HMSET = RedisClient.prototype.hmset; + +Multi.prototype.hmset = function () { + var args = to_array(arguments), tmp_args; + if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") { + tmp_args = [ "hmset", args[0] ]; + Object.keys(args[1]).map(function (key) { + tmp_args.push(key); + tmp_args.push(args[1][key]); + }); + if (args[2]) { + tmp_args.push(args[2]); + } + args = tmp_args; + } else { + args.unshift("hmset"); + } + + this.queue.push(args); + return this; +}; +Multi.prototype.HMSET = Multi.prototype.hmset; + +Multi.prototype.exec = function (callback) { + var self = this; + + // drain queue, callback will catch "QUEUED" or error + // TODO - get rid of all of these anonymous functions which are elegant but slow + this.queue.forEach(function (args, index) { + var command = args[0], obj; + if (typeof args[args.length - 1] === "function") { + args = args.slice(1, -1); + } else { + args = args.slice(1); + } + if (args.length === 1 && Array.isArray(args[0])) { + args = args[0]; + } + if (command === 'hmset' && typeof args[1] === 'object') { + obj = args.pop(); + Object.keys(obj).forEach(function (key) { + args.push(key); + args.push(obj[key]); + }); + } + this.client.send_command(command, args, function (err, reply) { + if (err) { + var cur = self.queue[index]; + if (typeof cur[cur.length - 1] === "function") { + cur[cur.length - 1](err); + } else { + throw new Error(err); + } + self.queue.splice(index, 1); + } + }); + }, this); + + // TODO - make this callback part of Multi.prototype instead of creating it each time + return this.client.send_command("EXEC", [], function (err, replies) { + if (err) { + if (callback) { + callback(new Error(err)); + return; + } else { + throw new Error(err); + } + } + + var i, il, j, jl, reply, args, obj, key, val; + + if (replies) { + for (i = 1, il = self.queue.length; i < il; i += 1) { + reply = replies[i - 1]; + args = self.queue[i]; + + // Convert HGETALL reply to object + if (reply && args[0].toLowerCase() === "hgetall") { + obj = {}; + for (j = 0, jl = reply.length; j < jl; j += 2) { + key = reply[j].toString(); + val = reply[j + 1]; + obj[key] = val; + } + replies[i - 1] = reply = obj; + } + + if (typeof args[args.length - 1] === "function") { + args[args.length - 1](null, reply); + } + } + } + + if (callback) { + callback(null, replies); + } + }); +}; + +RedisClient.prototype.multi = function (args) { + return new Multi(this, args); +}; +RedisClient.prototype.MULTI = function (args) { + return new Multi(this, args); +}; + +exports.createClient = function (port_arg, host_arg, options) { + var port = port_arg || default_port, + host = host_arg || default_host, + redis_client, net_client; + + net_client = net.createConnection(port, host); + + redis_client = new RedisClient(net_client, options); + + redis_client.port = port; + redis_client.host = host; + + return redis_client; +}; + +exports.print = function (err, reply) { + if (err) { + console.log("Error: " + err); + } else { + console.log("Reply: " + reply); + } +}; diff --git a/node_modules/socket.io/node_modules/redis/lib/commands.js b/node_modules/socket.io/node_modules/redis/lib/commands.js new file mode 100644 index 0000000..0293ae8 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/lib/commands.js @@ -0,0 +1,126 @@ +// This file was generated by ./generate_commands.js on Tue Jun 28 2011 22:37:02 GMT-0700 (PDT) +module.exports = [ + "append", + "auth", + "bgrewriteaof", + "bgsave", + "blpop", + "brpop", + "brpoplpush", + "config get", + "config set", + "config resetstat", + "dbsize", + "debug object", + "debug segfault", + "decr", + "decrby", + "del", + "discard", + "echo", + "exec", + "exists", + "expire", + "expireat", + "flushall", + "flushdb", + "get", + "getbit", + "getrange", + "getset", + "hdel", + "hexists", + "hget", + "hgetall", + "hincrby", + "hkeys", + "hlen", + "hmget", + "hmset", + "hset", + "hsetnx", + "hvals", + "incr", + "incrby", + "info", + "keys", + "lastsave", + "lindex", + "linsert", + "llen", + "lpop", + "lpush", + "lpushx", + "lrange", + "lrem", + "lset", + "ltrim", + "mget", + "monitor", + "move", + "mset", + "msetnx", + "multi", + "object", + "persist", + "ping", + "psubscribe", + "publish", + "punsubscribe", + "quit", + "randomkey", + "rename", + "renamenx", + "rpop", + "rpoplpush", + "rpush", + "rpushx", + "sadd", + "save", + "scard", + "sdiff", + "sdiffstore", + "select", + "set", + "setbit", + "setex", + "setnx", + "setrange", + "shutdown", + "sinter", + "sinterstore", + "sismember", + "slaveof", + "smembers", + "smove", + "sort", + "spop", + "srandmember", + "srem", + "strlen", + "subscribe", + "sunion", + "sunionstore", + "sync", + "ttl", + "type", + "unsubscribe", + "unwatch", + "watch", + "zadd", + "zcard", + "zcount", + "zincrby", + "zinterstore", + "zrange", + "zrangebyscore", + "zrank", + "zrem", + "zremrangebyrank", + "zremrangebyscore", + "zrevrange", + "zrevrangebyscore", + "zrevrank", + "zscore", + "zunionstore" +]; diff --git a/node_modules/socket.io/node_modules/redis/lib/parser/hiredis.js b/node_modules/socket.io/node_modules/redis/lib/parser/hiredis.js new file mode 100644 index 0000000..9dba8c9 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/lib/parser/hiredis.js @@ -0,0 +1,41 @@ +/*global Buffer require exports console setTimeout */ + +var events = require("events"), + util = require("../util").util, + hiredis = require("hiredis"); + +exports.debug_mode = false; +exports.name = "hiredis"; + +function HiredisReplyParser(options) { + this.name = exports.name; + this.options = options || {}; + this.reset(); + events.EventEmitter.call(this); +} + +util.inherits(HiredisReplyParser, events.EventEmitter); + +exports.Parser = HiredisReplyParser; + +HiredisReplyParser.prototype.reset = function () { + this.reader = new hiredis.Reader({ + return_buffers: this.options.return_buffers || false + }); +}; + +HiredisReplyParser.prototype.execute = function (data) { + var reply; + this.reader.feed(data); + try { + while ((reply = this.reader.get()) !== undefined) { + if (reply && reply.constructor === Error) { + this.emit("reply error", reply); + } else { + this.emit("reply", reply); + } + } + } catch (err) { + this.emit("error", err); + } +}; diff --git a/node_modules/socket.io/node_modules/redis/lib/parser/javascript.js b/node_modules/socket.io/node_modules/redis/lib/parser/javascript.js new file mode 100644 index 0000000..6f250c9 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/lib/parser/javascript.js @@ -0,0 +1,316 @@ +/*global Buffer require exports console setTimeout */ + +// TODO - incorporate these V8 pro tips: +// pre-allocate Arrays if length is known in advance +// do not use delete +// use numbers for parser state + +var events = require("events"), + util = require("../util").util; + +exports.debug_mode = false; +exports.name = "javascript"; + +function RedisReplyParser(options) { + this.name = exports.name; + this.options = options || {}; + this.reset(); + events.EventEmitter.call(this); +} + +util.inherits(RedisReplyParser, events.EventEmitter); + +exports.Parser = RedisReplyParser; + +// Buffer.toString() is quite slow for small strings +function small_toString(buf, len) { + var tmp = "", i; + + for (i = 0; i < len; i += 1) { + tmp += String.fromCharCode(buf[i]); + } + + return tmp; +} + +// Reset parser to it's original state. +RedisReplyParser.prototype.reset = function () { + this.return_buffer = new Buffer(16384); // for holding replies, might grow + this.return_string = ""; + this.tmp_string = ""; // for holding size fields + + this.multi_bulk_length = 0; + this.multi_bulk_replies = null; + this.multi_bulk_pos = 0; + this.multi_bulk_nested_length = 0; + this.multi_bulk_nested_replies = null; + + this.states = { + TYPE: 1, + SINGLE_LINE: 2, + MULTI_BULK_COUNT: 3, + INTEGER_LINE: 4, + BULK_LENGTH: 5, + ERROR_LINE: 6, + BULK_DATA: 7, + UNKNOWN_TYPE: 8, + FINAL_CR: 9, + FINAL_LF: 10, + MULTI_BULK_COUNT_LF: 11 + }; + + this.state = this.states.TYPE; +}; + +RedisReplyParser.prototype.parser_error = function (message) { + this.emit("error", message); + this.reset(); +}; + +RedisReplyParser.prototype.execute = function (incoming_buf) { + var pos = 0, bd_tmp, bd_str, i, il, states = this.states; + //, state_times = {}, start_execute = new Date(), start_switch, end_switch, old_state; + //start_switch = new Date(); + + while (pos < incoming_buf.length) { + // old_state = this.state; + // console.log("execute: " + this.state + ", " + pos + "/" + incoming_buf.length + ", " + String.fromCharCode(incoming_buf[pos])); + + switch (this.state) { + case states.TYPE: + this.type = incoming_buf[pos]; + pos += 1; + + switch (this.type) { + case 43: // + + this.state = states.SINGLE_LINE; + this.return_buffer.end = 0; + this.return_string = ""; + break; + case 42: // * + this.state = states.MULTI_BULK_COUNT; + this.tmp_string = ""; + break; + case 58: // : + this.state = states.INTEGER_LINE; + this.return_buffer.end = 0; + this.return_string = ""; + break; + case 36: // $ + this.state = states.BULK_LENGTH; + this.tmp_string = ""; + break; + case 45: // - + this.state = states.ERROR_LINE; + this.return_buffer.end = 0; + this.return_string = ""; + break; + default: + this.state = states.UNKNOWN_TYPE; + } + break; + case states.INTEGER_LINE: + if (incoming_buf[pos] === 13) { + this.send_reply(+small_toString(this.return_buffer, this.return_buffer.end)); + this.state = states.FINAL_LF; + } else { + this.return_buffer[this.return_buffer.end] = incoming_buf[pos]; + this.return_buffer.end += 1; + } + pos += 1; + break; + case states.ERROR_LINE: + if (incoming_buf[pos] === 13) { + this.send_error(this.return_buffer.toString("ascii", 0, this.return_buffer.end)); + this.state = states.FINAL_LF; + } else { + this.return_buffer[this.return_buffer.end] = incoming_buf[pos]; + this.return_buffer.end += 1; + } + pos += 1; + break; + case states.SINGLE_LINE: + if (incoming_buf[pos] === 13) { + this.send_reply(this.return_string); + this.state = states.FINAL_LF; + } else { + this.return_string += String.fromCharCode(incoming_buf[pos]); + } + pos += 1; + break; + case states.MULTI_BULK_COUNT: + if (incoming_buf[pos] === 13) { // \r + this.state = states.MULTI_BULK_COUNT_LF; + } else { + this.tmp_string += String.fromCharCode(incoming_buf[pos]); + } + pos += 1; + break; + case states.MULTI_BULK_COUNT_LF: + if (incoming_buf[pos] === 10) { // \n + if (this.multi_bulk_length) { // nested multi-bulk + this.multi_bulk_nested_length = this.multi_bulk_length; + this.multi_bulk_nested_replies = this.multi_bulk_replies; + this.multi_bulk_nested_pos = this.multi_bulk_pos; + } + this.multi_bulk_length = +this.tmp_string; + this.multi_bulk_pos = 0; + this.state = states.TYPE; + if (this.multi_bulk_length < 0) { + this.send_reply(null); + this.multi_bulk_length = 0; + } else if (this.multi_bulk_length === 0) { + this.multi_bulk_pos = 0; + this.multi_bulk_replies = null; + this.send_reply([]); + } else { + this.multi_bulk_replies = new Array(this.multi_bulk_length); + } + } else { + this.parser_error(new Error("didn't see LF after NL reading multi bulk count")); + return; + } + pos += 1; + break; + case states.BULK_LENGTH: + if (incoming_buf[pos] === 13) { // \r + this.state = states.BULK_LF; + } else { + this.tmp_string += String.fromCharCode(incoming_buf[pos]); + } + pos += 1; + break; + case states.BULK_LF: + if (incoming_buf[pos] === 10) { // \n + this.bulk_length = +this.tmp_string; + if (this.bulk_length === -1) { + this.send_reply(null); + this.state = states.TYPE; + } else if (this.bulk_length === 0) { + this.send_reply(new Buffer("")); + this.state = states.FINAL_CR; + } else { + this.state = states.BULK_DATA; + if (this.bulk_length > this.return_buffer.length) { + if (exports.debug_mode) { + console.log("Growing return_buffer from " + this.return_buffer.length + " to " + this.bulk_length); + } + this.return_buffer = new Buffer(this.bulk_length); + } + this.return_buffer.end = 0; + } + } else { + this.parser_error(new Error("didn't see LF after NL while reading bulk length")); + return; + } + pos += 1; + break; + case states.BULK_DATA: + this.return_buffer[this.return_buffer.end] = incoming_buf[pos]; + this.return_buffer.end += 1; + pos += 1; + if (this.return_buffer.end === this.bulk_length) { + bd_tmp = new Buffer(this.bulk_length); + // When the response is small, Buffer.copy() is a lot slower. + if (this.bulk_length > 10) { + this.return_buffer.copy(bd_tmp, 0, 0, this.bulk_length); + } else { + for (i = 0, il = this.bulk_length; i < il; i += 1) { + bd_tmp[i] = this.return_buffer[i]; + } + } + this.send_reply(bd_tmp); + this.state = states.FINAL_CR; + } + break; + case states.FINAL_CR: + if (incoming_buf[pos] === 13) { // \r + this.state = states.FINAL_LF; + pos += 1; + } else { + this.parser_error(new Error("saw " + incoming_buf[pos] + " when expecting final CR")); + return; + } + break; + case states.FINAL_LF: + if (incoming_buf[pos] === 10) { // \n + this.state = states.TYPE; + pos += 1; + } else { + this.parser_error(new Error("saw " + incoming_buf[pos] + " when expecting final LF")); + return; + } + break; + default: + this.parser_error(new Error("invalid state " + this.state)); + } + // end_switch = new Date(); + // if (state_times[old_state] === undefined) { + // state_times[old_state] = 0; + // } + // state_times[old_state] += (end_switch - start_switch); + // start_switch = end_switch; + } + // console.log("execute ran for " + (Date.now() - start_execute) + " ms, on " + incoming_buf.length + " Bytes. "); + // Object.keys(state_times).forEach(function (state) { + // console.log(" " + state + ": " + state_times[state]); + // }); +}; + +RedisReplyParser.prototype.send_error = function (reply) { + if (this.multi_bulk_length > 0 || this.multi_bulk_nested_length > 0) { + // TODO - can this happen? Seems like maybe not. + this.add_multi_bulk_reply(reply); + } else { + this.emit("reply error", reply); + } +}; + +RedisReplyParser.prototype.send_reply = function (reply) { + if (this.multi_bulk_length > 0 || this.multi_bulk_nested_length > 0) { + if (!this.options.return_buffers && Buffer.isBuffer(reply)) { + this.add_multi_bulk_reply(reply.toString("utf8")); + } else { + this.add_multi_bulk_reply(reply); + } + } else { + if (!this.options.return_buffers && Buffer.isBuffer(reply)) { + this.emit("reply", reply.toString("utf8")); + } else { + this.emit("reply", reply); + } + } +}; + +RedisReplyParser.prototype.add_multi_bulk_reply = function (reply) { + if (this.multi_bulk_replies) { + this.multi_bulk_replies[this.multi_bulk_pos] = reply; + this.multi_bulk_pos += 1; + if (this.multi_bulk_pos < this.multi_bulk_length) { + return; + } + } else { + this.multi_bulk_replies = reply; + } + + if (this.multi_bulk_nested_length > 0) { + this.multi_bulk_nested_replies[this.multi_bulk_nested_pos] = this.multi_bulk_replies; + this.multi_bulk_nested_pos += 1; + + this.multi_bulk_length = 0; + this.multi_bulk_replies = null; + this.multi_bulk_pos = 0; + + if (this.multi_bulk_nested_length === this.multi_bulk_nested_pos) { + this.emit("reply", this.multi_bulk_nested_replies); + this.multi_bulk_nested_length = 0; + this.multi_bulk_nested_pos = 0; + this.multi_bulk_nested_replies = null; + } + } else { + this.emit("reply", this.multi_bulk_replies); + this.multi_bulk_length = 0; + this.multi_bulk_replies = null; + this.multi_bulk_pos = 0; + } +}; diff --git a/node_modules/socket.io/node_modules/redis/lib/queue.js b/node_modules/socket.io/node_modules/redis/lib/queue.js new file mode 100644 index 0000000..5cc3c42 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/lib/queue.js @@ -0,0 +1,58 @@ +var to_array = require("./to_array"); + +// Queue class adapted from Tim Caswell's pattern library +// http://github.com/creationix/pattern/blob/master/lib/pattern/queue.js + +function Queue() { + this.tail = []; + this.head = to_array(arguments); + this.offset = 0; +} + +Queue.prototype.shift = function () { + if (this.offset === this.head.length) { + var tmp = this.head; + tmp.length = 0; + this.head = this.tail; + this.tail = tmp; + this.offset = 0; + if (this.head.length === 0) { + return; + } + } + return this.head[this.offset++]; // sorry, JSLint +}; + +Queue.prototype.push = function (item) { + return this.tail.push(item); +}; + +Queue.prototype.forEach = function (fn, thisv) { + var array = this.head.slice(this.offset), i, il; + + array.push.apply(array, this.tail); + + if (thisv) { + for (i = 0, il = array.length; i < il; i += 1) { + fn.call(thisv, array[i], i, array); + } + } else { + for (i = 0, il = array.length; i < il; i += 1) { + fn(array[i], i, array); + } + } + + return array; +}; + +Queue.prototype.getLength = function () { + return this.head.length - this.offset + this.tail.length; +}; + +Object.defineProperty(Queue.prototype, 'length', { + get: function () { + return this.getLength(); + } +}); + +exports.Queue = Queue; diff --git a/node_modules/socket.io/node_modules/redis/lib/to_array.js b/node_modules/socket.io/node_modules/redis/lib/to_array.js new file mode 100644 index 0000000..88a57e1 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/lib/to_array.js @@ -0,0 +1,12 @@ +function to_array(args) { + var len = args.length, + arr = new Array(len), i; + + for (i = 0; i < len; i += 1) { + arr[i] = args[i]; + } + + return arr; +} + +module.exports = to_array; diff --git a/node_modules/socket.io/node_modules/redis/lib/util.js b/node_modules/socket.io/node_modules/redis/lib/util.js new file mode 100644 index 0000000..3dc41a5 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/lib/util.js @@ -0,0 +1,6 @@ +if (process.versions.node.match(/^0.3/)) { + exports.util = require("util"); +} else { + // This module is called "sys" in 0.2.x + exports.util = require("sys"); +} diff --git a/node_modules/socket.io/node_modules/redis/multi_bench.js b/node_modules/socket.io/node_modules/redis/multi_bench.js new file mode 100644 index 0000000..b78c126 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/multi_bench.js @@ -0,0 +1,135 @@ +var redis = require("./index"), + num_clients = parseInt(process.argv[2], 10) || 50, + active_clients = 0, + clients = new Array(num_clients), + num_requests = 20000, + issued_requests = 0, + latency = new Array(num_requests), + tests = [], + test_start, parser_logged = false, + client_options = { + return_buffers: false + }; + +redis.debug_mode = false; + +tests.push({ + descr: "PING", + command: ["ping"] +}); + +tests.push({ + descr: "SET", + command: ["set", "foo_rand000000000000", "bar"] +}); + +tests.push({ + descr: "GET", + command: ["get", "foo_rand000000000000"] +}); + +tests.push({ + descr: "INCR", + command: ["incr", "counter_rand000000000000"] +}); + +tests.push({ + descr: "LPUSH", + command: ["lpush", "mylist", new Array(8).join("-")] +}); + +tests.push({ + descr: "LRANGE (10 elements)", + command: ["lrange", "mylist", "0", "9"] +}); + +tests.push({ + descr: "LRANGE (100 elements)", + command: ["lrange", "mylist", "0", "99"] +}); + +function create_clients(callback) { + if (active_clients === num_clients) { + // common case is all clients are already created + console.log("create_clients: all clients already created " + num_clients); + callback(); + } else { + var client, connected = active_clients; + + while (active_clients < num_clients) { + client = clients[active_clients++] = redis.createClient(6379, "127.0.0.1", client_options); + if (! parser_logged) { + console.log("Using reply parser " + client.reply_parser.name); + parser_logged = true; + } + client.on("connect", function () { + // Fire callback when all clients are connected + connected += 1; + if (connected === num_clients) { + callback(); + } + }); + // TODO - need to check for client disconnect + client.on("error", function (msg) { + console.log("Connect problem:" + msg.stack); + }); + } + } +} + +function issue_request(client, test, cmd, args) { + var i = issued_requests++; + latency[i] = Date.now(); + + client[cmd](args, function() { + latency[i] = Date.now() - latency[i]; + if (issued_requests < num_requests) { + issue_request(client, test, cmd, args); + } else { + client.end(); + if (--active_clients == 0) + test_complete(test); + } + }); +} + +function test_run(test) { + create_clients(function() { + var i = num_clients, + cmd = test.command[0], + args = test.command.slice(1); + + test_start = Date.now(); + issued_requests = 0; + while(i-- && issued_requests < num_requests) { + issue_request(clients[i], test, cmd, args); + } + }); +} + +function test_complete(test) { + var min, max, sum, avg; + var total_time = Date.now() - test_start; + var op_rate = (issued_requests / (total_time / 1000.0)).toFixed(2); + var i; + + latency.sort(); + min = latency[0]; + max = latency[issued_requests-1]; + for (sum = 0, i = 0; i < issued_requests; i++) + sum += latency[i]; + avg = (sum / issued_requests).toFixed(3); + + console.log(test.descr + ": " + issued_requests + " ops " + op_rate + " ops/sec " + min + "/" + max + "/" + avg); + + next(); +} + +function next() { + var test = tests.shift(); + if (test) { + test_run(test); + } +} + +next(); diff --git a/node_modules/socket.io/node_modules/redis/package.json b/node_modules/socket.io/node_modules/redis/package.json new file mode 100644 index 0000000..6ad55ca --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/package.json @@ -0,0 +1,26 @@ +{ "name" : "redis", + "version" : "0.6.7", + "description" : "Redis client library", + "author": "Matt Ranney ", + "contributors": [ + "Rick Olson", + "Tim-Smart", + "TJ Holowaychuk", + "Orion Henry", + "Hank Sims", + "Aivo Paas", + "Paul Carey", + "Pieter Noordhuis", + "Andy Ray", + "Vladimir Dronnikov", + "Dave Hoover" + ], + "main": "./index.js", + "scripts": { + "test": "node ./test.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/mranney/node_redis.git" + } +} diff --git a/node_modules/socket.io/node_modules/redis/simple_test.js b/node_modules/socket.io/node_modules/redis/simple_test.js new file mode 100644 index 0000000..f32ab9d --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/simple_test.js @@ -0,0 +1,3 @@ +var client = require("./index").createClient(); + +client.hmset("test hash", "key 1", "val 1", "key 2", "val 2"); diff --git a/node_modules/socket.io/node_modules/redis/test.js b/node_modules/socket.io/node_modules/redis/test.js new file mode 100644 index 0000000..7489e0c --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/test.js @@ -0,0 +1,1248 @@ +/*global require console setTimeout process Buffer */ +var redis = require("./index"), + client = redis.createClient(), + client2 = redis.createClient(), + client3 = redis.createClient(), + client4 = redis.createClient(9006, "filefish.redistogo.com"), + assert = require("assert"), + util = require("./lib/util").util, + test_db_num = 15, // this DB will be flushed and used for testing + tests = {}, + connected = false, + ended = false, + next, cur_start, run_next_test, all_tests, all_start, test_count; + +// Set this to truthy to see the wire protocol and other debugging info +redis.debug_mode = process.argv[2]; + +function buffers_to_strings(arr) { + return arr.map(function (val) { + return val.toString(); + }); +} + +function require_number(expected, label) { + return function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.strictEqual(expected, results, label + " " + expected + " !== " + results); + assert.strictEqual(typeof results, "number", label); + return true; + }; +} + +function require_number_any(label) { + return function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.strictEqual(typeof results, "number", label + " " + results + " is not a number"); + return true; + }; +} + +function require_number_pos(label) { + return function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.strictEqual(true, (results > 0), label + " " + results + " is not a positive number"); + return true; + }; +} + +function require_string(str, label) { + return function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.equal(str, results, label + " " + str + " does not match " + results); + return true; + }; +} + +function require_null(label) { + return function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.strictEqual(null, results, label + ": " + results + " is not null"); + return true; + }; +} + +function require_error(label) { + return function (err, results) { + assert.notEqual(err, null, label + " err is null, but an error is expected here."); + return true; + }; +} + +function is_empty_array(obj) { + return Array.isArray(obj) && obj.length === 0; +} + +function last(name, fn) { + return function (err, results) { + fn(err, results); + next(name); + }; +} + +next = function next(name) { + console.log(" \x1b[33m" + (Date.now() - cur_start) + "\x1b[0m ms"); + run_next_test(); +}; + +// Tests are run in the order they are defined. So FLUSHDB should be stay first. + +tests.FLUSHDB = function () { + var name = "FLUSHDB"; + client.select(test_db_num, require_string("OK", name)); + client2.select(test_db_num, require_string("OK", name)); + client3.select(test_db_num, require_string("OK", name)); + client.mset("flush keys 1", "flush val 1", "flush keys 2", "flush val 2", require_string("OK", name)); + client.FLUSHDB(require_string("OK", name)); + client.dbsize(last(name, require_number(0, name))); +}; + +tests.MULTI_1 = function () { + var name = "MULTI_1", multi1, multi2; + + // Provoke an error at queue time + multi1 = client.multi(); + multi1.mset("multifoo", "10", "multibar", "20", require_string("OK", name)); + multi1.set("foo2", require_error(name)); + multi1.incr("multifoo", require_number(11, name)); + multi1.incr("multibar", require_number(21, name)); + multi1.exec(); + + // Confirm that the previous command, while containing an error, still worked. + multi2 = client.multi(); + multi2.incr("multibar", require_number(22, name)); + multi2.incr("multifoo", require_number(12, name)); + multi2.exec(function (err, replies) { + assert.strictEqual(22, replies[0]); + assert.strictEqual(12, replies[1]); + next(name); + }); +}; + +tests.MULTI_2 = function () { + var name = "MULTI_2"; + + // test nested multi-bulk replies + client.multi([ + ["mget", "multifoo", "multibar", function (err, res) { + assert.strictEqual(2, res.length, name); + assert.strictEqual("12", res[0].toString(), name); + assert.strictEqual("22", res[1].toString(), name); + }], + ["set", "foo2", require_error(name)], + ["incr", "multifoo", require_number(13, name)], + ["incr", "multibar", require_number(23, name)] + ]).exec(function (err, replies) { + assert.strictEqual(2, replies[0].length, name); + assert.strictEqual("12", replies[0][0].toString(), name); + assert.strictEqual("22", replies[0][1].toString(), name); + + assert.strictEqual("13", replies[1].toString()); + assert.strictEqual("23", replies[2].toString()); + next(name); + }); +}; + +tests.MULTI_3 = function () { + var name = "MULTI_3"; + + client.sadd("some set", "mem 1"); + client.sadd("some set", "mem 2"); + client.sadd("some set", "mem 3"); + client.sadd("some set", "mem 4"); + + // make sure empty mb reply works + client.del("some missing set"); + client.smembers("some missing set", function (err, reply) { + // make sure empty mb reply works + assert.strictEqual(true, is_empty_array(reply), name); + }); + + // test nested multi-bulk replies with empty mb elements. + client.multi([ + ["smembers", "some set"], + ["del", "some set"], + ["smembers", "some set"] + ]) + .scard("some set") + .exec(function (err, replies) { + assert.strictEqual(true, is_empty_array(replies[2]), name); + next(name); + }); +}; + +tests.MULTI_4 = function () { + var name = "MULTI_4"; + + client.multi() + .mset('some', '10', 'keys', '20') + .incr('some') + .incr('keys') + .mget('some', 'keys') + .exec(function (err, replies) { + assert.strictEqual(null, err); + assert.equal('OK', replies[0]); + assert.equal(11, replies[1]); + assert.equal(21, replies[2]); + assert.equal(11, replies[3][0].toString()); + assert.equal(21, replies[3][1].toString()); + next(name); + }); +}; + +tests.MULTI_5 = function () { + var name = "MULTI_5"; + + // test nested multi-bulk replies with nulls. + client.multi([ + ["mget", ["multifoo", "some", "random value", "keys"]], + ["incr", "multifoo"] + ]) + .exec(function (err, replies) { + assert.strictEqual(replies.length, 2, name); + assert.strictEqual(replies[0].length, 4, name); + next(name); + }); +}; + +tests.MULTI_6 = function () { + var name = "MULTI_6"; + + client.multi() + .hmset("multihash", "a", "foo", "b", 1) + .hmset("multihash", { + extra: "fancy", + things: "here" + }) + .hgetall("multihash") + .exec(function (err, replies) { + assert.strictEqual(null, err); + assert.equal("OK", replies[0]); + assert.equal(Object.keys(replies[2]).length, 4); + assert.equal("foo", replies[2].a); + assert.equal("1", replies[2].b); + assert.equal("fancy", replies[2].extra); + assert.equal("here", replies[2].things); + next(name); + }); +}; + +tests.EVAL_1 = function () { + var name = "EVAL_1"; + + if (client.server_info.versions[0] >= 2 && client.server_info.versions[1] >= 9) { + // test {EVAL - Lua integer -> Redis protocol type conversion} + client.eval("return 100.5", 0, require_number(100, name)); + // test {EVAL - Lua string -> Redis protocol type conversion} + client.eval("return 'hello world'", 0, require_string("hello world", name)); + // test {EVAL - Lua true boolean -> Redis protocol type conversion} + client.eval("return true", 0, require_number(1, name)); + // test {EVAL - Lua false boolean -> Redis protocol type conversion} + client.eval("return false", 0, require_null(name)); + // test {EVAL - Lua status code reply -> Redis protocol type conversion} + client.eval("return {ok='fine'}", 0, require_string("fine", name)); + // test {EVAL - Lua error reply -> Redis protocol type conversion} + client.eval("return {err='this is an error'}", 0, require_error(name)); + // test {EVAL - Lua table -> Redis protocol type conversion} + client.eval("return {1,2,3,'ciao',{1,2}}", 0, function (err, res) { + assert.strictEqual(5, res.length, name); + assert.strictEqual(1, res[0], name); + assert.strictEqual(2, res[1], name); + assert.strictEqual(3, res[2], name); + assert.strictEqual("ciao", res[3], name); + assert.strictEqual(2, res[4].length, name); + assert.strictEqual(1, res[4][0], name); + assert.strictEqual(2, res[4][1], name); + }); + // test {EVAL - Are the KEYS and ARGS arrays populated correctly?} + client.eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", 2, "a", "b", "c", "d", function (err, res) { + assert.strictEqual(4, res.length, name); + assert.strictEqual("a", res[0], name); + assert.strictEqual("b", res[1], name); + assert.strictEqual("c", res[2], name); + assert.strictEqual("d", res[3], name); + }); + // test {EVAL - is Lua able to call Redis API?} + client.set("mykey", "myval"); + client.eval("return redis.call('get','mykey')", 0, require_string("myval", name)); + // test {EVALSHA - Can we call a SHA1 if already defined?} + client.evalsha("9bd632c7d33e571e9f24556ebed26c3479a87129", 0, require_string("myval", name)); + // test {EVALSHA - Do we get an error on non defined SHA1?} + client.evalsha("ffffffffffffffffffffffffffffffffffffffff", 0, require_error(name)); + // test {EVAL - Redis integer -> Lua type conversion} + client.set("x", 0); + client.eval("local foo = redis.call('incr','x')\n" + "return {type(foo),foo}", 0, function (err, res) { + assert.strictEqual(2, res.length, name); + assert.strictEqual("number", res[0], name); + assert.strictEqual(1, res[1], name); + }); + // test {EVAL - Redis bulk -> Lua type conversion} + client.eval("local foo = redis.call('get','mykey'); return {type(foo),foo}", 0, function (err, res) { + assert.strictEqual(2, res.length, name); + assert.strictEqual("string", res[0], name); + assert.strictEqual("myval", res[1], name); + }); + // test {EVAL - Redis multi bulk -> Lua type conversion} + client.del("mylist"); + client.rpush("mylist", "a"); + client.rpush("mylist", "b"); + client.rpush("mylist", "c"); + client.eval("local foo = redis.call('lrange','mylist',0,-1)\n" + "return {type(foo),foo[1],foo[2],foo[3],# foo}", 0, function (err, res) { + assert.strictEqual(5, res.length, name); + assert.strictEqual("table", res[0], name); + assert.strictEqual("a", res[1], name); + assert.strictEqual("b", res[2], name); + assert.strictEqual("c", res[3], name); + assert.strictEqual(3, res[4], name); + }); + // test {EVAL - Redis status reply -> Lua type conversion} + client.eval("local foo = redis.call('set','mykey','myval'); return {type(foo),foo['ok']}", 0, function (err, res) { + assert.strictEqual(2, res.length, name); + assert.strictEqual("table", res[0], name); + assert.strictEqual("OK", res[1], name); + }); + // test {EVAL - Redis error reply -> Lua type conversion} + client.set("mykey", "myval"); + client.eval("local foo = redis.call('incr','mykey'); return {type(foo),foo['err']}", 0, function (err, res) { + assert.strictEqual(2, res.length, name); + assert.strictEqual("table", res[0], name); + assert.strictEqual("ERR value is not an integer or out of range", res[1], name); + }); + // test {EVAL - Redis nil bulk reply -> Lua type conversion} + client.del("mykey"); + client.eval("local foo = redis.call('get','mykey'); return {type(foo),foo == false}", 0, function (err, res) { + assert.strictEqual(2, res.length, name); + assert.strictEqual("boolean", res[0], name); + assert.strictEqual(1, res[1], name); + }); + // test {EVAL - Script can't run more than configured time limit} { + client.config("set", "lua-time-limit", 1); + client.eval("local i = 0; while true do i=i+1 end", 0, last("name", require_error(name))); + } else { + console.log("Skipping " + name + " because server version isn't new enough."); + next(name); + } +}; + +tests.WATCH_MULTI = function () { + var name = 'WATCH_MULTI', multi; + + if (client.server_info.versions[0] >= 2 && client.server_info.versions[1] >= 1) { + client.watch(name); + client.incr(name); + multi = client.multi(); + multi.incr(name); + multi.exec(last(name, require_null(name))); + } else { + console.log("Skipping " + name + " because server version isn't new enough."); + next(name); + } +}; + +tests.reconnect = function () { + var name = "reconnect"; + + client.set("recon 1", "one"); + client.set("recon 2", "two", function (err, res) { + // Do not do this in normal programs. This is to simulate the server closing on us. + // For orderly shutdown in normal programs, do client.quit() + client.stream.destroy(); + }); + + client.on("reconnecting", function on_recon(params) { + client.on("connect", function on_connect() { + client.select(test_db_num, require_string("OK", name)); + client.get("recon 1", require_string("one", name)); + client.get("recon 1", require_string("one", name)); + client.get("recon 2", require_string("two", name)); + client.get("recon 2", require_string("two", name)); + client.removeListener("connect", on_connect); + client.removeListener("reconnecting", on_recon); + next(name); + }); + }); +}; + +tests.HSET = function () { + var key = "test hash", + field1 = new Buffer("0123456789"), + value1 = new Buffer("abcdefghij"), + field2 = new Buffer(0), + value2 = new Buffer(0), + name = "HSET"; + + client.HSET(key, field1, value1, require_number(1, name)); + client.HGET(key, field1, require_string(value1.toString(), name)); + + // Empty value + client.HSET(key, field1, value2, require_number(0, name)); + client.HGET([key, field1], require_string("", name)); + + // Empty key, empty value + client.HSET([key, field2, value1], require_number(1, name)); + client.HSET(key, field2, value2, last(name, require_number(0, name))); +}; + +tests.HMSET_BUFFER_AND_ARRAY = function () { + // Saving a buffer and an array to the same key should not error + var key = "test hash", + field1 = "buffer", + value1 = new Buffer("abcdefghij"), + field2 = "array", + value2 = ["array contents"], + name = "HSET"; + + client.HMSET(key, field1, value1, field2, value2, last(name, require_string("OK", name))); +}; + +// TODO - add test for HMSET. It is special. Test for all forms as well as optional callbacks + +tests.HMGET = function () { + var key1 = "test hash 1", key2 = "test hash 2", name = "HMGET"; + + // redis-like hmset syntax + client.HMSET(key1, "0123456789", "abcdefghij", "some manner of key", "a type of value", require_string("OK", name)); + + // fancy hmset syntax + client.HMSET(key2, { + "0123456789": "abcdefghij", + "some manner of key": "a type of value" + }, require_string("OK", name)); + + client.HMGET(key1, "0123456789", "some manner of key", function (err, reply) { + assert.strictEqual("abcdefghij", reply[0].toString(), name); + assert.strictEqual("a type of value", reply[1].toString(), name); + }); + + client.HMGET(key2, "0123456789", "some manner of key", function (err, reply) { + assert.strictEqual("abcdefghij", reply[0].toString(), name); + assert.strictEqual("a type of value", reply[1].toString(), name); + }); + + client.HMGET(key1, ["0123456789"], function (err, reply) { + assert.strictEqual("abcdefghij", reply[0], name); + }); + + client.HMGET(key1, ["0123456789", "some manner of key"], function (err, reply) { + assert.strictEqual("abcdefghij", reply[0], name); + assert.strictEqual("a type of value", reply[1], name); + }); + + client.HMGET(key1, "missing thing", "another missing thing", function (err, reply) { + assert.strictEqual(null, reply[0], name); + assert.strictEqual(null, reply[1], name); + next(name); + }); +}; + +tests.HINCRBY = function () { + var name = "HINCRBY"; + client.hset("hash incr", "value", 10, require_number(1, name)); + client.HINCRBY("hash incr", "value", 1, require_number(11, name)); + client.HINCRBY("hash incr", "value 2", 1, last(name, require_number(1, name))); +}; + +tests.SUBSCRIBE = function () { + var client1 = client, msg_count = 0, name = "SUBSCRIBE"; + + client1.on("subscribe", function (channel, count) { + if (channel === "chan1") { + client2.publish("chan1", "message 1", require_number(1, name)); + client2.publish("chan2", "message 2", require_number(1, name)); + client2.publish("chan1", "message 3", require_number(1, name)); + } + }); + + client1.on("unsubscribe", function (channel, count) { + if (count === 0) { + // make sure this connection can go into and out of pub/sub mode + client1.incr("did a thing", last(name, require_number(2, name))); + } + }); + + client1.on("message", function (channel, message) { + msg_count += 1; + assert.strictEqual("message " + msg_count, message.toString()); + if (msg_count === 3) { + client1.unsubscribe("chan1", "chan2"); + } + }); + + client1.set("did a thing", 1, require_string("OK", name)); + client1.subscribe("chan1", "chan2"); +}; + +tests.SUBSCRIBE_QUIT = function () { + var name = "SUBSCRIBE_QUIT"; + client3.on("end", function () { + next(name); + }); + client3.on("subscribe", function (channel, count) { + client3.quit(); + }); + client3.subscribe("chan3"); +}; + +tests.EXISTS = function () { + var name = "EXISTS"; + client.del("foo", "foo2", require_number_any(name)); + client.set("foo", "bar", require_string("OK", name)); + client.EXISTS("foo", require_number(1, name)); + client.EXISTS("foo2", last(name, require_number(0, name))); +}; + +tests.DEL = function () { + var name = "DEL"; + client.DEL("delkey", require_number_any(name)); + client.set("delkey", "delvalue", require_string("OK", name)); + client.DEL("delkey", require_number(1, name)); + client.exists("delkey", require_number(0, name)); + client.DEL("delkey", require_number(0, name)); + client.mset("delkey", "delvalue", "delkey2", "delvalue2", require_string("OK", name)); + client.DEL("delkey", "delkey2", last(name, require_number(2, name))); +}; + +tests.TYPE = function () { + var name = "TYPE"; + client.set(["string key", "should be a string"], require_string("OK", name)); + client.rpush(["list key", "should be a list"], require_number_pos(name)); + client.sadd(["set key", "should be a set"], require_number_any(name)); + client.zadd(["zset key", "10.0", "should be a zset"], require_number_any(name)); + client.hset(["hash key", "hashtest", "should be a hash"], require_number_any(0, name)); + + client.TYPE(["string key"], require_string("string", name)); + client.TYPE(["list key"], require_string("list", name)); + client.TYPE(["set key"], require_string("set", name)); + client.TYPE(["zset key"], require_string("zset", name)); + client.TYPE("not here yet", require_string("none", name)); + client.TYPE(["hash key"], last(name, require_string("hash", name))); +}; + +tests.KEYS = function () { + var name = "KEYS"; + client.mset(["test keys 1", "test val 1", "test keys 2", "test val 2"], require_string("OK", name)); + client.KEYS(["test keys*"], function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.strictEqual(2, results.length, name); + assert.strictEqual("test keys 1", results[0].toString(), name); + assert.strictEqual("test keys 2", results[1].toString(), name); + next(name); + }); +}; + +tests.MULTIBULK_ZERO_LENGTH = function () { + var name = "MULTIBULK_ZERO_LENGTH"; + client.KEYS(['users:*'], function (err, results) { + assert.strictEqual(null, err, 'error on empty multibulk reply'); + assert.strictEqual(true, is_empty_array(results), "not an empty array"); + next(name); + }); +}; + +tests.RANDOMKEY = function () { + var name = "RANDOMKEY"; + client.mset(["test keys 1", "test val 1", "test keys 2", "test val 2"], require_string("OK", name)); + client.RANDOMKEY([], function (err, results) { + assert.strictEqual(null, err, name + " result sent back unexpected error: " + err); + assert.strictEqual(true, /\w+/.test(results), name); + next(name); + }); +}; + +tests.RENAME = function () { + var name = "RENAME"; + client.set(['foo', 'bar'], require_string("OK", name)); + client.RENAME(["foo", "new foo"], require_string("OK", name)); + client.exists(["foo"], require_number(0, name)); + client.exists(["new foo"], last(name, require_number(1, name))); +}; + +tests.RENAMENX = function () { + var name = "RENAMENX"; + client.set(['foo', 'bar'], require_string("OK", name)); + client.set(['foo2', 'bar2'], require_string("OK", name)); + client.RENAMENX(["foo", "foo2"], require_number(0, name)); + client.exists(["foo"], require_number(1, name)); + client.exists(["foo2"], require_number(1, name)); + client.del(["foo2"], require_number(1, name)); + client.RENAMENX(["foo", "foo2"], require_number(1, name)); + client.exists(["foo"], require_number(0, name)); + client.exists(["foo2"], last(name, require_number(1, name))); +}; + +tests.DBSIZE = function () { + var name = "DBSIZE"; + client.set(['foo', 'bar'], require_string("OK", name)); + client.DBSIZE([], last(name, require_number_pos("DBSIZE"))); +}; + +tests.GET = function () { + var name = "GET"; + client.set(["get key", "get val"], require_string("OK", name)); + client.GET(["get key"], last(name, require_string("get val", name))); +}; + +tests.SET = function () { + var name = "SET"; + client.SET(["set key", "set val"], require_string("OK", name)); + client.get(["set key"], last(name, require_string("set val", name))); +}; + +tests.GETSET = function () { + var name = "GETSET"; + client.set(["getset key", "getset val"], require_string("OK", name)); + client.GETSET(["getset key", "new getset val"], require_string("getset val", name)); + client.get(["getset key"], last(name, require_string("new getset val", name))); +}; + +tests.MGET = function () { + var name = "MGET"; + client.mset(["mget keys 1", "mget val 1", "mget keys 2", "mget val 2", "mget keys 3", "mget val 3"], require_string("OK", name)); + client.MGET("mget keys 1", "mget keys 2", "mget keys 3", function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.strictEqual(3, results.length, name); + assert.strictEqual("mget val 1", results[0].toString(), name); + assert.strictEqual("mget val 2", results[1].toString(), name); + assert.strictEqual("mget val 3", results[2].toString(), name); + }); + client.MGET(["mget keys 1", "mget keys 2", "mget keys 3"], function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.strictEqual(3, results.length, name); + assert.strictEqual("mget val 1", results[0].toString(), name); + assert.strictEqual("mget val 2", results[1].toString(), name); + assert.strictEqual("mget val 3", results[2].toString(), name); + }); + client.MGET(["mget keys 1", "some random shit", "mget keys 2", "mget keys 3"], function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.strictEqual(4, results.length, name); + assert.strictEqual("mget val 1", results[0].toString(), name); + assert.strictEqual(null, results[1], name); + assert.strictEqual("mget val 2", results[2].toString(), name); + assert.strictEqual("mget val 3", results[3].toString(), name); + next(name); + }); +}; + +tests.SETNX = function () { + var name = "SETNX"; + client.set(["setnx key", "setnx value"], require_string("OK", name)); + client.SETNX(["setnx key", "new setnx value"], require_number(0, name)); + client.del(["setnx key"], require_number(1, name)); + client.exists(["setnx key"], require_number(0, name)); + client.SETNX(["setnx key", "new setnx value"], require_number(1, name)); + client.exists(["setnx key"], last(name, require_number(1, name))); +}; + +tests.SETEX = function () { + var name = "SETEX"; + client.SETEX(["setex key", "100", "setex val"], require_string("OK", name)); + client.exists(["setex key"], require_number(1, name)); + client.ttl(["setex key"], last(name, require_number_pos(name))); +}; + +tests.MSETNX = function () { + var name = "MSETNX"; + client.mset(["mset1", "val1", "mset2", "val2", "mset3", "val3"], require_string("OK", name)); + client.MSETNX(["mset3", "val3", "mset4", "val4"], require_number(0, name)); + client.del(["mset3"], require_number(1, name)); + client.MSETNX(["mset3", "val3", "mset4", "val4"], require_number(1, name)); + client.exists(["mset3"], require_number(1, name)); + client.exists(["mset4"], last(name, require_number(1, name))); +}; + +tests.HGETALL = function () { + var name = "HGETALL"; + client.hmset(["hosts", "mjr", "1", "another", "23", "home", "1234"], require_string("OK", name)); + client.HGETALL(["hosts"], function (err, obj) { + assert.strictEqual(null, err, name + " result sent back unexpected error: " + err); + assert.strictEqual(3, Object.keys(obj).length, name); + assert.strictEqual("1", obj.mjr.toString(), name); + assert.strictEqual("23", obj.another.toString(), name); + assert.strictEqual("1234", obj.home.toString(), name); + next(name); + }); +}; + +tests.HGETALL_NULL = function () { + var name = "HGETALL_NULL"; + + client.hgetall('missing', function (err, obj) { + assert.strictEqual(null, err); + assert.deepEqual([], obj); + next(name); + }); +}; + +tests.UTF8 = function () { + var name = "UTF8", + utf8_sample = "ಠ_ಠ"; + + client.set(["utf8test", utf8_sample], require_string("OK", name)); + client.get(["utf8test"], function (err, obj) { + assert.strictEqual(null, err); + assert.strictEqual(utf8_sample, obj); + next(name); + }); +}; + +// Set tests were adapted from Brian Hammond's redis-node-client.js, which has a comprehensive test suite + +tests.SADD = function () { + var name = "SADD"; + + client.del('set0'); + client.sadd('set0', 'member0', require_number(1, name)); + client.sadd('set0', 'member0', last(name, require_number(0, name))); +}; + +tests.SADD2 = function () { + var name = "SADD2"; + + client.del("set0"); + client.sadd("set0", ["member0", "member1", "member2"], require_number(3, name)); + client.smembers("set0", function (err, res) { + assert.strictEqual(res.length, 3); + assert.strictEqual(res[0], "member0"); + assert.strictEqual(res[1], "member1"); + assert.strictEqual(res[2], "member2"); + next(name); + }); +}; + +tests.SISMEMBER = function () { + var name = "SISMEMBER"; + + client.del('set0'); + client.sadd('set0', 'member0', require_number(1, name)); + client.sismember('set0', 'member0', require_number(1, name)); + client.sismember('set0', 'member1', last(name, require_number(0, name))); +}; + +tests.SCARD = function () { + var name = "SCARD"; + + client.del('set0'); + client.sadd('set0', 'member0', require_number(1, name)); + client.scard('set0', require_number(1, name)); + client.sadd('set0', 'member1', require_number(1, name)); + client.scard('set0', last(name, require_number(2, name))); +}; + +tests.SREM = function () { + var name = "SREM"; + + client.del('set0'); + client.sadd('set0', 'member0', require_number(1, name)); + client.srem('set0', 'foobar', require_number(0, name)); + client.srem('set0', 'member0', require_number(1, name)); + client.scard('set0', last(name, require_number(0, name))); +}; + +tests.SPOP = function () { + var name = "SPOP"; + + client.del('zzz'); + client.sadd('zzz', 'member0', require_number(1, name)); + client.scard('zzz', require_number(1, name)); + + client.spop('zzz', function (err, value) { + if (err) { + assert.fail(err); + } + assert.equal(value, 'member0', name); + }); + + client.scard('zzz', last(name, require_number(0, name))); +}; + +tests.SDIFF = function () { + var name = "SDIFF"; + + client.del('foo'); + client.sadd('foo', 'x', require_number(1, name)); + client.sadd('foo', 'a', require_number(1, name)); + client.sadd('foo', 'b', require_number(1, name)); + client.sadd('foo', 'c', require_number(1, name)); + + client.sadd('bar', 'c', require_number(1, name)); + + client.sadd('baz', 'a', require_number(1, name)); + client.sadd('baz', 'd', require_number(1, name)); + + client.sdiff('foo', 'bar', 'baz', function (err, values) { + if (err) { + assert.fail(err, name); + } + values.sort(); + assert.equal(values.length, 2, name); + assert.equal(values[0], 'b', name); + assert.equal(values[1], 'x', name); + next(name); + }); +}; + +tests.SDIFFSTORE = function () { + var name = "SDIFFSTORE"; + + client.del('foo'); + client.del('bar'); + client.del('baz'); + client.del('quux'); + + client.sadd('foo', 'x', require_number(1, name)); + client.sadd('foo', 'a', require_number(1, name)); + client.sadd('foo', 'b', require_number(1, name)); + client.sadd('foo', 'c', require_number(1, name)); + + client.sadd('bar', 'c', require_number(1, name)); + + client.sadd('baz', 'a', require_number(1, name)); + client.sadd('baz', 'd', require_number(1, name)); + + // NB: SDIFFSTORE returns the number of elements in the dstkey + + client.sdiffstore('quux', 'foo', 'bar', 'baz', require_number(2, name)); + + client.smembers('quux', function (err, values) { + if (err) { + assert.fail(err, name); + } + var members = buffers_to_strings(values).sort(); + + assert.deepEqual(members, [ 'b', 'x' ], name); + next(name); + }); +}; + +tests.SMEMBERS = function () { + var name = "SMEMBERS"; + + client.del('foo'); + client.sadd('foo', 'x', require_number(1, name)); + + client.smembers('foo', function (err, members) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(members), [ 'x' ], name); + }); + + client.sadd('foo', 'y', require_number(1, name)); + + client.smembers('foo', function (err, values) { + if (err) { + assert.fail(err, name); + } + assert.equal(values.length, 2, name); + var members = buffers_to_strings(values).sort(); + + assert.deepEqual(members, [ 'x', 'y' ], name); + next(name); + }); +}; + +tests.SMOVE = function () { + var name = "SMOVE"; + + client.del('foo'); + client.del('bar'); + + client.sadd('foo', 'x', require_number(1, name)); + client.smove('foo', 'bar', 'x', require_number(1, name)); + client.sismember('foo', 'x', require_number(0, name)); + client.sismember('bar', 'x', require_number(1, name)); + client.smove('foo', 'bar', 'x', last(name, require_number(0, name))); +}; + +tests.SINTER = function () { + var name = "SINTER"; + + client.del('sa'); + client.del('sb'); + client.del('sc'); + + client.sadd('sa', 'a', require_number(1, name)); + client.sadd('sa', 'b', require_number(1, name)); + client.sadd('sa', 'c', require_number(1, name)); + + client.sadd('sb', 'b', require_number(1, name)); + client.sadd('sb', 'c', require_number(1, name)); + client.sadd('sb', 'd', require_number(1, name)); + + client.sadd('sc', 'c', require_number(1, name)); + client.sadd('sc', 'd', require_number(1, name)); + client.sadd('sc', 'e', require_number(1, name)); + + client.sinter('sa', 'sb', function (err, intersection) { + if (err) { + assert.fail(err, name); + } + assert.equal(intersection.length, 2, name); + assert.deepEqual(buffers_to_strings(intersection).sort(), [ 'b', 'c' ], name); + }); + + client.sinter('sb', 'sc', function (err, intersection) { + if (err) { + assert.fail(err, name); + } + assert.equal(intersection.length, 2, name); + assert.deepEqual(buffers_to_strings(intersection).sort(), [ 'c', 'd' ], name); + }); + + client.sinter('sa', 'sc', function (err, intersection) { + if (err) { + assert.fail(err, name); + } + assert.equal(intersection.length, 1, name); + assert.equal(intersection[0], 'c', name); + }); + + // 3-way + + client.sinter('sa', 'sb', 'sc', function (err, intersection) { + if (err) { + assert.fail(err, name); + } + assert.equal(intersection.length, 1, name); + assert.equal(intersection[0], 'c', name); + next(name); + }); +}; + +tests.SINTERSTORE = function () { + var name = "SINTERSTORE"; + + client.del('sa'); + client.del('sb'); + client.del('sc'); + client.del('foo'); + + client.sadd('sa', 'a', require_number(1, name)); + client.sadd('sa', 'b', require_number(1, name)); + client.sadd('sa', 'c', require_number(1, name)); + + client.sadd('sb', 'b', require_number(1, name)); + client.sadd('sb', 'c', require_number(1, name)); + client.sadd('sb', 'd', require_number(1, name)); + + client.sadd('sc', 'c', require_number(1, name)); + client.sadd('sc', 'd', require_number(1, name)); + client.sadd('sc', 'e', require_number(1, name)); + + client.sinterstore('foo', 'sa', 'sb', 'sc', require_number(1, name)); + + client.smembers('foo', function (err, members) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(members), [ 'c' ], name); + next(name); + }); +}; + +tests.SUNION = function () { + var name = "SUNION"; + + client.del('sa'); + client.del('sb'); + client.del('sc'); + + client.sadd('sa', 'a', require_number(1, name)); + client.sadd('sa', 'b', require_number(1, name)); + client.sadd('sa', 'c', require_number(1, name)); + + client.sadd('sb', 'b', require_number(1, name)); + client.sadd('sb', 'c', require_number(1, name)); + client.sadd('sb', 'd', require_number(1, name)); + + client.sadd('sc', 'c', require_number(1, name)); + client.sadd('sc', 'd', require_number(1, name)); + client.sadd('sc', 'e', require_number(1, name)); + + client.sunion('sa', 'sb', 'sc', function (err, union) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(union).sort(), ['a', 'b', 'c', 'd', 'e'], name); + next(name); + }); +}; + +tests.SUNIONSTORE = function () { + var name = "SUNIONSTORE"; + + client.del('sa'); + client.del('sb'); + client.del('sc'); + client.del('foo'); + + client.sadd('sa', 'a', require_number(1, name)); + client.sadd('sa', 'b', require_number(1, name)); + client.sadd('sa', 'c', require_number(1, name)); + + client.sadd('sb', 'b', require_number(1, name)); + client.sadd('sb', 'c', require_number(1, name)); + client.sadd('sb', 'd', require_number(1, name)); + + client.sadd('sc', 'c', require_number(1, name)); + client.sadd('sc', 'd', require_number(1, name)); + client.sadd('sc', 'e', require_number(1, name)); + + client.sunionstore('foo', 'sa', 'sb', 'sc', function (err, cardinality) { + if (err) { + assert.fail(err, name); + } + assert.equal(cardinality, 5, name); + }); + + client.smembers('foo', function (err, members) { + if (err) { + assert.fail(err, name); + } + assert.equal(members.length, 5, name); + assert.deepEqual(buffers_to_strings(members).sort(), ['a', 'b', 'c', 'd', 'e'], name); + next(name); + }); +}; + +// SORT test adapted from Brian Hammond's redis-node-client.js, which has a comprehensive test suite + +tests.SORT = function () { + var name = "SORT"; + + client.del('y'); + client.del('x'); + + client.rpush('y', 'd', require_number(1, name)); + client.rpush('y', 'b', require_number(2, name)); + client.rpush('y', 'a', require_number(3, name)); + client.rpush('y', 'c', require_number(4, name)); + + client.rpush('x', '3', require_number(1, name)); + client.rpush('x', '9', require_number(2, name)); + client.rpush('x', '2', require_number(3, name)); + client.rpush('x', '4', require_number(4, name)); + + client.set('w3', '4', require_string("OK", name)); + client.set('w9', '5', require_string("OK", name)); + client.set('w2', '12', require_string("OK", name)); + client.set('w4', '6', require_string("OK", name)); + + client.set('o2', 'buz', require_string("OK", name)); + client.set('o3', 'foo', require_string("OK", name)); + client.set('o4', 'baz', require_string("OK", name)); + client.set('o9', 'bar', require_string("OK", name)); + + client.set('p2', 'qux', require_string("OK", name)); + client.set('p3', 'bux', require_string("OK", name)); + client.set('p4', 'lux', require_string("OK", name)); + client.set('p9', 'tux', require_string("OK", name)); + + // Now the data has been setup, we can test. + + // But first, test basic sorting. + + // y = [ d b a c ] + // sort y ascending = [ a b c d ] + // sort y descending = [ d c b a ] + + client.sort('y', 'asc', 'alpha', function (err, sorted) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(sorted), ['a', 'b', 'c', 'd'], name); + }); + + client.sort('y', 'desc', 'alpha', function (err, sorted) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(sorted), ['d', 'c', 'b', 'a'], name); + }); + + // Now try sorting numbers in a list. + // x = [ 3, 9, 2, 4 ] + + client.sort('x', 'asc', function (err, sorted) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(sorted), [2, 3, 4, 9], name); + }); + + client.sort('x', 'desc', function (err, sorted) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(sorted), [9, 4, 3, 2], name); + }); + + // Try sorting with a 'by' pattern. + + client.sort('x', 'by', 'w*', 'asc', function (err, sorted) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(sorted), [3, 9, 4, 2], name); + }); + + // Try sorting with a 'by' pattern and 1 'get' pattern. + + client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', function (err, sorted) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(sorted), ['foo', 'bar', 'baz', 'buz'], name); + }); + + // Try sorting with a 'by' pattern and 2 'get' patterns. + + client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', function (err, sorted) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(sorted), ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux'], name); + }); + + // Try sorting with a 'by' pattern and 2 'get' patterns. + // Instead of getting back the sorted set/list, store the values to a list. + // Then check that the values are there in the expected order. + + client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', 'store', 'bacon', function (err) { + if (err) { + assert.fail(err, name); + } + }); + + client.lrange('bacon', 0, -1, function (err, values) { + if (err) { + assert.fail(err, name); + } + assert.deepEqual(buffers_to_strings(values), ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux'], name); + next(name); + }); + + // TODO - sort by hash value +}; + +tests.BLPOP = function () { + var name = "BLPOP"; + + client.rpush("blocking list", "initial value", function (err, res) { + client2.BLPOP("blocking list", 0, function (err, res) { + assert.strictEqual("blocking list", res[0].toString()); + assert.strictEqual("initial value", res[1].toString()); + + client.rpush("blocking list", "wait for this value"); + }); + client2.BLPOP("blocking list", 0, function (err, res) { + assert.strictEqual("blocking list", res[0].toString()); + assert.strictEqual("wait for this value", res[1].toString()); + next(name); + }); + }); +}; + +tests.BLPOP_TIMEOUT = function () { + var name = "BLPOP_TIMEOUT"; + + // try to BLPOP the list again, which should be empty. This should timeout and return null. + client2.BLPOP("blocking list", 1, function (err, res) { + if (err) { + throw err; + } + + assert.strictEqual(res, null); + next(name); + }); +}; + +tests.EXPIRE = function () { + var name = "EXPIRE"; + client.set(['expiry key', 'bar'], require_string("OK", name)); + client.EXPIRE(["expiry key", "1"], require_number_pos(name)); + setTimeout(function () { + client.exists(["expiry key"], last(name, require_number(0, name))); + }, 2000); +}; + +tests.TTL = function () { + var name = "TTL"; + client.set(["ttl key", "ttl val"], require_string("OK", name)); + client.expire(["ttl key", "100"], require_number_pos(name)); + setTimeout(function () { + client.TTL(["ttl key"], last(name, require_number_pos(0, name))); + }, 500); +}; + +all_tests = Object.keys(tests); +all_start = new Date(); +test_count = 0; + +run_next_test = function run_next_test() { + var test_name = all_tests.shift(); + if (typeof tests[test_name] === "function") { + util.print('- \x1b[1m' + test_name.toLowerCase() + '\x1b[0m:'); + cur_start = new Date(); + test_count += 1; + tests[test_name](); + } else { + console.log('\n completed \x1b[32m%d\x1b[0m tests in \x1b[33m%d\x1b[0m ms\n', test_count, new Date() - all_start); + client.quit(); + client2.quit(); + client4.quit(); + } +}; + +console.log("Using reply parser " + client.reply_parser.name); + +client.once("ready", function start_tests() { + console.log("Connected to " + client.host + ":" + client.port + ", Redis server version " + client.server_info.redis_version + "\n"); + + run_next_test(); + + connected = true; +}); + +client.on('end', function () { + ended = true; +}); + +// TODO - need a better way to test auth, maybe auto-config a local Redis server? Sounds hard. +// Yes, this is the real password. Please be nice, thanks. +client4.auth("664b1b6aaf134e1ec281945a8de702a9", function (err, res) { + var name = "AUTH_4"; + + if (err) { + assert.fail(err, name); + } + assert.strictEqual("OK", res.toString(), "auth"); +}); + +// Exit immediately on connection failure, which triggers "exit", below, which fails the test +client.on("error", function (err) { + console.error("client: " + err.stack); + process.exit(); +}); +client2.on("error", function (err) { + console.error("client2: " + err.stack); + process.exit(); +}); +client3.on("error", function (err) { + console.error("client3: " + err.stack); + process.exit(); +}); + +client.on("reconnecting", function (params) { +// console.log("reconnecting: " + util.inspect(params)); +}); + +process.on('uncaughtException', function (err) { + console.error("Uncaught exception: " + err.stack); + process.exit(1); +}); + +process.on('exit', function (code) { + assert.equal(true, connected); + assert.equal(true, ended); +}); diff --git a/node_modules/socket.io/node_modules/redis/tests/buffer_bench.js b/node_modules/socket.io/node_modules/redis/tests/buffer_bench.js new file mode 100644 index 0000000..a504fbc --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/buffer_bench.js @@ -0,0 +1,89 @@ +var source = new Buffer(100), + dest = new Buffer(100), i, j, k, tmp, count = 1000000, bytes = 100; + +for (i = 99 ; i >= 0 ; i--) { + source[i] = 120; +} + +var str = "This is a nice String.", + buf = new Buffer("This is a lovely Buffer."); + +var start = new Date(); +for (i = count * 100; i > 0 ; i--) { + if (Buffer.isBuffer(str)) {} +} +var end = new Date(); +console.log("Buffer.isBuffer(str) " + (end - start) + " ms"); + +var start = new Date(); +for (i = count * 100; i > 0 ; i--) { + if (Buffer.isBuffer(buf)) {} +} +var end = new Date(); +console.log("Buffer.isBuffer(buf) " + (end - start) + " ms"); + +var start = new Date(); +for (i = count * 100; i > 0 ; i--) { + if (str instanceof Buffer) {} +} +var end = new Date(); +console.log("str instanceof Buffer " + (end - start) + " ms"); + +var start = new Date(); +for (i = count * 100; i > 0 ; i--) { + if (buf instanceof Buffer) {} +} +var end = new Date(); +console.log("buf instanceof Buffer " + (end - start) + " ms"); + +for (i = bytes ; i > 0 ; i --) { + var start = new Date(); + for (j = count ; j > 0; j--) { + tmp = source.toString("ascii", 0, bytes); + } + var end = new Date(); + console.log("toString() " + i + " bytes " + (end - start) + " ms"); +} + +for (i = bytes ; i > 0 ; i --) { + var start = new Date(); + for (j = count ; j > 0; j--) { + tmp = ""; + for (k = 0; k <= i ; k++) { + tmp += String.fromCharCode(source[k]); + } + } + var end = new Date(); + console.log("manual string " + i + " bytes " + (end - start) + " ms"); +} + +for (i = bytes ; i > 0 ; i--) { + var start = new Date(); + for (j = count ; j > 0 ; j--) { + for (k = i ; k > 0 ; k--) { + dest[k] = source[k]; + } + } + var end = new Date(); + console.log("Manual copy " + i + " bytes " + (end - start) + " ms"); +} + +for (i = bytes ; i > 0 ; i--) { + var start = new Date(); + for (j = count ; j > 0 ; j--) { + for (k = i ; k > 0 ; k--) { + dest[k] = 120; + } + } + var end = new Date(); + console.log("Direct assignment " + i + " bytes " + (end - start) + " ms"); +} + +for (i = bytes ; i > 0 ; i--) { + var start = new Date(); + for (j = count ; j > 0 ; j--) { + source.copy(dest, 0, 0, i); + } + var end = new Date(); + console.log("Buffer.copy() " + i + " bytes " + (end - start) + " ms"); +} diff --git a/node_modules/socket.io/node_modules/redis/tests/reconnect_test.js b/node_modules/socket.io/node_modules/redis/tests/reconnect_test.js new file mode 100644 index 0000000..08a6ca6 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/reconnect_test.js @@ -0,0 +1,27 @@ +var redis = require("redis").createClient(); + +redis.on("error", function (err) { + console.log("Redis says: " + err); +}); + +redis.on("ready", function () { + console.log("Redis ready."); +}); + +redis.on("reconnecting", function (arg) { + console.log("Redis reconnecting: " + JSON.stringify(arg)); +}); +redis.on("connect", function () { + console.log("Redis connected."); +}); + +setInterval(function () { + var now = Date.now(); + redis.set("now", now, function (err, res) { + if (err) { + console.log(now + " Redis reply error: " + err); + } else { + console.log(now + " Redis reply: " + res); + } + }); +}, 200); diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/codec.js b/node_modules/socket.io/node_modules/redis/tests/stress/codec.js new file mode 100644 index 0000000..7d764f6 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/codec.js @@ -0,0 +1,16 @@ +var json = { + encode: JSON.stringify, + decode: JSON.parse +}; + +var MsgPack = require('node-msgpack'); +msgpack = { + encode: MsgPack.pack, + decode: function(str) { return MsgPack.unpack(new Buffer(str)); } +}; + +bison = require('bison'); + +module.exports = json; +//module.exports = msgpack; +//module.exports = bison; diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/pubsub/pub.js b/node_modules/socket.io/node_modules/redis/tests/stress/pubsub/pub.js new file mode 100644 index 0000000..0acde7a --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/pubsub/pub.js @@ -0,0 +1,38 @@ +'use strict'; + +var freemem = require('os').freemem; +var profiler = require('v8-profiler'); +var codec = require('../codec'); + +var sent = 0; + +var pub = require('redis').createClient(null, null, { + //command_queue_high_water: 5, + //command_queue_low_water: 1 +}) +.on('ready', function() { + this.emit('drain'); +}) +.on('drain', function() { + process.nextTick(exec); +}); + +var payload = '1'; for (var i = 0; i < 12; ++i) payload += payload; +console.log('Message payload length', payload.length); + +function exec() { + pub.publish('timeline', codec.encode({ foo: payload })); + ++sent; + if (!pub.should_buffer) { + process.nextTick(exec); + } +} + +profiler.takeSnapshot('s_0'); + +exec(); + +setInterval(function() { + profiler.takeSnapshot('s_' + sent); + console.error('sent', sent, 'free', freemem(), 'cmdqlen', pub.command_queue.length, 'offqlen', pub.offline_queue.length); +}, 2000); diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/pubsub/run b/node_modules/socket.io/node_modules/redis/tests/stress/pubsub/run new file mode 100755 index 0000000..bd9ac39 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/pubsub/run @@ -0,0 +1,10 @@ +#!/bin/sh +node server.js & +node server.js & +node server.js & +node server.js & +node server.js & +node server.js & +node server.js & +node server.js & +node --debug pub.js diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/pubsub/server.js b/node_modules/socket.io/node_modules/redis/tests/stress/pubsub/server.js new file mode 100644 index 0000000..035e6b7 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/pubsub/server.js @@ -0,0 +1,23 @@ +'use strict'; + +var freemem = require('os').freemem; +var codec = require('../codec'); + +var id = Math.random(); +var recv = 0; + +var sub = require('redis').createClient() + .on('ready', function() { + this.subscribe('timeline'); + }) + .on('message', function(channel, message) { + var self = this; + if (message) { + message = codec.decode(message); + ++recv; + } + }); + +setInterval(function() { + console.error('id', id, 'received', recv, 'free', freemem()); +}, 2000); diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/rpushblpop/pub.js b/node_modules/socket.io/node_modules/redis/tests/stress/rpushblpop/pub.js new file mode 100644 index 0000000..9caf1d0 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/rpushblpop/pub.js @@ -0,0 +1,49 @@ +'use strict'; + +var freemem = require('os').freemem; +//var profiler = require('v8-profiler'); +var codec = require('../codec'); + +var sent = 0; + +var pub = require('redis').createClient(null, null, { + //command_queue_high_water: 5, + //command_queue_low_water: 1 +}) +.on('ready', function() { + this.del('timeline'); + this.emit('drain'); +}) +.on('drain', function() { + process.nextTick(exec); +}); + +var payload = '1'; for (var i = 0; i < 12; ++i) payload += payload; +console.log('Message payload length', payload.length); + +function exec() { + pub.rpush('timeline', codec.encode({ foo: payload })); + ++sent; + if (!pub.should_buffer) { + process.nextTick(exec); + } +} + +//profiler.takeSnapshot('s_0'); + +exec(); + +setInterval(function() { + //var ss = profiler.takeSnapshot('s_' + sent); + //console.error(ss.stringify()); + pub.llen('timeline', function(err, result) { + console.error('sent', sent, 'free', freemem(), + 'cmdqlen', pub.command_queue.length, 'offqlen', pub.offline_queue.length, + 'llen', result + ); + }); +}, 2000); + +/*setTimeout(function() { + process.exit(); +}, 30000);*/ diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/rpushblpop/run b/node_modules/socket.io/node_modules/redis/tests/stress/rpushblpop/run new file mode 100755 index 0000000..8045ae8 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/rpushblpop/run @@ -0,0 +1,6 @@ +#!/bin/sh +node server.js & +#node server.js & +#node server.js & +#node server.js & +node --debug pub.js diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/rpushblpop/server.js b/node_modules/socket.io/node_modules/redis/tests/stress/rpushblpop/server.js new file mode 100644 index 0000000..9cbcdd9 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/rpushblpop/server.js @@ -0,0 +1,30 @@ +'use strict'; + +var freemem = require('os').freemem; +var codec = require('../codec'); + +var id = Math.random(); +var recv = 0; + +var cmd = require('redis').createClient(); +var sub = require('redis').createClient() + .on('ready', function() { + this.emit('timeline'); + }) + .on('timeline', function() { + var self = this; + this.blpop('timeline', 0, function(err, result) { + var message = result[1]; + if (message) { + message = codec.decode(message); + ++recv; + } + self.emit('timeline'); + }); + }); + +setInterval(function() { + cmd.llen('timeline', function(err, result) { + console.error('id', id, 'received', recv, 'free', freemem(), 'llen', result); + }); +}, 2000); diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/speed/00 b/node_modules/socket.io/node_modules/redis/tests/stress/speed/00 new file mode 100644 index 0000000..29d7bf7 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/speed/00 @@ -0,0 +1,13 @@ +# size JSON msgpack bison +26602 2151.0170848180414 +25542 ? 2842.589272665782 +24835 ? ? 7280.4538397469805 +6104 6985.234528557929 +5045 ? 7217.461392841478 +4341 ? ? 14261.406335354604 +4180 15864.633685636572 +4143 ? 12954.806235781925 +4141 ? ? 44650.70733912719 +75 114227.07313350472 +40 ? 30162.440062810834 +39 ? ? 119815.66013519121 diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/speed/plot b/node_modules/socket.io/node_modules/redis/tests/stress/speed/plot new file mode 100755 index 0000000..2563797 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/speed/plot @@ -0,0 +1,13 @@ +#!/bin/sh + +gnuplot >size-rate.jpg << _EOF_ + +set terminal png nocrop enhanced font verdana 12 size 640,480 +set logscale x +set logscale y +set grid +set xlabel 'Serialized object size, octets' +set ylabel 'decode(encode(obj)) rate, 1/sec' +plot '00' using 1:2 title 'json' smooth bezier, '00' using 1:3 title 'msgpack' smooth bezier, '00' using 1:4 title 'bison' smooth bezier + +_EOF_ diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/speed/size-rate.png b/node_modules/socket.io/node_modules/redis/tests/stress/speed/size-rate.png new file mode 100644 index 0000000..c9c2bee Binary files /dev/null and b/node_modules/socket.io/node_modules/redis/tests/stress/speed/size-rate.png differ diff --git a/node_modules/socket.io/node_modules/redis/tests/stress/speed/speed.js b/node_modules/socket.io/node_modules/redis/tests/stress/speed/speed.js new file mode 100644 index 0000000..8e43cbc --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/stress/speed/speed.js @@ -0,0 +1,84 @@ +var msgpack = require('node-msgpack'); +var bison = require('bison'); +var codec = { + JSON: { + encode: JSON.stringify, + decode: JSON.parse + }, + msgpack: { + encode: msgpack.pack, + decode: msgpack.unpack + }, + bison: bison +}; + +var obj, l; + +var s = '0'; +for (var i = 0; i < 12; ++i) s += s; + +obj = { + foo: s, + arrrrrr: [{a:1,b:false,c:null,d:1.0}, 1111, 2222, 33333333], + rand: [], + a: s, + ccc: s, + b: s + s + s +}; +for (i = 0; i < 100; ++i) obj.rand.push(Math.random()); +forObj(obj); + +obj = { + foo: s, + arrrrrr: [{a:1,b:false,c:null,d:1.0}, 1111, 2222, 33333333], + rand: [] +}; +for (i = 0; i < 100; ++i) obj.rand.push(Math.random()); +forObj(obj); + +obj = { + foo: s, + arrrrrr: [{a:1,b:false,c:null,d:1.0}, 1111, 2222, 33333333], + rand: [] +}; +forObj(obj); + +obj = { + arrrrrr: [{a:1,b:false,c:null,d:1.0}, 1111, 2222, 33333333], + rand: [] +}; +forObj(obj); + +function run(obj, codec) { + var t1 = Date.now(); + var n = 10000; + for (var i = 0; i < n; ++i) { + codec.decode(l = codec.encode(obj)); + } + var t2 = Date.now(); + //console.log('DONE', n*1000/(t2-t1), 'codecs/sec, length=', l.length); + return [n*1000/(t2-t1), l.length]; +} + +function series(obj, cname, n) { + var rate = 0; + var len = 0; + for (var i = 0; i < n; ++i) { + var r = run(obj, codec[cname]); + rate += r[0]; + len += r[1]; + } + rate /= n; + len /= n; + console.log(cname + ' ' + rate + ' ' + len); + return [rate, len]; +} + +function forObj(obj) { + var r = { + JSON: series(obj, 'JSON', 20), + msgpack: series(obj, 'msgpack', 20), + bison: series(obj, 'bison', 20) + }; + return r; +} diff --git a/node_modules/socket.io/node_modules/redis/tests/sub_quit_test.js b/node_modules/socket.io/node_modules/redis/tests/sub_quit_test.js new file mode 100644 index 0000000..ad1f413 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/sub_quit_test.js @@ -0,0 +1,18 @@ +var client = require("redis").createClient(), + client2 = require("redis").createClient(); + +client.subscribe("something"); +client.on("subscribe", function (channel, count) { + console.log("Got sub: " + channel); + client.unsubscribe("something"); +}); + +client.on("unsubscribe", function (channel, count) { + console.log("Got unsub: " + channel + ", quitting"); + client.quit(); +}); + +// exercise unsub before sub +client2.unsubscribe("something"); +client2.subscribe("another thing"); +client2.quit(); diff --git a/node_modules/socket.io/node_modules/redis/tests/test_start_stop.js b/node_modules/socket.io/node_modules/redis/tests/test_start_stop.js new file mode 100644 index 0000000..0770893 --- /dev/null +++ b/node_modules/socket.io/node_modules/redis/tests/test_start_stop.js @@ -0,0 +1,17 @@ +var redis = require("./index"), + client = redis.createClient(); + +// This currently doesn't work, due to what I beleive to be a bug in redis 2.0.1. +// INFO and QUIT are pipelined together, and the socket closes before the INFO +// command gets a reply. + +redis.debug_mode = true; +client.info(redis.print); +client.quit(); + +// A workaround is: +// client.info(function (err, res) { +// console.log(res.toString()); +// client.quit(); +// }); + diff --git a/node_modules/socket.io/node_modules/socket.io-client/.npmignore b/node_modules/socket.io/node_modules/socket.io-client/.npmignore new file mode 100644 index 0000000..c27cb50 --- /dev/null +++ b/node_modules/socket.io/node_modules/socket.io-client/.npmignore @@ -0,0 +1,2 @@ +test/node_modules +support diff --git a/node_modules/socket.io/node_modules/socket.io-client/History.md b/node_modules/socket.io/node_modules/socket.io-client/History.md new file mode 100644 index 0000000..6f24aab --- /dev/null +++ b/node_modules/socket.io/node_modules/socket.io-client/History.md @@ -0,0 +1,125 @@ + +0.8.6 / 2011-10-27 +================== + + * Added WebWorker support. + * Fixed swfobject and web_socket.js to not assume window. + * Fixed CORS detection for webworker. + * Fix `defer` for webkit in a webworker. + * Fixed io.util.request to not rely on window. + * FIxed; use global instead of window and dont rely on document. + * Fixed; JSON-P handshake if CORS is not available. + * Made underlying Transport disconnection trigger immediate socket.io disconnect. + * Fixed warning when compressing with Google Closure Compiler. + * Fixed builder's uglify utf-8 support. + * Added workaround for loading indicator in FF jsonp-polling. [3rd-Eden] + * Fixed host discovery lookup. [holic] + * Fixed close timeout when disconnected/reconnecting. [jscharlach] + * Fixed jsonp-polling feature detection. + * Fixed jsonp-polling client POSTing of \n. + * Fixed test runner on IE6/7 + +0.8.5 / 2011-10-07 +================== + + * Bumped client + +0.8.4 / 2011-09-06 +================== + + * Corrected build + +0.8.3 / 2011-09-03 +================== + + * Fixed `\n` parsing for non-JSON packets. + * Fixed; make Socket.IO XHTML doctype compatible (fixes #460 from server) + * Fixed support for Node.JS running `socket.io-client`. + * Updated repository name in `package.json`. + * Added support for different policy file ports without having to port + forward 843 on the server side [3rd-Eden] + +0.8.2 / 2011-08-29 +================== + + * Fixed flashsocket detection. + +0.8.1 / 2011-08-29 +================== + + * Bump version. + +0.8.0 / 2011-08-28 +================== + + * Added MozWebSocket support (hybi-10 doesn't require API changes) [einaros]. + +0.7.11 / 2011-08-27 +=================== + + * Corrected previous release (missing build). + +0.7.10 / 2011-08-27 +=================== + + * Fix for failing fallback in websockets + +0.7.9 / 2011-08-12 +================== + + * Added check on `Socket#onConnect` to prevent double `connect` events on the main manager. + * Fixed socket namespace connect test. Remove broken alternative namespace connect test. + * Removed test handler for removed test. + * Bumped version to match `socket.io` server. + +0.7.5 / 2011-08-08 +================== + + * Added querystring support for `connect` [3rd-Eden] + * Added partial Node.JS transports support [3rd-Eden, josephg] + * Fixed builder test. + * Changed `util.inherit` to replicate Object.create / __proto__. + * Changed and cleaned up some acceptance tests. + * Fixed race condition with a test that could not be run multiple times. + * Added test for encoding a payload. + * Added the ability to override the transport to use in acceptance test [3rd-Eden] + * Fixed multiple connect packets [DanielBaulig] + * Fixed jsonp-polling over-buffering [3rd-Eden] + * Fixed ascii preservation in minified socket.io client [3rd-Eden] + * Fixed socket.io in situations where the page is not served through utf8. + * Fixed namespaces not reconnecting after disconnect [3rd-Eden] + * Fixed default port for secure connections. + +0.7.4 / 2011-07-12 +================== + + * Added `SocketNamespace#of` shortcut. [3rd-Eden] + * Fixed a IE payload decoding bug. [3rd-Eden] + * Honor document protocol, unless overriden. [dvv] + * Fixed new builder dependencies. [3rd-Eden] + +0.7.3 / 2011-06-30 +================== + + * Fixed; acks don't depend on arity. They're automatic for `.send` and + callback based for `.emit`. [dvv] + * Added support for sub-sockets authorization. [3rd-Eden] + * Added BC support for `new io.connect`. [fat] + * Fixed double `connect` events. [3rd-Eden] + * Fixed reconnection with jsonp-polling maintaining old sessionid. [franck34] + +0.7.2 / 2011-06-22 +================== + + * Added `noop` message type. + +0.7.1 / 2011-06-21 +================== + + * Bumped socket.io dependency version for acceptance tests. + +0.7.0 / 2011-06-21 +================== + + * http://socket.io/announcement.html + diff --git a/node_modules/socket.io/node_modules/socket.io-client/Makefile b/node_modules/socket.io/node_modules/socket.io-client/Makefile new file mode 100644 index 0000000..f2d2f41 --- /dev/null +++ b/node_modules/socket.io/node_modules/socket.io-client/Makefile @@ -0,0 +1,20 @@ + +ALL_TESTS = $(shell find test/ -name '*.test.js') + +run-tests: + @./node_modules/.bin/expresso \ + -I lib \ + -I support \ + --serial \ + $(TESTS) + +test: + @$(MAKE) TESTS="$(ALL_TESTS)" run-tests + +test-acceptance: + @node support/test-runner/app $(TRANSPORT) + +build: + @node ./bin/builder.js + +.PHONY: test diff --git a/node_modules/socket.io/node_modules/socket.io-client/README.md b/node_modules/socket.io/node_modules/socket.io-client/README.md new file mode 100644 index 0000000..cdb7715 --- /dev/null +++ b/node_modules/socket.io/node_modules/socket.io-client/README.md @@ -0,0 +1,246 @@ +socket.io +========= + +#### Sockets for the rest of us + +The `socket.io` client is basically a simple HTTP Socket interface implementation. +It looks similar to WebSocket while providing additional features and +leveraging other transports when WebSocket is not supported by the user's +browser. + +```js +var socket = io.connect('http://domain.com'); +socket.on('connect', function () { + // socket connected +}); +socket.on('custom event', function () { + // server emitted a custom event +}); +socket.on('disconnect', function () { + // socket disconnected +}); +socket.send('hi there'); +``` + +### Recipes + +#### Utilizing namespaces (ie: multiple sockets) + +If you want to namespace all the messages and events emitted to a particular +endpoint, simply specify it as part of the `connect` uri: + +```js +var chat = io.connect('http://localhost/chat'); +chat.on('connect', function () { + // chat socket connected +}); + +var news = io.connect('/news'); // io.connect auto-detects host +news.on('connect', function () { + // news socket connected +}); +``` + +#### Emitting custom events + +To ease with the creation of applications, you can emit custom events outside +of the global `message` event. + +```js +var socket = io.connect(); +socket.emit('server custom event', { my: 'data' }); +``` + +#### Forcing disconnection + +```js +var socket = io.connect(); +socket.on('connect', function () { + socket.disconnect(); +}); +``` + +### Documentation + +#### io#connect + +```js +io.connect(uri, [options]); +``` + +##### Options: + +- *resource* + + socket.io + + The resource is what allows the `socket.io` server to identify incoming connections by `socket.io` clients. In other words, any HTTP server can implement socket.io and still serve other normal, non-realtime HTTP requests. + +- *transports* + +```js +['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling', 'jsonp-polling'] +``` + + A list of the transports to attempt to utilize (in order of preference). + +- *'connect timeout'* + +```js +5000 +``` + + The amount of milliseconds a transport has to create a connection before we consider it timed out. + +- *'try multiple transports'* + +```js +true +``` + + A boolean indicating if we should try other transports when the connectTimeout occurs. + +- *reconnect* + +```js +true +``` + + A boolean indicating if we should automatically reconnect if a connection is disconnected. + +- *'reconnection delay'* + +```js +500 +``` + + The amount of milliseconds before we try to connect to the server again. We are using a exponential back off algorithm for the following reconnections, on each reconnect attempt this value will get multiplied (500 > 1000 > 2000 > 4000 > 8000). + + +- *'max reconnection attempts'* + +```js +10 +``` + + The amount of attempts should we make using the current transport to connect to the server? After this we will do one final attempt, and re-try with all enabled transport methods before we give up. + +##### Properties: + +- *options* + + The passed in options combined with the defaults. + +- *connected* + + Whether the socket is connected or not. + +- *connecting* + + Whether the socket is connecting or not. + +- *reconnecting* + + Whether we are reconnecting or not. + +- *transport* + + The transport instance. + +##### Methods: + +- *connect(λ)* + + Establishes a connection. If λ is supplied as argument, it will be called once the connection is established. + +- *send(message)* + + A string of data to send. + +- *disconnect* + + Closes the connection. + +- *on(event, λ)* + + Adds a listener for the event *event*. + +- *once(event, λ)* + + Adds a one time listener for the event *event*. The listener is removed after the first time the event is fired. + +- *removeListener(event, λ)* + + Removes the listener λ for the event *event*. + +##### Events: + +- *connect* + + Fired when the connection is established and the handshake successful. + +- *connecting(transport_type)* + + Fired when a connection is attempted, passing the transport name. + +- *connect_failed* + + Fired when the connection timeout occurs after the last connection attempt. + This only fires if the `connectTimeout` option is set. + If the `tryTransportsOnConnectTimeout` option is set, this only fires once all + possible transports have been tried. + +- *message(message)* + + Fired when a message arrives from the server + +- *close* + + Fired when the connection is closed. Be careful with using this event, as some transports will fire it even under temporary, expected disconnections (such as XHR-Polling). + +- *disconnect* + + Fired when the connection is considered disconnected. + +- *reconnect(transport_type,reconnectionAttempts)* + + Fired when the connection has been re-established. This only fires if the `reconnect` option is set. + +- *reconnecting(reconnectionDelay,reconnectionAttempts)* + + Fired when a reconnection is attempted, passing the next delay for the next reconnection. + +- *reconnect_failed* + + Fired when all reconnection attempts have failed and we where unsuccessful in reconnecting to the server. + +### Contributors + +Guillermo Rauch <guillermo@learnboost.com> + +Arnout Kazemier <info@3rd-eden.com> + +### License + +(The MIT License) + +Copyright (c) 2010 LearnBoost <dev@learnboost.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/socket.io/node_modules/socket.io-client/bin/builder.js b/node_modules/socket.io/node_modules/socket.io-client/bin/builder.js new file mode 100755 index 0000000..8b1b680 --- /dev/null +++ b/node_modules/socket.io/node_modules/socket.io-client/bin/builder.js @@ -0,0 +1,278 @@ +/*! + * socket.io-node + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var fs = require('fs') + , socket = require('../lib/io') + , uglify = require('uglify-js'); + +/** + * License headers. + * + * @api private + */ + +var template = '/*! Socket.IO.%ext% build:' + socket.version + ', %type%. Copyright(c) 2011 LearnBoost MIT Licensed */\n' + , development = template.replace('%type%', 'development').replace('%ext%', 'js') + , production = template.replace('%type%', 'production').replace('%ext%', 'min.js'); + +/** + * If statements, these allows you to create serveride & client side compatible + * code using specially designed `if` statements that remove serverside + * designed code from the source files + * + * @api private + */ + +var starttagIF = '// if node' + , endtagIF = '// end node'; + +/** + * The modules that are required to create a base build of Socket.IO. + * + * @const + * @type {Array} + * @api private + */ + +var base = [ + 'io.js' + , 'util.js' + , 'events.js' + , 'json.js' + , 'parser.js' + , 'transport.js' + , 'socket.js' + , 'namespace.js' + ]; + +/** + * The available transports for Socket.IO. These are mapped as: + * + * - `key` the name of the transport + * - `value` the dependencies for the transport + * + * @const + * @type {Object} + * @api public + */ + +var baseTransports = { + 'websocket': ['transports/websocket.js'] + , 'flashsocket': [ + 'transports/websocket.js' + , 'transports/flashsocket.js' + , 'vendor/web-socket-js/swfobject.js' + , 'vendor/web-socket-js/web_socket.js' + ] + , 'htmlfile': ['transports/xhr.js', 'transports/htmlfile.js'] + /* FIXME: re-enable me once we have multi-part support + , 'xhr-multipart': ['transports/xhr.js', 'transports/xhr-multipart.js'] */ + , 'xhr-polling': ['transports/xhr.js', 'transports/xhr-polling.js'] + , 'jsonp-polling': [ + 'transports/xhr.js' + , 'transports/xhr-polling.js' + , 'transports/jsonp-polling.js' + ] +}; + +/** + * Builds a custom Socket.IO distribution based on the transports that you + * need. You can configure the build to create development build or production + * build (minified). + * + * @param {Array} transports The transports that needs to be bundled. + * @param {Object} [options] Options to configure the building process. + * @param {Function} callback Last argument should always be the callback + * @callback {String|Boolean} err An optional argument, if it exists than an error + * occurred during the build process. + * @callback {String} result The result of the build process. + * @api public + */ + +var builder = module.exports = function () { + var transports, options, callback, error = null + , args = Array.prototype.slice.call(arguments, 0) + , settings = { + minify: true + , node: false + , custom: [] + }; + + // Fancy pancy argument support this makes any pattern possible mainly + // because we require only one of each type + args.forEach(function (arg) { + var type = Object.prototype.toString.call(arg) + .replace(/\[object\s(\w+)\]/gi , '$1' ).toLowerCase(); + + switch (type) { + case 'array': + return transports = arg; + case 'object': + return options = arg; + case 'function': + return callback = arg; + } + }); + + // Add defaults + options = options || {}; + transports = transports || Object.keys(baseTransports); + + // Merge the data + for(var option in options) { + settings[option] = options[option]; + } + + // Start creating a dependencies chain with all the required files for the + // custom Socket.IO bundle. + var files = []; + base.forEach(function (file) { + files.push(__dirname + '/../lib/' + file); + }); + + transports.forEach(function (transport) { + var dependencies = baseTransports[transport]; + if (!dependencies) { + error = 'Unsupported transport `' + transport + '` supplied as argument.'; + return; + } + + // Add the files to the files list, but only if they are not added before + dependencies.forEach(function (file) { + var path = __dirname + '/../lib/' + file; + if (!~files.indexOf(path)) files.push(path); + }) + }); + + // check to see if the files tree compilation generated any errors. + if (error) return callback(error); + + var results = {}; + files.forEach(function (file) { + fs.readFile(file, function (err, content) { + if (err) error = err; + results[file] = content; + + // check if we are done yet, or not.. Just by checking the size of the result + // object. + if (Object.keys(results).length !== files.length) return; + + // we are done, did we error? + if (error) return callback(error); + + // concatinate the file contents in order + var code = development + , ignore = 0; + + files.forEach(function (file) { + code += results[file]; + }); + + // check if we need to add custom code + if (settings.custom.length) { + settings.custom.forEach(function (content) { + code += content; + }); + } + + // Search for conditional code blocks that need to be removed as they + // where designed for a server side env. but only if we don't want to + // make this build node compatible. + if (!settings.node) { + code = code.split('\n').filter(function (line) { + // check if there are tags in here + var start = line.indexOf(starttagIF) >= 0 + , end = line.indexOf(endtagIF) >= 0 + , ret = ignore; + + // ignore the current line + if (start) { + ignore++; + ret = ignore; + } + + // stop ignoring the next line + if (end) { + ignore--; + } + + return ret == 0; + }).join('\n'); + } + + // check if we need to process it any further + if (settings.minify) { + var ast = uglify.parser.parse(code); + ast = uglify.uglify.ast_mangle(ast); + ast = uglify.uglify.ast_squeeze(ast); + + code = production + uglify.uglify.gen_code(ast, { ascii_only: true }); + } + + callback(error, code); + }) + }) +}; + +/** + * Builder version is also the current client version + * this way we don't have to do another include for the + * clients version number and we can just include the builder. + * + * @type {String} + * @api public + */ + +builder.version = socket.version; + +/** + * A list of all build in transport types. + * + * @type {Object} + * @api public + */ + +builder.transports = baseTransports; + +/** + * Command line support, this allows us to generate builds without having + * to load it as module. + */ + +if (!module.parent){ + // the first 2 are `node` and the path to this file, we don't need them + var args = process.argv.slice(2); + + // build a development build + builder(args.length ? args : false, { minify:false }, function (err, content) { + if (err) return console.error(err); + + fs.write( + fs.openSync(__dirname + '/../dist/socket.io.js', 'w') + , content + , 0 + , 'utf8' + ); + console.log('Successfully generated the development build: socket.io.js'); + }); + + // and build a production build + builder(args.length ? args : false, function (err, content) { + if (err) return console.error(err); + + fs.write( + fs.openSync(__dirname + '/../dist/socket.io.min.js', 'w') + , content + , 0 + , 'utf8' + ); + console.log('Successfully generated the production build: socket.io.min.js'); + }); +} diff --git a/node_modules/socket.io/node_modules/socket.io-client/dist/WebSocketMain.swf b/node_modules/socket.io/node_modules/socket.io-client/dist/WebSocketMain.swf new file mode 100644 index 0000000..20a451f Binary files /dev/null and b/node_modules/socket.io/node_modules/socket.io-client/dist/WebSocketMain.swf differ diff --git a/node_modules/socket.io/node_modules/socket.io-client/dist/WebSocketMainInsecure.swf b/node_modules/socket.io/node_modules/socket.io-client/dist/WebSocketMainInsecure.swf new file mode 100644 index 0000000..5949ff3 Binary files /dev/null and b/node_modules/socket.io/node_modules/socket.io-client/dist/WebSocketMainInsecure.swf differ diff --git a/node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js b/node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js new file mode 100644 index 0000000..529627d --- /dev/null +++ b/node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js @@ -0,0 +1,3750 @@ +/*! Socket.IO.js build:0.8.7, development. Copyright(c) 2011 LearnBoost MIT Licensed */ + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, global) { + + /** + * IO namespace. + * + * @namespace + */ + + var io = exports; + + /** + * Socket.IO version + * + * @api public + */ + + io.version = '0.8.7'; + + /** + * Protocol implemented. + * + * @api public + */ + + io.protocol = 1; + + /** + * Available transports, these will be populated with the available transports + * + * @api public + */ + + io.transports = []; + + /** + * Keep track of jsonp callbacks. + * + * @api private + */ + + io.j = []; + + /** + * Keep track of our io.Sockets + * + * @api private + */ + io.sockets = {}; + + + /** + * Manages connections to hosts. + * + * @param {String} uri + * @Param {Boolean} force creation of new socket (defaults to false) + * @api public + */ + + io.connect = function (host, details) { + var uri = io.util.parseUri(host) + , uuri + , socket; + + if (global && global.location) { + uri.protocol = uri.protocol || global.location.protocol.slice(0, -1); + uri.host = uri.host || (global.document + ? global.document.domain : global.location.hostname); + uri.port = uri.port || global.location.port; + } + + uuri = io.util.uniqueUri(uri); + + var options = { + host: uri.host + , secure: 'https' == uri.protocol + , port: uri.port || ('https' == uri.protocol ? 443 : 80) + , query: uri.query || '' + }; + + io.util.merge(options, details); + + if (options['force new connection'] || !io.sockets[uuri]) { + socket = new io.Socket(options); + } + + if (!options['force new connection'] && socket) { + io.sockets[uuri] = socket; + } + + socket = socket || io.sockets[uuri]; + + // if path is different from '' or / + return socket.of(uri.path.length > 1 ? uri.path : ''); + }; + +})('object' === typeof module ? module.exports : (this.io = {}), this); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, global) { + + /** + * Utilities namespace. + * + * @namespace + */ + + var util = exports.util = {}; + + /** + * Parses an URI + * + * @author Steven Levithan (MIT license) + * @api public + */ + + var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; + + var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', + 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', + 'anchor']; + + util.parseUri = function (str) { + var m = re.exec(str || '') + , uri = {} + , i = 14; + + while (i--) { + uri[parts[i]] = m[i] || ''; + } + + return uri; + }; + + /** + * Produces a unique url that identifies a Socket.IO connection. + * + * @param {Object} uri + * @api public + */ + + util.uniqueUri = function (uri) { + var protocol = uri.protocol + , host = uri.host + , port = uri.port; + + if ('document' in global) { + host = host || document.domain; + port = port || (protocol == 'https' + && document.location.protocol !== 'https:' ? 443 : document.location.port); + } else { + host = host || 'localhost'; + + if (!port && protocol == 'https') { + port = 443; + } + } + + return (protocol || 'http') + '://' + host + ':' + (port || 80); + }; + + /** + * Mergest 2 query strings in to once unique query string + * + * @param {String} base + * @param {String} addition + * @api public + */ + + util.query = function (base, addition) { + var query = util.chunkQuery(base || '') + , components = []; + + util.merge(query, util.chunkQuery(addition || '')); + for (var part in query) { + if (query.hasOwnProperty(part)) { + components.push(part + '=' + query[part]); + } + } + + return components.length ? '?' + components.join('&') : ''; + }; + + /** + * Transforms a querystring in to an object + * + * @param {String} qs + * @api public + */ + + util.chunkQuery = function (qs) { + var query = {} + , params = qs.split('&') + , i = 0 + , l = params.length + , kv; + + for (; i < l; ++i) { + kv = params[i].split('='); + if (kv[0]) { + query[kv[0]] = decodeURIComponent(kv[1]); + } + } + + return query; + }; + + /** + * Executes the given function when the page is loaded. + * + * io.util.load(function () { console.log('page loaded'); }); + * + * @param {Function} fn + * @api public + */ + + var pageLoaded = false; + + util.load = function (fn) { + if ('document' in global && document.readyState === 'complete' || pageLoaded) { + return fn(); + } + + util.on(global, 'load', fn, false); + }; + + /** + * Adds an event. + * + * @api private + */ + + util.on = function (element, event, fn, capture) { + if (element.attachEvent) { + element.attachEvent('on' + event, fn); + } else if (element.addEventListener) { + element.addEventListener(event, fn, capture); + } + }; + + /** + * Generates the correct `XMLHttpRequest` for regular and cross domain requests. + * + * @param {Boolean} [xdomain] Create a request that can be used cross domain. + * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest. + * @api private + */ + + util.request = function (xdomain) { + + if (xdomain && 'undefined' != typeof XDomainRequest) { + return new XDomainRequest(); + } + + if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) { + return new XMLHttpRequest(); + } + + if (!xdomain) { + try { + return new ActiveXObject('Microsoft.XMLHTTP'); + } catch(e) { } + } + + return null; + }; + + /** + * XHR based transport constructor. + * + * @constructor + * @api public + */ + + /** + * Change the internal pageLoaded value. + */ + + if ('undefined' != typeof window) { + util.load(function () { + pageLoaded = true; + }); + } + + /** + * Defers a function to ensure a spinner is not displayed by the browser + * + * @param {Function} fn + * @api public + */ + + util.defer = function (fn) { + if (!util.ua.webkit || 'undefined' != typeof importScripts) { + return fn(); + } + + util.load(function () { + setTimeout(fn, 100); + }); + }; + + /** + * Merges two objects. + * + * @api public + */ + + util.merge = function merge (target, additional, deep, lastseen) { + var seen = lastseen || [] + , depth = typeof deep == 'undefined' ? 2 : deep + , prop; + + for (prop in additional) { + if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) { + if (typeof target[prop] !== 'object' || !depth) { + target[prop] = additional[prop]; + seen.push(additional[prop]); + } else { + util.merge(target[prop], additional[prop], depth - 1, seen); + } + } + } + + return target; + }; + + /** + * Merges prototypes from objects + * + * @api public + */ + + util.mixin = function (ctor, ctor2) { + util.merge(ctor.prototype, ctor2.prototype); + }; + + /** + * Shortcut for prototypical and static inheritance. + * + * @api private + */ + + util.inherit = function (ctor, ctor2) { + function f() {}; + f.prototype = ctor2.prototype; + ctor.prototype = new f; + }; + + /** + * Checks if the given object is an Array. + * + * io.util.isArray([]); // true + * io.util.isArray({}); // false + * + * @param Object obj + * @api public + */ + + util.isArray = Array.isArray || function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; + + /** + * Intersects values of two arrays into a third + * + * @api public + */ + + util.intersect = function (arr, arr2) { + var ret = [] + , longest = arr.length > arr2.length ? arr : arr2 + , shortest = arr.length > arr2.length ? arr2 : arr; + + for (var i = 0, l = shortest.length; i < l; i++) { + if (~util.indexOf(longest, shortest[i])) + ret.push(shortest[i]); + } + + return ret; + } + + /** + * Array indexOf compatibility. + * + * @see bit.ly/a5Dxa2 + * @api public + */ + + util.indexOf = function (arr, o, i) { + if (Array.prototype.indexOf) { + return Array.prototype.indexOf.call(arr, o, i); + } + + for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; + i < j && arr[i] !== o; i++) {} + + return j <= i ? -1 : i; + }; + + /** + * Converts enumerables to array. + * + * @api public + */ + + util.toArray = function (enu) { + var arr = []; + + for (var i = 0, l = enu.length; i < l; i++) + arr.push(enu[i]); + + return arr; + }; + + /** + * UA / engines detection namespace. + * + * @namespace + */ + + util.ua = {}; + + /** + * Whether the UA supports CORS for XHR. + * + * @api public + */ + + util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () { + try { + var a = new XMLHttpRequest(); + } catch (e) { + return false; + } + + return a.withCredentials != undefined; + })(); + + /** + * Detect webkit. + * + * @api public + */ + + util.ua.webkit = 'undefined' != typeof navigator + && /webkit/i.test(navigator.userAgent); + +})('undefined' != typeof io ? io : module.exports, this); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io) { + + /** + * Expose constructor. + */ + + exports.EventEmitter = EventEmitter; + + /** + * Event emitter constructor. + * + * @api public. + */ + + function EventEmitter () {}; + + /** + * Adds a listener + * + * @api public + */ + + EventEmitter.prototype.on = function (name, fn) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = fn; + } else if (io.util.isArray(this.$events[name])) { + this.$events[name].push(fn); + } else { + this.$events[name] = [this.$events[name], fn]; + } + + return this; + }; + + EventEmitter.prototype.addListener = EventEmitter.prototype.on; + + /** + * Adds a volatile listener. + * + * @api public + */ + + EventEmitter.prototype.once = function (name, fn) { + var self = this; + + function on () { + self.removeListener(name, on); + fn.apply(this, arguments); + }; + + on.listener = fn; + this.on(name, on); + + return this; + }; + + /** + * Removes a listener. + * + * @api public + */ + + EventEmitter.prototype.removeListener = function (name, fn) { + if (this.$events && this.$events[name]) { + var list = this.$events[name]; + + if (io.util.isArray(list)) { + var pos = -1; + + for (var i = 0, l = list.length; i < l; i++) { + if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { + pos = i; + break; + } + } + + if (pos < 0) { + return this; + } + + list.splice(pos, 1); + + if (!list.length) { + delete this.$events[name]; + } + } else if (list === fn || (list.listener && list.listener === fn)) { + delete this.$events[name]; + } + } + + return this; + }; + + /** + * Removes all listeners for an event. + * + * @api public + */ + + EventEmitter.prototype.removeAllListeners = function (name) { + // TODO: enable this when node 0.5 is stable + //if (name === undefined) { + //this.$events = {}; + //return this; + //} + + if (this.$events && this.$events[name]) { + this.$events[name] = null; + } + + return this; + }; + + /** + * Gets all listeners for a certain event. + * + * @api publci + */ + + EventEmitter.prototype.listeners = function (name) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = []; + } + + if (!io.util.isArray(this.$events[name])) { + this.$events[name] = [this.$events[name]]; + } + + return this.$events[name]; + }; + + /** + * Emits an event. + * + * @api public + */ + + EventEmitter.prototype.emit = function (name) { + if (!this.$events) { + return false; + } + + var handler = this.$events[name]; + + if (!handler) { + return false; + } + + var args = Array.prototype.slice.call(arguments, 1); + + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (io.util.isArray(handler)) { + var listeners = handler.slice(); + + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + } else { + return false; + } + + return true; + }; + +})( + 'undefined' != typeof io ? io : module.exports + , 'undefined' != typeof io ? io : module.parent.exports +); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +/** + * Based on JSON2 (http://www.JSON.org/js.html). + */ + +(function (exports, nativeJSON) { + "use strict"; + + // use native JSON if it's available + if (nativeJSON && nativeJSON.parse){ + return exports.JSON = { + parse: nativeJSON.parse + , stringify: nativeJSON.stringify + } + } + + var JSON = exports.JSON = {}; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + function date(d, key) { + return isFinite(d.valueOf()) ? + d.getUTCFullYear() + '-' + + f(d.getUTCMonth() + 1) + '-' + + f(d.getUTCDate()) + 'T' + + f(d.getUTCHours()) + ':' + + f(d.getUTCMinutes()) + ':' + + f(d.getUTCSeconds()) + 'Z' : null; + }; + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value instanceof Date) { + value = date(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : gap ? + '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : gap ? + '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : + '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + +// If the JSON object does not yet have a parse method, give it one. + + JSON.parse = function (text, reviver) { + // The parse method takes a text and an optional reviver function, and returns + // a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + + // The walk method is used to recursively walk the resulting structure so + // that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + + // Parsing happens in four stages. In the first stage, we replace certain + // Unicode characters with escape sequences. JavaScript handles many characters + // incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + + // In the second stage, we run the text against regular expressions that look + // for non-JSON patterns. We are especially concerned with '()' and 'new' + // because they can cause invocation, and '=' because it can cause mutation. + // But just to be safe, we want to reject all unexpected forms. + + // We split the second stage into 4 regexp operations in order to work around + // crippling inefficiencies in IE's and Safari's regexp engines. First we + // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we + // replace all simple value tokens with ']' characters. Third, we delete all + // open brackets that follow a colon or comma or that begin the text. Finally, + // we look to see that the remaining characters are only whitespace or ']' or + // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + + // In the third stage we use the eval function to compile the text into a + // JavaScript structure. The '{' operator is subject to a syntactic ambiguity + // in JavaScript: it can begin a block or an object literal. We wrap the text + // in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + + // In the optional fourth stage, we recursively walk the new structure, passing + // each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + + // If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + +})( + 'undefined' != typeof io ? io : module.exports + , typeof JSON !== 'undefined' ? JSON : undefined +); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io) { + + /** + * Parser namespace. + * + * @namespace + */ + + var parser = exports.parser = {}; + + /** + * Packet types. + */ + + var packets = parser.packets = [ + 'disconnect' + , 'connect' + , 'heartbeat' + , 'message' + , 'json' + , 'event' + , 'ack' + , 'error' + , 'noop' + ]; + + /** + * Errors reasons. + */ + + var reasons = parser.reasons = [ + 'transport not supported' + , 'client not handshaken' + , 'unauthorized' + ]; + + /** + * Errors advice. + */ + + var advice = parser.advice = [ + 'reconnect' + ]; + + /** + * Shortcuts. + */ + + var JSON = io.JSON + , indexOf = io.util.indexOf; + + /** + * Encodes a packet. + * + * @api private + */ + + parser.encodePacket = function (packet) { + var type = indexOf(packets, packet.type) + , id = packet.id || '' + , endpoint = packet.endpoint || '' + , ack = packet.ack + , data = null; + + switch (packet.type) { + case 'error': + var reason = packet.reason ? indexOf(reasons, packet.reason) : '' + , adv = packet.advice ? indexOf(advice, packet.advice) : ''; + + if (reason !== '' || adv !== '') + data = reason + (adv !== '' ? ('+' + adv) : ''); + + break; + + case 'message': + if (packet.data !== '') + data = packet.data; + break; + + case 'event': + var ev = { name: packet.name }; + + if (packet.args && packet.args.length) { + ev.args = packet.args; + } + + data = JSON.stringify(ev); + break; + + case 'json': + data = JSON.stringify(packet.data); + break; + + case 'connect': + if (packet.qs) + data = packet.qs; + break; + + case 'ack': + data = packet.ackId + + (packet.args && packet.args.length + ? '+' + JSON.stringify(packet.args) : ''); + break; + } + + // construct packet with required fragments + var encoded = [ + type + , id + (ack == 'data' ? '+' : '') + , endpoint + ]; + + // data fragment is optional + if (data !== null && data !== undefined) + encoded.push(data); + + return encoded.join(':'); + }; + + /** + * Encodes multiple messages (payload). + * + * @param {Array} messages + * @api private + */ + + parser.encodePayload = function (packets) { + var decoded = ''; + + if (packets.length == 1) + return packets[0]; + + for (var i = 0, l = packets.length; i < l; i++) { + var packet = packets[i]; + decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]; + } + + return decoded; + }; + + /** + * Decodes a packet + * + * @api private + */ + + var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; + + parser.decodePacket = function (data) { + var pieces = data.match(regexp); + + if (!pieces) return {}; + + var id = pieces[2] || '' + , data = pieces[5] || '' + , packet = { + type: packets[pieces[1]] + , endpoint: pieces[4] || '' + }; + + // whether we need to acknowledge the packet + if (id) { + packet.id = id; + if (pieces[3]) + packet.ack = 'data'; + else + packet.ack = true; + } + + // handle different packet types + switch (packet.type) { + case 'error': + var pieces = data.split('+'); + packet.reason = reasons[pieces[0]] || ''; + packet.advice = advice[pieces[1]] || ''; + break; + + case 'message': + packet.data = data || ''; + break; + + case 'event': + try { + var opts = JSON.parse(data); + packet.name = opts.name; + packet.args = opts.args; + } catch (e) { } + + packet.args = packet.args || []; + break; + + case 'json': + try { + packet.data = JSON.parse(data); + } catch (e) { } + break; + + case 'connect': + packet.qs = data || ''; + break; + + case 'ack': + var pieces = data.match(/^([0-9]+)(\+)?(.*)/); + if (pieces) { + packet.ackId = pieces[1]; + packet.args = []; + + if (pieces[3]) { + try { + packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; + } catch (e) { } + } + } + break; + + case 'disconnect': + case 'heartbeat': + break; + }; + + return packet; + }; + + /** + * Decodes data payload. Detects multiple messages + * + * @return {Array} messages + * @api public + */ + + parser.decodePayload = function (data) { + // IE doesn't like data[i] for unicode chars, charAt works fine + if (data.charAt(0) == '\ufffd') { + var ret = []; + + for (var i = 1, length = ''; i < data.length; i++) { + if (data.charAt(i) == '\ufffd') { + ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length))); + i += Number(length) + 1; + length = ''; + } else { + length += data.charAt(i); + } + } + + return ret; + } else { + return [parser.decodePacket(data)]; + } + }; + +})( + 'undefined' != typeof io ? io : module.exports + , 'undefined' != typeof io ? io : module.parent.exports +); +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io) { + + /** + * Expose constructor. + */ + + exports.Transport = Transport; + + /** + * This is the transport template for all supported transport methods. + * + * @constructor + * @api public + */ + + function Transport (socket, sessid) { + this.socket = socket; + this.sessid = sessid; + }; + + /** + * Apply EventEmitter mixin. + */ + + io.util.mixin(Transport, io.EventEmitter); + + /** + * Handles the response from the server. When a new response is received + * it will automatically update the timeout, decode the message and + * forwards the response to the onMessage function for further processing. + * + * @param {String} data Response from the server. + * @api private + */ + + Transport.prototype.onData = function (data) { + this.clearCloseTimeout(); + + // If the connection in currently open (or in a reopening state) reset the close + // timeout since we have just received data. This check is necessary so + // that we don't reset the timeout on an explicitly disconnected connection. + if (this.connected || this.connecting || this.reconnecting) { + this.setCloseTimeout(); + } + + if (data !== '') { + // todo: we should only do decodePayload for xhr transports + var msgs = io.parser.decodePayload(data); + + if (msgs && msgs.length) { + for (var i = 0, l = msgs.length; i < l; i++) { + this.onPacket(msgs[i]); + } + } + } + + return this; + }; + + /** + * Handles packets. + * + * @api private + */ + + Transport.prototype.onPacket = function (packet) { + if (packet.type == 'heartbeat') { + return this.onHeartbeat(); + } + + if (packet.type == 'connect' && packet.endpoint == '') { + this.onConnect(); + } + + this.socket.onPacket(packet); + + return this; + }; + + /** + * Sets close timeout + * + * @api private + */ + + Transport.prototype.setCloseTimeout = function () { + if (!this.closeTimeout) { + var self = this; + + this.closeTimeout = setTimeout(function () { + self.onDisconnect(); + }, this.socket.closeTimeout); + } + }; + + /** + * Called when transport disconnects. + * + * @api private + */ + + Transport.prototype.onDisconnect = function () { + if (this.close && this.open) this.close(); + this.clearTimeouts(); + this.socket.onDisconnect(); + return this; + }; + + /** + * Called when transport connects + * + * @api private + */ + + Transport.prototype.onConnect = function () { + this.socket.onConnect(); + return this; + } + + /** + * Clears close timeout + * + * @api private + */ + + Transport.prototype.clearCloseTimeout = function () { + if (this.closeTimeout) { + clearTimeout(this.closeTimeout); + this.closeTimeout = null; + } + }; + + /** + * Clear timeouts + * + * @api private + */ + + Transport.prototype.clearTimeouts = function () { + this.clearCloseTimeout(); + + if (this.reopenTimeout) { + clearTimeout(this.reopenTimeout); + } + }; + + /** + * Sends a packet + * + * @param {Object} packet object. + * @api private + */ + + Transport.prototype.packet = function (packet) { + this.send(io.parser.encodePacket(packet)); + }; + + /** + * Send the received heartbeat message back to server. So the server + * knows we are still connected. + * + * @param {String} heartbeat Heartbeat response from the server. + * @api private + */ + + Transport.prototype.onHeartbeat = function (heartbeat) { + this.packet({ type: 'heartbeat' }); + }; + + /** + * Called when the transport opens. + * + * @api private + */ + + Transport.prototype.onOpen = function () { + this.open = true; + this.clearCloseTimeout(); + this.socket.onOpen(); + }; + + /** + * Notifies the base when the connection with the Socket.IO server + * has been disconnected. + * + * @api private + */ + + Transport.prototype.onClose = function () { + var self = this; + + /* FIXME: reopen delay causing a infinit loop + this.reopenTimeout = setTimeout(function () { + self.open(); + }, this.socket.options['reopen delay']);*/ + + this.open = false; + this.socket.onClose(); + this.onDisconnect(); + }; + + /** + * Generates a connection url based on the Socket.IO URL Protocol. + * See for more details. + * + * @returns {String} Connection url + * @api private + */ + + Transport.prototype.prepareUrl = function () { + var options = this.socket.options; + + return this.scheme() + '://' + + options.host + ':' + options.port + '/' + + options.resource + '/' + io.protocol + + '/' + this.name + '/' + this.sessid; + }; + + /** + * Checks if the transport is ready to start a connection. + * + * @param {Socket} socket The socket instance that needs a transport + * @param {Function} fn The callback + * @api private + */ + + Transport.prototype.ready = function (socket, fn) { + fn.call(this); + }; +})( + 'undefined' != typeof io ? io : module.exports + , 'undefined' != typeof io ? io : module.parent.exports +); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io, global) { + + /** + * Expose constructor. + */ + + exports.Socket = Socket; + + /** + * Create a new `Socket.IO client` which can establish a persistent + * connection with a Socket.IO enabled server. + * + * @api public + */ + + function Socket (options) { + this.options = { + port: 80 + , secure: false + , document: 'document' in global ? document : false + , resource: 'socket.io' + , transports: io.transports + , 'connect timeout': 10000 + , 'try multiple transports': true + , 'reconnect': true + , 'reconnection delay': 500 + , 'reconnection limit': Infinity + , 'reopen delay': 3000 + , 'max reconnection attempts': 10 + , 'sync disconnect on unload': true + , 'auto connect': true + , 'flash policy port': 10843 + }; + + io.util.merge(this.options, options); + + this.connected = false; + this.open = false; + this.connecting = false; + this.reconnecting = false; + this.namespaces = {}; + this.buffer = []; + this.doBuffer = false; + + if (this.options['sync disconnect on unload'] && + (!this.isXDomain() || io.util.ua.hasCORS)) { + var self = this; + + io.util.on(global, 'beforeunload', function () { + self.disconnectSync(); + }, false); + } + + if (this.options['auto connect']) { + this.connect(); + } +}; + + /** + * Apply EventEmitter mixin. + */ + + io.util.mixin(Socket, io.EventEmitter); + + /** + * Returns a namespace listener/emitter for this socket + * + * @api public + */ + + Socket.prototype.of = function (name) { + if (!this.namespaces[name]) { + this.namespaces[name] = new io.SocketNamespace(this, name); + + if (name !== '') { + this.namespaces[name].packet({ type: 'connect' }); + } + } + + return this.namespaces[name]; + }; + + /** + * Emits the given event to the Socket and all namespaces + * + * @api private + */ + + Socket.prototype.publish = function () { + this.emit.apply(this, arguments); + + var nsp; + + for (var i in this.namespaces) { + if (this.namespaces.hasOwnProperty(i)) { + nsp = this.of(i); + nsp.$emit.apply(nsp, arguments); + } + } + }; + + /** + * Performs the handshake + * + * @api private + */ + + function empty () { }; + + Socket.prototype.handshake = function (fn) { + var self = this + , options = this.options; + + function complete (data) { + if (data instanceof Error) { + self.onError(data.message); + } else { + fn.apply(null, data.split(':')); + } + }; + + var url = [ + 'http' + (options.secure ? 's' : '') + ':/' + , options.host + ':' + options.port + , options.resource + , io.protocol + , io.util.query(this.options.query, 't=' + +new Date) + ].join('/'); + + if (this.isXDomain() && !io.util.ua.hasCORS) { + var insertAt = document.getElementsByTagName('script')[0] + , script = document.createElement('script'); + + script.src = url + '&jsonp=' + io.j.length; + insertAt.parentNode.insertBefore(script, insertAt); + + io.j.push(function (data) { + complete(data); + script.parentNode.removeChild(script); + }); + } else { + var xhr = io.util.request(); + + xhr.open('GET', url, true); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + xhr.onreadystatechange = empty; + + if (xhr.status == 200) { + complete(xhr.responseText); + } else { + !self.reconnecting && self.onError(xhr.responseText); + } + } + }; + xhr.send(null); + } + }; + + /** + * Find an available transport based on the options supplied in the constructor. + * + * @api private + */ + + Socket.prototype.getTransport = function (override) { + var transports = override || this.transports, match; + + for (var i = 0, transport; transport = transports[i]; i++) { + if (io.Transport[transport] + && io.Transport[transport].check(this) + && (!this.isXDomain() || io.Transport[transport].xdomainCheck())) { + return new io.Transport[transport](this, this.sessionid); + } + } + + return null; + }; + + /** + * Connects to the server. + * + * @param {Function} [fn] Callback. + * @returns {io.Socket} + * @api public + */ + + Socket.prototype.connect = function (fn) { + if (this.connecting) { + return this; + } + + var self = this; + + this.handshake(function (sid, heartbeat, close, transports) { + self.sessionid = sid; + self.closeTimeout = close * 1000; + self.heartbeatTimeout = heartbeat * 1000; + self.transports = io.util.intersect( + transports.split(',') + , self.options.transports + ); + + function connect (transports){ + if (self.transport) self.transport.clearTimeouts(); + + self.transport = self.getTransport(transports); + if (!self.transport) return self.publish('connect_failed'); + + // once the transport is ready + self.transport.ready(self, function () { + self.connecting = true; + self.publish('connecting', self.transport.name); + self.transport.open(); + + if (self.options['connect timeout']) { + self.connectTimeoutTimer = setTimeout(function () { + if (!self.connected) { + self.connecting = false; + + if (self.options['try multiple transports']) { + if (!self.remainingTransports) { + self.remainingTransports = self.transports.slice(0); + } + + var remaining = self.remainingTransports; + + while (remaining.length > 0 && remaining.splice(0,1)[0] != + self.transport.name) {} + + if (remaining.length){ + connect(remaining); + } else { + self.publish('connect_failed'); + } + } + } + }, self.options['connect timeout']); + } + }); + } + + connect(); + + self.once('connect', function (){ + clearTimeout(self.connectTimeoutTimer); + + fn && typeof fn == 'function' && fn(); + }); + }); + + return this; + }; + + /** + * Sends a message. + * + * @param {Object} data packet. + * @returns {io.Socket} + * @api public + */ + + Socket.prototype.packet = function (data) { + if (this.connected && !this.doBuffer) { + this.transport.packet(data); + } else { + this.buffer.push(data); + } + + return this; + }; + + /** + * Sets buffer state + * + * @api private + */ + + Socket.prototype.setBuffer = function (v) { + this.doBuffer = v; + + if (!v && this.connected && this.buffer.length) { + this.transport.payload(this.buffer); + this.buffer = []; + } + }; + + /** + * Disconnect the established connect. + * + * @returns {io.Socket} + * @api public + */ + + Socket.prototype.disconnect = function () { + if (this.connected) { + if (this.open) { + this.of('').packet({ type: 'disconnect' }); + } + + // handle disconnection immediately + this.onDisconnect('booted'); + } + + return this; + }; + + /** + * Disconnects the socket with a sync XHR. + * + * @api private + */ + + Socket.prototype.disconnectSync = function () { + // ensure disconnection + var xhr = io.util.request() + , uri = this.resource + '/' + io.protocol + '/' + this.sessionid; + + xhr.open('GET', uri, true); + + // handle disconnection immediately + this.onDisconnect('booted'); + }; + + /** + * Check if we need to use cross domain enabled transports. Cross domain would + * be a different port or different domain name. + * + * @returns {Boolean} + * @api private + */ + + Socket.prototype.isXDomain = function () { + + var port = global.location.port || + ('https:' == global.location.protocol ? 443 : 80); + + return this.options.host !== global.location.hostname + || this.options.port != port; + }; + + /** + * Called upon handshake. + * + * @api private + */ + + Socket.prototype.onConnect = function () { + if (!this.connected) { + this.connected = true; + this.connecting = false; + if (!this.doBuffer) { + // make sure to flush the buffer + this.setBuffer(false); + } + this.emit('connect'); + } + }; + + /** + * Called when the transport opens + * + * @api private + */ + + Socket.prototype.onOpen = function () { + this.open = true; + }; + + /** + * Called when the transport closes. + * + * @api private + */ + + Socket.prototype.onClose = function () { + this.open = false; + }; + + /** + * Called when the transport first opens a connection + * + * @param text + */ + + Socket.prototype.onPacket = function (packet) { + this.of(packet.endpoint).onPacket(packet); + }; + + /** + * Handles an error. + * + * @api private + */ + + Socket.prototype.onError = function (err) { + if (err && err.advice) { + if (err.advice === 'reconnect' && this.connected) { + this.disconnect(); + this.reconnect(); + } + } + + this.publish('error', err && err.reason ? err.reason : err); + }; + + /** + * Called when the transport disconnects. + * + * @api private + */ + + Socket.prototype.onDisconnect = function (reason) { + var wasConnected = this.connected; + + this.connected = false; + this.connecting = false; + this.open = false; + + if (wasConnected) { + this.transport.close(); + this.transport.clearTimeouts(); + this.publish('disconnect', reason); + + if ('booted' != reason && this.options.reconnect && !this.reconnecting) { + this.reconnect(); + } + } + }; + + /** + * Called upon reconnection. + * + * @api private + */ + + Socket.prototype.reconnect = function () { + this.reconnecting = true; + this.reconnectionAttempts = 0; + this.reconnectionDelay = this.options['reconnection delay']; + + var self = this + , maxAttempts = this.options['max reconnection attempts'] + , tryMultiple = this.options['try multiple transports'] + , limit = this.options['reconnection limit']; + + function reset () { + if (self.connected) { + for (var i in self.namespaces) { + if (self.namespaces.hasOwnProperty(i) && '' !== i) { + self.namespaces[i].packet({ type: 'connect' }); + } + } + self.publish('reconnect', self.transport.name, self.reconnectionAttempts); + } + + self.removeListener('connect_failed', maybeReconnect); + self.removeListener('connect', maybeReconnect); + + self.reconnecting = false; + + delete self.reconnectionAttempts; + delete self.reconnectionDelay; + delete self.reconnectionTimer; + delete self.redoTransports; + + self.options['try multiple transports'] = tryMultiple; + }; + + function maybeReconnect () { + if (!self.reconnecting) { + return; + } + + if (self.connected) { + return reset(); + }; + + if (self.connecting && self.reconnecting) { + return self.reconnectionTimer = setTimeout(maybeReconnect, 1000); + } + + if (self.reconnectionAttempts++ >= maxAttempts) { + if (!self.redoTransports) { + self.on('connect_failed', maybeReconnect); + self.options['try multiple transports'] = true; + self.transport = self.getTransport(); + self.redoTransports = true; + self.connect(); + } else { + self.publish('reconnect_failed'); + reset(); + } + } else { + if (self.reconnectionDelay < limit) { + self.reconnectionDelay *= 2; // exponential back off + } + + self.connect(); + self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts); + self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay); + } + }; + + this.options['try multiple transports'] = false; + this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay); + + this.on('connect', maybeReconnect); + }; + +})( + 'undefined' != typeof io ? io : module.exports + , 'undefined' != typeof io ? io : module.parent.exports + , this +); +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io) { + + /** + * Expose constructor. + */ + + exports.SocketNamespace = SocketNamespace; + + /** + * Socket namespace constructor. + * + * @constructor + * @api public + */ + + function SocketNamespace (socket, name) { + this.socket = socket; + this.name = name || ''; + this.flags = {}; + this.json = new Flag(this, 'json'); + this.ackPackets = 0; + this.acks = {}; + }; + + /** + * Apply EventEmitter mixin. + */ + + io.util.mixin(SocketNamespace, io.EventEmitter); + + /** + * Copies emit since we override it + * + * @api private + */ + + SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; + + /** + * Creates a new namespace, by proxying the request to the socket. This + * allows us to use the synax as we do on the server. + * + * @api public + */ + + SocketNamespace.prototype.of = function () { + return this.socket.of.apply(this.socket, arguments); + }; + + /** + * Sends a packet. + * + * @api private + */ + + SocketNamespace.prototype.packet = function (packet) { + packet.endpoint = this.name; + this.socket.packet(packet); + this.flags = {}; + return this; + }; + + /** + * Sends a message + * + * @api public + */ + + SocketNamespace.prototype.send = function (data, fn) { + var packet = { + type: this.flags.json ? 'json' : 'message' + , data: data + }; + + if ('function' == typeof fn) { + packet.id = ++this.ackPackets; + packet.ack = true; + this.acks[packet.id] = fn; + } + + return this.packet(packet); + }; + + /** + * Emits an event + * + * @api public + */ + + SocketNamespace.prototype.emit = function (name) { + var args = Array.prototype.slice.call(arguments, 1) + , lastArg = args[args.length - 1] + , packet = { + type: 'event' + , name: name + }; + + if ('function' == typeof lastArg) { + packet.id = ++this.ackPackets; + packet.ack = 'data'; + this.acks[packet.id] = lastArg; + args = args.slice(0, args.length - 1); + } + + packet.args = args; + + return this.packet(packet); + }; + + /** + * Disconnects the namespace + * + * @api private + */ + + SocketNamespace.prototype.disconnect = function () { + if (this.name === '') { + this.socket.disconnect(); + } else { + this.packet({ type: 'disconnect' }); + this.$emit('disconnect'); + } + + return this; + }; + + /** + * Handles a packet + * + * @api private + */ + + SocketNamespace.prototype.onPacket = function (packet) { + var self = this; + + function ack () { + self.packet({ + type: 'ack' + , args: io.util.toArray(arguments) + , ackId: packet.id + }); + }; + + switch (packet.type) { + case 'connect': + this.$emit('connect'); + break; + + case 'disconnect': + if (this.name === '') { + this.socket.onDisconnect(packet.reason || 'booted'); + } else { + this.$emit('disconnect', packet.reason); + } + break; + + case 'message': + case 'json': + var params = ['message', packet.data]; + + if (packet.ack == 'data') { + params.push(ack); + } else if (packet.ack) { + this.packet({ type: 'ack', ackId: packet.id }); + } + + this.$emit.apply(this, params); + break; + + case 'event': + var params = [packet.name].concat(packet.args); + + if (packet.ack == 'data') + params.push(ack); + + this.$emit.apply(this, params); + break; + + case 'ack': + if (this.acks[packet.ackId]) { + this.acks[packet.ackId].apply(this, packet.args); + delete this.acks[packet.ackId]; + } + break; + + case 'error': + if (packet.advice){ + this.socket.onError(packet); + } else { + if (packet.reason == 'unauthorized') { + this.$emit('connect_failed', packet.reason); + } else { + this.$emit('error', packet.reason); + } + } + break; + } + }; + + /** + * Flag interface. + * + * @api private + */ + + function Flag (nsp, name) { + this.namespace = nsp; + this.name = name; + }; + + /** + * Send a message + * + * @api public + */ + + Flag.prototype.send = function () { + this.namespace.flags[this.name] = true; + this.namespace.send.apply(this.namespace, arguments); + }; + + /** + * Emit an event + * + * @api public + */ + + Flag.prototype.emit = function () { + this.namespace.flags[this.name] = true; + this.namespace.emit.apply(this.namespace, arguments); + }; + +})( + 'undefined' != typeof io ? io : module.exports + , 'undefined' != typeof io ? io : module.parent.exports +); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io, global) { + + /** + * Expose constructor. + */ + + exports.websocket = WS; + + /** + * The WebSocket transport uses the HTML5 WebSocket API to establish an + * persistent connection with the Socket.IO server. This transport will also + * be inherited by the FlashSocket fallback as it provides a API compatible + * polyfill for the WebSockets. + * + * @constructor + * @extends {io.Transport} + * @api public + */ + + function WS (socket) { + io.Transport.apply(this, arguments); + }; + + /** + * Inherits from Transport. + */ + + io.util.inherit(WS, io.Transport); + + /** + * Transport name + * + * @api public + */ + + WS.prototype.name = 'websocket'; + + /** + * Initializes a new `WebSocket` connection with the Socket.IO server. We attach + * all the appropriate listeners to handle the responses from the server. + * + * @returns {Transport} + * @api public + */ + + WS.prototype.open = function () { + var query = io.util.query(this.socket.options.query) + , self = this + , Socket + + + if (!Socket) { + Socket = global.MozWebSocket || global.WebSocket; + } + + this.websocket = new Socket(this.prepareUrl() + query); + + this.websocket.onopen = function () { + self.onOpen(); + self.socket.setBuffer(false); + }; + this.websocket.onmessage = function (ev) { + self.onData(ev.data); + }; + this.websocket.onclose = function () { + self.onClose(); + self.socket.setBuffer(true); + }; + this.websocket.onerror = function (e) { + self.onError(e); + }; + + return this; + }; + + /** + * Send a message to the Socket.IO server. The message will automatically be + * encoded in the correct message format. + * + * @returns {Transport} + * @api public + */ + + WS.prototype.send = function (data) { + this.websocket.send(data); + return this; + }; + + /** + * Payload + * + * @api private + */ + + WS.prototype.payload = function (arr) { + for (var i = 0, l = arr.length; i < l; i++) { + this.packet(arr[i]); + } + return this; + }; + + /** + * Disconnect the established `WebSocket` connection. + * + * @returns {Transport} + * @api public + */ + + WS.prototype.close = function () { + this.websocket.close(); + return this; + }; + + /** + * Handle the errors that `WebSocket` might be giving when we + * are attempting to connect or send messages. + * + * @param {Error} e The error. + * @api private + */ + + WS.prototype.onError = function (e) { + this.socket.onError(e); + }; + + /** + * Returns the appropriate scheme for the URI generation. + * + * @api private + */ + WS.prototype.scheme = function () { + return this.socket.options.secure ? 'wss' : 'ws'; + }; + + /** + * Checks if the browser has support for native `WebSockets` and that + * it's not the polyfill created for the FlashSocket transport. + * + * @return {Boolean} + * @api public + */ + + WS.check = function () { + return ('WebSocket' in global && !('__addTask' in WebSocket)) + || 'MozWebSocket' in global; + }; + + /** + * Check if the `WebSocket` transport support cross domain communications. + * + * @returns {Boolean} + * @api public + */ + + WS.xdomainCheck = function () { + return true; + }; + + /** + * Add the transport to your public io.transports array. + * + * @api private + */ + + io.transports.push('websocket'); + +})( + 'undefined' != typeof io ? io.Transport : module.exports + , 'undefined' != typeof io ? io : module.parent.exports + , this +); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io) { + + /** + * Expose constructor. + */ + + exports.flashsocket = Flashsocket; + + /** + * The FlashSocket transport. This is a API wrapper for the HTML5 WebSocket + * specification. It uses a .swf file to communicate with the server. If you want + * to serve the .swf file from a other server than where the Socket.IO script is + * coming from you need to use the insecure version of the .swf. More information + * about this can be found on the github page. + * + * @constructor + * @extends {io.Transport.websocket} + * @api public + */ + + function Flashsocket () { + io.Transport.websocket.apply(this, arguments); + }; + + /** + * Inherits from Transport. + */ + + io.util.inherit(Flashsocket, io.Transport.websocket); + + /** + * Transport name + * + * @api public + */ + + Flashsocket.prototype.name = 'flashsocket'; + + /** + * Disconnect the established `FlashSocket` connection. This is done by adding a + * new task to the FlashSocket. The rest will be handled off by the `WebSocket` + * transport. + * + * @returns {Transport} + * @api public + */ + + Flashsocket.prototype.open = function () { + var self = this + , args = arguments; + + WebSocket.__addTask(function () { + io.Transport.websocket.prototype.open.apply(self, args); + }); + return this; + }; + + /** + * Sends a message to the Socket.IO server. This is done by adding a new + * task to the FlashSocket. The rest will be handled off by the `WebSocket` + * transport. + * + * @returns {Transport} + * @api public + */ + + Flashsocket.prototype.send = function () { + var self = this, args = arguments; + WebSocket.__addTask(function () { + io.Transport.websocket.prototype.send.apply(self, args); + }); + return this; + }; + + /** + * Disconnects the established `FlashSocket` connection. + * + * @returns {Transport} + * @api public + */ + + Flashsocket.prototype.close = function () { + WebSocket.__tasks.length = 0; + io.Transport.websocket.prototype.close.call(this); + return this; + }; + + /** + * The WebSocket fall back needs to append the flash container to the body + * element, so we need to make sure we have access to it. Or defer the call + * until we are sure there is a body element. + * + * @param {Socket} socket The socket instance that needs a transport + * @param {Function} fn The callback + * @api private + */ + + Flashsocket.prototype.ready = function (socket, fn) { + function init () { + var options = socket.options + , port = options['flash policy port'] + , path = [ + 'http' + (options.secure ? 's' : '') + ':/' + , options.host + ':' + options.port + , options.resource + , 'static/flashsocket' + , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf' + ]; + + // Only start downloading the swf file when the checked that this browser + // actually supports it + if (!Flashsocket.loaded) { + if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') { + // Set the correct file based on the XDomain settings + WEB_SOCKET_SWF_LOCATION = path.join('/'); + } + + if (port !== 843) { + WebSocket.loadFlashPolicyFile('xmlsocket://' + options.host + ':' + port); + } + + WebSocket.__initialize(); + Flashsocket.loaded = true; + } + + fn.call(self); + } + + var self = this; + if (document.body) return init(); + + io.util.load(init); + }; + + /** + * Check if the FlashSocket transport is supported as it requires that the Adobe + * Flash Player plug-in version `10.0.0` or greater is installed. And also check if + * the polyfill is correctly loaded. + * + * @returns {Boolean} + * @api public + */ + + Flashsocket.check = function () { + if ( + typeof WebSocket == 'undefined' + || !('__initialize' in WebSocket) || !swfobject + ) return false; + + return swfobject.getFlashPlayerVersion().major >= 10; + }; + + /** + * Check if the FlashSocket transport can be used as cross domain / cross origin + * transport. Because we can't see which type (secure or insecure) of .swf is used + * we will just return true. + * + * @returns {Boolean} + * @api public + */ + + Flashsocket.xdomainCheck = function () { + return true; + }; + + /** + * Disable AUTO_INITIALIZATION + */ + + if (typeof window != 'undefined') { + WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true; + } + + /** + * Add the transport to your public io.transports array. + * + * @api private + */ + + io.transports.push('flashsocket'); +})( + 'undefined' != typeof io ? io.Transport : module.exports + , 'undefined' != typeof io ? io : module.parent.exports +); +/* SWFObject v2.2 + is released under the MIT License +*/ +if ('undefined' != typeof window) { +var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab +// License: New BSD License +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol + +(function() { + + if ('undefined' == typeof window || window.WebSocket) return; + + var console = window.console; + if (!console || !console.log || !console.error) { + console = {log: function(){ }, error: function(){ }}; + } + + if (!swfobject.hasFlashPlayerVersion("10.0.0")) { + console.error("Flash Player >= 10.0.0 is required."); + return; + } + if (location.protocol == "file:") { + console.error( + "WARNING: web-socket-js doesn't work in file:///... URL " + + "unless you set Flash Security Settings properly. " + + "Open the page via Web server i.e. http://..."); + } + + /** + * This class represents a faux web socket. + * @param {string} url + * @param {array or string} protocols + * @param {string} proxyHost + * @param {int} proxyPort + * @param {string} headers + */ + WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { + var self = this; + self.__id = WebSocket.__nextId++; + WebSocket.__instances[self.__id] = self; + self.readyState = WebSocket.CONNECTING; + self.bufferedAmount = 0; + self.__events = {}; + if (!protocols) { + protocols = []; + } else if (typeof protocols == "string") { + protocols = [protocols]; + } + // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. + // Otherwise, when onopen fires immediately, onopen is called before it is set. + setTimeout(function() { + WebSocket.__addTask(function() { + WebSocket.__flash.create( + self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); + }); + }, 0); + }; + + /** + * Send data to the web socket. + * @param {string} data The data to send to the socket. + * @return {boolean} True for success, false for failure. + */ + WebSocket.prototype.send = function(data) { + if (this.readyState == WebSocket.CONNECTING) { + throw "INVALID_STATE_ERR: Web Socket connection has not been established"; + } + // We use encodeURIComponent() here, because FABridge doesn't work if + // the argument includes some characters. We don't use escape() here + // because of this: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions + // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't + // preserve all Unicode characters either e.g. "\uffff" in Firefox. + // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require + // additional testing. + var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); + if (result < 0) { // success + return true; + } else { + this.bufferedAmount += result; + return false; + } + }; + + /** + * Close this web socket gracefully. + */ + WebSocket.prototype.close = function() { + if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { + return; + } + this.readyState = WebSocket.CLOSING; + WebSocket.__flash.close(this.__id); + }; + + /** + * Implementation of {@link DOM 2 EventTarget Interface} + * + * @param {string} type + * @param {function} listener + * @param {boolean} useCapture + * @return void + */ + WebSocket.prototype.addEventListener = function(type, listener, useCapture) { + if (!(type in this.__events)) { + this.__events[type] = []; + } + this.__events[type].push(listener); + }; + + /** + * Implementation of {@link DOM 2 EventTarget Interface} + * + * @param {string} type + * @param {function} listener + * @param {boolean} useCapture + * @return void + */ + WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { + if (!(type in this.__events)) return; + var events = this.__events[type]; + for (var i = events.length - 1; i >= 0; --i) { + if (events[i] === listener) { + events.splice(i, 1); + break; + } + } + }; + + /** + * Implementation of {@link DOM 2 EventTarget Interface} + * + * @param {Event} event + * @return void + */ + WebSocket.prototype.dispatchEvent = function(event) { + var events = this.__events[event.type] || []; + for (var i = 0; i < events.length; ++i) { + events[i](event); + } + var handler = this["on" + event.type]; + if (handler) handler(event); + }; + + /** + * Handles an event from Flash. + * @param {Object} flashEvent + */ + WebSocket.prototype.__handleEvent = function(flashEvent) { + if ("readyState" in flashEvent) { + this.readyState = flashEvent.readyState; + } + if ("protocol" in flashEvent) { + this.protocol = flashEvent.protocol; + } + + var jsEvent; + if (flashEvent.type == "open" || flashEvent.type == "error") { + jsEvent = this.__createSimpleEvent(flashEvent.type); + } else if (flashEvent.type == "close") { + // TODO implement jsEvent.wasClean + jsEvent = this.__createSimpleEvent("close"); + } else if (flashEvent.type == "message") { + var data = decodeURIComponent(flashEvent.message); + jsEvent = this.__createMessageEvent("message", data); + } else { + throw "unknown event type: " + flashEvent.type; + } + + this.dispatchEvent(jsEvent); + }; + + WebSocket.prototype.__createSimpleEvent = function(type) { + if (document.createEvent && window.Event) { + var event = document.createEvent("Event"); + event.initEvent(type, false, false); + return event; + } else { + return {type: type, bubbles: false, cancelable: false}; + } + }; + + WebSocket.prototype.__createMessageEvent = function(type, data) { + if (document.createEvent && window.MessageEvent && !window.opera) { + var event = document.createEvent("MessageEvent"); + event.initMessageEvent("message", false, false, data, null, null, window, null); + return event; + } else { + // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. + return {type: type, data: data, bubbles: false, cancelable: false}; + } + }; + + /** + * Define the WebSocket readyState enumeration. + */ + WebSocket.CONNECTING = 0; + WebSocket.OPEN = 1; + WebSocket.CLOSING = 2; + WebSocket.CLOSED = 3; + + WebSocket.__flash = null; + WebSocket.__instances = {}; + WebSocket.__tasks = []; + WebSocket.__nextId = 0; + + /** + * Load a new flash security policy file. + * @param {string} url + */ + WebSocket.loadFlashPolicyFile = function(url){ + WebSocket.__addTask(function() { + WebSocket.__flash.loadManualPolicyFile(url); + }); + }; + + /** + * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. + */ + WebSocket.__initialize = function() { + if (WebSocket.__flash) return; + + if (WebSocket.__swfLocation) { + // For backword compatibility. + window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; + } + if (!window.WEB_SOCKET_SWF_LOCATION) { + console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); + return; + } + var container = document.createElement("div"); + container.id = "webSocketContainer"; + // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents + // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). + // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash + // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is + // the best we can do as far as we know now. + container.style.position = "absolute"; + if (WebSocket.__isFlashLite()) { + container.style.left = "0px"; + container.style.top = "0px"; + } else { + container.style.left = "-100px"; + container.style.top = "-100px"; + } + var holder = document.createElement("div"); + holder.id = "webSocketFlash"; + container.appendChild(holder); + document.body.appendChild(container); + // See this article for hasPriority: + // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html + swfobject.embedSWF( + WEB_SOCKET_SWF_LOCATION, + "webSocketFlash", + "1" /* width */, + "1" /* height */, + "10.0.0" /* SWF version */, + null, + null, + {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, + null, + function(e) { + if (!e.success) { + console.error("[WebSocket] swfobject.embedSWF failed"); + } + }); + }; + + /** + * Called by Flash to notify JS that it's fully loaded and ready + * for communication. + */ + WebSocket.__onFlashInitialized = function() { + // We need to set a timeout here to avoid round-trip calls + // to flash during the initialization process. + setTimeout(function() { + WebSocket.__flash = document.getElementById("webSocketFlash"); + WebSocket.__flash.setCallerUrl(location.href); + WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); + for (var i = 0; i < WebSocket.__tasks.length; ++i) { + WebSocket.__tasks[i](); + } + WebSocket.__tasks = []; + }, 0); + }; + + /** + * Called by Flash to notify WebSockets events are fired. + */ + WebSocket.__onFlashEvent = function() { + setTimeout(function() { + try { + // Gets events using receiveEvents() instead of getting it from event object + // of Flash event. This is to make sure to keep message order. + // It seems sometimes Flash events don't arrive in the same order as they are sent. + var events = WebSocket.__flash.receiveEvents(); + for (var i = 0; i < events.length; ++i) { + WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); + } + } catch (e) { + console.error(e); + } + }, 0); + return true; + }; + + // Called by Flash. + WebSocket.__log = function(message) { + console.log(decodeURIComponent(message)); + }; + + // Called by Flash. + WebSocket.__error = function(message) { + console.error(decodeURIComponent(message)); + }; + + WebSocket.__addTask = function(task) { + if (WebSocket.__flash) { + task(); + } else { + WebSocket.__tasks.push(task); + } + }; + + /** + * Test if the browser is running flash lite. + * @return {boolean} True if flash lite is running, false otherwise. + */ + WebSocket.__isFlashLite = function() { + if (!window.navigator || !window.navigator.mimeTypes) { + return false; + } + var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; + if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { + return false; + } + return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; + }; + + if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { + if (window.addEventListener) { + window.addEventListener("load", function(){ + WebSocket.__initialize(); + }, false); + } else { + window.attachEvent("onload", function(){ + WebSocket.__initialize(); + }); + } + } + +})(); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io, global) { + + /** + * Expose constructor. + * + * @api public + */ + + exports.XHR = XHR; + + /** + * XHR constructor + * + * @costructor + * @api public + */ + + function XHR (socket) { + if (!socket) return; + + io.Transport.apply(this, arguments); + this.sendBuffer = []; + }; + + /** + * Inherits from Transport. + */ + + io.util.inherit(XHR, io.Transport); + + /** + * Establish a connection + * + * @returns {Transport} + * @api public + */ + + XHR.prototype.open = function () { + this.socket.setBuffer(false); + this.onOpen(); + this.get(); + + // we need to make sure the request succeeds since we have no indication + // whether the request opened or not until it succeeded. + this.setCloseTimeout(); + + return this; + }; + + /** + * Check if we need to send data to the Socket.IO server, if we have data in our + * buffer we encode it and forward it to the `post` method. + * + * @api private + */ + + XHR.prototype.payload = function (payload) { + var msgs = []; + + for (var i = 0, l = payload.length; i < l; i++) { + msgs.push(io.parser.encodePacket(payload[i])); + } + + this.send(io.parser.encodePayload(msgs)); + }; + + /** + * Send data to the Socket.IO server. + * + * @param data The message + * @returns {Transport} + * @api public + */ + + XHR.prototype.send = function (data) { + this.post(data); + return this; + }; + + /** + * Posts a encoded message to the Socket.IO server. + * + * @param {String} data A encoded message. + * @api private + */ + + function empty () { }; + + XHR.prototype.post = function (data) { + var self = this; + this.socket.setBuffer(true); + + function stateChange () { + if (this.readyState == 4) { + this.onreadystatechange = empty; + self.posting = false; + + if (this.status == 200){ + self.socket.setBuffer(false); + } else { + self.onClose(); + } + } + } + + function onload () { + this.onload = empty; + self.socket.setBuffer(false); + }; + + this.sendXHR = this.request('POST'); + + if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) { + this.sendXHR.onload = this.sendXHR.onerror = onload; + } else { + this.sendXHR.onreadystatechange = stateChange; + } + + this.sendXHR.send(data); + }; + + /** + * Disconnects the established `XHR` connection. + * + * @returns {Transport} + * @api public + */ + + XHR.prototype.close = function () { + this.onClose(); + return this; + }; + + /** + * Generates a configured XHR request + * + * @param {String} url The url that needs to be requested. + * @param {String} method The method the request should use. + * @returns {XMLHttpRequest} + * @api private + */ + + XHR.prototype.request = function (method) { + var req = io.util.request(this.socket.isXDomain()) + , query = io.util.query(this.socket.options.query, 't=' + +new Date); + + req.open(method || 'GET', this.prepareUrl() + query, true); + + if (method == 'POST') { + try { + if (req.setRequestHeader) { + req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); + } else { + // XDomainRequest + req.contentType = 'text/plain'; + } + } catch (e) {} + } + + return req; + }; + + /** + * Returns the scheme to use for the transport URLs. + * + * @api private + */ + + XHR.prototype.scheme = function () { + return this.socket.options.secure ? 'https' : 'http'; + }; + + /** + * Check if the XHR transports are supported + * + * @param {Boolean} xdomain Check if we support cross domain requests. + * @returns {Boolean} + * @api public + */ + + XHR.check = function (socket, xdomain) { + try { + if (io.util.request(xdomain)) { + return true; + } + } catch(e) {} + + return false; + }; + + /** + * Check if the XHR transport supports corss domain requests. + * + * @returns {Boolean} + * @api public + */ + + XHR.xdomainCheck = function () { + return XHR.check(null, true); + }; + +})( + 'undefined' != typeof io ? io.Transport : module.exports + , 'undefined' != typeof io ? io : module.parent.exports + , this +); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io) { + + /** + * Expose constructor. + */ + + exports.htmlfile = HTMLFile; + + /** + * The HTMLFile transport creates a `forever iframe` based transport + * for Internet Explorer. Regular forever iframe implementations will + * continuously trigger the browsers buzy indicators. If the forever iframe + * is created inside a `htmlfile` these indicators will not be trigged. + * + * @constructor + * @extends {io.Transport.XHR} + * @api public + */ + + function HTMLFile (socket) { + io.Transport.XHR.apply(this, arguments); + }; + + /** + * Inherits from XHR transport. + */ + + io.util.inherit(HTMLFile, io.Transport.XHR); + + /** + * Transport name + * + * @api public + */ + + HTMLFile.prototype.name = 'htmlfile'; + + /** + * Creates a new ActiveX `htmlfile` with a forever loading iframe + * that can be used to listen to messages. Inside the generated + * `htmlfile` a reference will be made to the HTMLFile transport. + * + * @api private + */ + + HTMLFile.prototype.get = function () { + this.doc = new ActiveXObject('htmlfile'); + this.doc.open(); + this.doc.write(''); + this.doc.close(); + this.doc.parentWindow.s = this; + + var iframeC = this.doc.createElement('div'); + iframeC.className = 'socketio'; + + this.doc.body.appendChild(iframeC); + this.iframe = this.doc.createElement('iframe'); + + iframeC.appendChild(this.iframe); + + var self = this + , query = io.util.query(this.socket.options.query, 't='+ +new Date); + + this.iframe.src = this.prepareUrl() + query; + + io.util.on(window, 'unload', function () { + self.destroy(); + }); + }; + + /** + * The Socket.IO server will write script tags inside the forever + * iframe, this function will be used as callback for the incoming + * information. + * + * @param {String} data The message + * @param {document} doc Reference to the context + * @api private + */ + + HTMLFile.prototype._ = function (data, doc) { + this.onData(data); + try { + var script = doc.getElementsByTagName('script')[0]; + script.parentNode.removeChild(script); + } catch (e) { } + }; + + /** + * Destroy the established connection, iframe and `htmlfile`. + * And calls the `CollectGarbage` function of Internet Explorer + * to release the memory. + * + * @api private + */ + + HTMLFile.prototype.destroy = function () { + if (this.iframe){ + try { + this.iframe.src = 'about:blank'; + } catch(e){} + + this.doc = null; + this.iframe.parentNode.removeChild(this.iframe); + this.iframe = null; + + CollectGarbage(); + } + }; + + /** + * Disconnects the established connection. + * + * @returns {Transport} Chaining. + * @api public + */ + + HTMLFile.prototype.close = function () { + this.destroy(); + return io.Transport.XHR.prototype.close.call(this); + }; + + /** + * Checks if the browser supports this transport. The browser + * must have an `ActiveXObject` implementation. + * + * @return {Boolean} + * @api public + */ + + HTMLFile.check = function () { + if ('ActiveXObject' in window){ + try { + var a = new ActiveXObject('htmlfile'); + return a && io.Transport.XHR.check(); + } catch(e){} + } + return false; + }; + + /** + * Check if cross domain requests are supported. + * + * @returns {Boolean} + * @api public + */ + + HTMLFile.xdomainCheck = function () { + // we can probably do handling for sub-domains, we should + // test that it's cross domain but a subdomain here + return false; + }; + + /** + * Add the transport to your public io.transports array. + * + * @api private + */ + + io.transports.push('htmlfile'); + +})( + 'undefined' != typeof io ? io.Transport : module.exports + , 'undefined' != typeof io ? io : module.parent.exports +); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io, global) { + + /** + * Expose constructor. + */ + + exports['xhr-polling'] = XHRPolling; + + /** + * The XHR-polling transport uses long polling XHR requests to create a + * "persistent" connection with the server. + * + * @constructor + * @api public + */ + + function XHRPolling () { + io.Transport.XHR.apply(this, arguments); + }; + + /** + * Inherits from XHR transport. + */ + + io.util.inherit(XHRPolling, io.Transport.XHR); + + /** + * Merge the properties from XHR transport + */ + + io.util.merge(XHRPolling, io.Transport.XHR); + + /** + * Transport name + * + * @api public + */ + + XHRPolling.prototype.name = 'xhr-polling'; + + /** + * Establish a connection, for iPhone and Android this will be done once the page + * is loaded. + * + * @returns {Transport} Chaining. + * @api public + */ + + XHRPolling.prototype.open = function () { + var self = this; + + io.Transport.XHR.prototype.open.call(self); + return false; + }; + + /** + * Starts a XHR request to wait for incoming messages. + * + * @api private + */ + + function empty () {}; + + XHRPolling.prototype.get = function () { + if (!this.open) return; + + var self = this; + + function stateChange () { + if (this.readyState == 4) { + this.onreadystatechange = empty; + + if (this.status == 200) { + self.onData(this.responseText); + self.get(); + } else { + self.onClose(); + } + } + }; + + function onload () { + this.onload = empty; + self.onData(this.responseText); + self.get(); + }; + + this.xhr = this.request(); + + if (global.XDomainRequest && this.xhr instanceof XDomainRequest) { + this.xhr.onload = this.xhr.onerror = onload; + } else { + this.xhr.onreadystatechange = stateChange; + } + + this.xhr.send(null); + }; + + /** + * Handle the unclean close behavior. + * + * @api private + */ + + XHRPolling.prototype.onClose = function () { + io.Transport.XHR.prototype.onClose.call(this); + + if (this.xhr) { + this.xhr.onreadystatechange = this.xhr.onload = empty; + try { + this.xhr.abort(); + } catch(e){} + this.xhr = null; + } + }; + + /** + * Webkit based browsers show a infinit spinner when you start a XHR request + * before the browsers onload event is called so we need to defer opening of + * the transport until the onload event is called. Wrapping the cb in our + * defer method solve this. + * + * @param {Socket} socket The socket instance that needs a transport + * @param {Function} fn The callback + * @api private + */ + + XHRPolling.prototype.ready = function (socket, fn) { + var self = this; + + io.util.defer(function () { + fn.call(self); + }); + }; + + /** + * Add the transport to your public io.transports array. + * + * @api private + */ + + io.transports.push('xhr-polling'); + +})( + 'undefined' != typeof io ? io.Transport : module.exports + , 'undefined' != typeof io ? io : module.parent.exports + , this +); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io, global) { + /** + * There is a way to hide the loading indicator in Firefox. If you create and + * remove a iframe it will stop showing the current loading indicator. + * Unfortunately we can't feature detect that and UA sniffing is evil. + * + * @api private + */ + + var indicator = global.document && "MozAppearance" in + global.document.documentElement.style; + + /** + * Expose constructor. + */ + + exports['jsonp-polling'] = JSONPPolling; + + /** + * The JSONP transport creates an persistent connection by dynamically + * inserting a script tag in the page. This script tag will receive the + * information of the Socket.IO server. When new information is received + * it creates a new script tag for the new data stream. + * + * @constructor + * @extends {io.Transport.xhr-polling} + * @api public + */ + + function JSONPPolling (socket) { + io.Transport['xhr-polling'].apply(this, arguments); + + this.index = io.j.length; + + var self = this; + + io.j.push(function (msg) { + self._(msg); + }); + }; + + /** + * Inherits from XHR polling transport. + */ + + io.util.inherit(JSONPPolling, io.Transport['xhr-polling']); + + /** + * Transport name + * + * @api public + */ + + JSONPPolling.prototype.name = 'jsonp-polling'; + + /** + * Posts a encoded message to the Socket.IO server using an iframe. + * The iframe is used because script tags can create POST based requests. + * The iframe is positioned outside of the view so the user does not + * notice it's existence. + * + * @param {String} data A encoded message. + * @api private + */ + + JSONPPolling.prototype.post = function (data) { + var self = this + , query = io.util.query( + this.socket.options.query + , 't='+ (+new Date) + '&i=' + this.index + ); + + if (!this.form) { + var form = document.createElement('form') + , area = document.createElement('textarea') + , id = this.iframeId = 'socketio_iframe_' + this.index + , iframe; + + form.className = 'socketio'; + form.style.position = 'absolute'; + form.style.top = '-1000px'; + form.style.left = '-1000px'; + form.target = id; + form.method = 'POST'; + form.setAttribute('accept-charset', 'utf-8'); + area.name = 'd'; + form.appendChild(area); + document.body.appendChild(form); + + this.form = form; + this.area = area; + } + + this.form.action = this.prepareUrl() + query; + + function complete () { + initIframe(); + self.socket.setBuffer(false); + }; + + function initIframe () { + if (self.iframe) { + self.form.removeChild(self.iframe); + } + + try { + // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) + iframe = document.createElement('